Theme

Last Updated on : 2023-10-12 08:00:23download

Purpose

The theme serves the following purposes:

  1. Make a unified management of global styles.
  2. Facilitate style reuse and extension.
  3. Reduce the passing of excessive style properties.

Custom theme

You can override all theme configurations in the local theme configuration file (src/config/theme.js). Alternatively, you can extend the theme configuration in the project. Custom theme examples are as follows:

Note: for all theme variables, see the Quick reference table of themes at the end of this topic.

export default { // Override the local default theme variable global: { brand: "#ff0000", }, switchButton: { margin: 2, width: 40, height: 24, thumbSize: 20, }, // Extend theme configuration myExtendTheme: { customKey: "blue", }, };

API

Inject the global theme

First, add Theme(ThemeProvider) to the top level of the application, pass the theme to the React component tree, and then access the theme object in the following three ways.

import _ from "lodash"; import PropTypes from "prop-types"; import React, { Component } from "react"; import { View } from "react-native"; import { Provider, connect } from "react-redux"; import { TYSdk, Theme } from "tuya-panel-kit"; import { devInfoChange, deviceChange, responseUpdateDp, } from "./redux/modules/common"; import DebugView from "./components/DebugView"; const TYEvent = TYSdk.event; const TYDevice = TYSdk.device; const composeLayout = (store, component) => { const NavigatorLayoutContainer = connect(_.identity)(component); const ThemeContainer = connect(({ theme }) => ({ theme }))(Theme); const { dispatch } = store; TYEvent.on("deviceChanged", (data) => { dispatch(deviceChange(data)); }); // eslint-disable-next-line TYEvent.on("dpDataChange", (data) => { dispatch(responseUpdateDp(data)); }); TYEvent.on("AppOnline", (data) => { dispatch(deviceChange({ AppOnline: data.online })); }); TYEvent.on("deviceOnline", (data) => { dispatch(deviceChange({ deviceOnline: data.online })); }); class PanelComponent extends Component { static propTypes = { // eslint-disable-next-line devInfo: PropTypes.object.isRequired, }; constructor(props) { super(props); if (props && props.devInfo && props.devInfo.devId) { TYDevice.setDeviceInfo(props.devInfo); TYDevice.getDeviceInfo().then((data) => dispatch(devInfoChange(data))); // eslint-disable-next-line } else if (props.preload) { // do something } else { TYDevice.getDeviceInfo().then((data) => dispatch(devInfoChange(data))); } } render() { return ( <Provider store={store}> <ThemeContainer> <View style={{ flex: 1 }}> <NavigatorLayoutContainer /> <DebugView /> </View> </ThemeContainer> </Provider> ); } } return PanelComponent; }; export default composeLayout;

Obtain the global theme

  • styled: access the theme through the components wrapped by styled in styled-components.
import styled from "styled-components/native"; const defaultColor = "#333"; export const StyledTitle = styled(TYText).attrs({ type: "title", size: "small", })` color: ${(props) => getTheme(props, "list.fontColor", "#333")}; color: ${(props) => props.fontColor || props.theme.list.fontColor || props.theme.list.light.fontColor || "#333"}; `;
  • withTheme: access the theme through components wrapped by the withTheme higher-order function.
import React from "react"; import PropTypes from "prop-types"; import { View } from "react-native"; import { Utils } from "tuya-panel-kit"; const { withTheme } = Utils.ThemeUtils; const ThemedView = (props) => { const { theme } = props; return <View style={{ backgroundColor: theme.global.brand }} />; }; ThemedView.propTypes = { theme: PropTypes.object.isRequired, }; export default withTheme(ThemedView);
  • ThemeConsumer: receive the theme through the ThemeConsumer component.
import { Utils } from "tuya-panel-kit"; const { ThemeConsumer } = Utils.ThemeUtils; export const StyledIconFont = (props) => { return ( <ThemeConsumer> {(theme) => { const propsWithTheme = { ...props, theme }; return ( <IconFont color={getTheme( propsWithTheme, "list.iconColor", DEFAULT_THEME.iconColor )} {...props} /> ); }} </ThemeConsumer> ); };

Quick reference table of themes

import { Platform } from "react-native"; import { CoreUtils, RatioUtils } from "../../utils"; const { get } = CoreUtils; const { convertX: cx } = RatioUtils; /** * General auxiliary function */ const getBrandColor = (props) => get(props, "theme.global.brand", global.brand); const getDividerColor = (props) => get(props, "theme.global.dividerColor", global.dividerColor); const getTypedFontColor = (props, reverse = false) => { let type = get(props.theme, "type", "light"); if (reverse) type = type === "light" ? "dark" : "light"; const path = `global.text.${type}`; return get(props.theme, path, global.text[type]); }; // Adjust the font size according to the global font benchmark ratio const normalizeFont = (props, fontSize, lineHeight) => { const baseline = get(props, "theme.global.fontSizeBase", global.fontSizeBase); return { fontSize: fontSize * baseline, lineHeight: Math.round(lineHeight * baseline), // Xiaomi will crash if it is not an integer }; }; export default { type: "light", /** * Global basic variables */ global: { brand: "#FF4800", // Brand color (theme color) bgColor: "#f8f8f8", // Background color fontSizeBase: 1, // Font benchmark ratio dividerColor: "#e5e5e5", // Divider color success: "#00C800", // Success color warning: "#FAAE17", // Warning color error: "#F4182C", // Failure // info, // Information color (not available now) // disabled, // Disable the transparency (not available now) mask: "rgba(0, 0, 0, 0.7)", // Mask color text: { light: "#333", // Font color under the `light` dark: "#fff", // Font color under the `dark` }, }, /** * Font size variable */ text: { heading: { // Font size when type is heading, and size is small small: (props) => normalizeFont(props, 28, 40), normal: (props) => normalizeFont(props, 40, 56), large: (props) => normalizeFont(props, 72, 100), }, title: { // Font size when type is title, and size is small small: (props) => normalizeFont(props, 16, 22), normal: (props) => normalizeFont(props, 17, 24), large: (props) => normalizeFont(props, 20, 28), }, // The main font color #333 applies to title and above paragraph: { // Font size when type is paragraph, and size is small small: (props) => normalizeFont(props, 10, 14), normal: (props) => normalizeFont(props, 12, 17), large: (props) => normalizeFont(props, 14, 20), }, }, /** * Picker scroll selector variable */ picker: { fontSize: 16, // Picker font size fontColor: "#000", // Picker font color dividerColor: getDividerColor, // Reserved, and not supported by iOS now unitFontSize: 16, // Picker unit size unitFontColor: "#000", // Picker unit color }, /** * Button variable */ button: { margin: [0, 0, 0, 0], // Button container margin (top, right, bottom, and left) fontSize: 10, // Font size fontColor: getTypedFontColor, // Font color iconSize: 24, // Icon size iconColor: (props) => getTypedFontColor(props, props.type === "primary"), // Icon color bgWidth: null, // Button background width, which is adaptive inside the component by default bgHeight: null, // Button background height, which is adaptive inside the component by default bgRadius: null, // Button background border-radius, which is adaptive inside the component by default bgColor: getBrandColor, // Button background color, which follows the theme color by default }, /** * TopBar variable */ topbar: { background: "#fff", // Background color of the top bar color: "#000", // Font color of the top bar (including icon color) }, /** * SwitchButton switch variable */ switchButton: { width: 50, // Button width height: Platform.select({ // Button width web: 28, ios: 28, android: 14, }), thumbSize: 26, // Thumb width and height sizes margin: Platform.select({ // Margins around the thumb web: 1, ios: 1, android: 0, }), tintColor: "#e5e5e5", // Background color when disabled onTintColor: "#4CD964", // Background color when enabled thumbTintColor: "#fff", // Background color of the thumb when disabled onThumbTintColor: "#fff", // Background color of the thumb when enabled }, /** * Slider variable */ slider: { width: null, // Follow the parent container by default (slider width) trackRadius: 2, // Border-radius trackHeight: 4, // Slider height minimumTrackTintColor: getBrandColor, // Color of the minimum value maximumTrackTintColor: "#e5e5e5", // Color of the maximum value thumbSize: 24, // Thumb size thumbRadius: 14, // Thumb border-radius thumbTintColor: "#fff", // Thumb color }, /** * Checkbox variable */ checkbox: { size: 28, // Checkbox size fontColor: "#333", // Checkbox font color activeColor: "#3388FF", // The color when Checkbox is active disabledColor: "#333", // The color when Checkbox is disabled }, /** * List variable */ list: { boardBg: "#f8f8f8", // Container background color of the list iconColor: "rgba(51, 51, 51, 0.5)", // Icon color fontColor: "#333", // Title color subFontColor: "rgba(51, 51, 51, 0.5)", // Subtitle color descFontColor: "rgba(51, 51, 51, 0.5)", // Descriptive title color cellLine: "rgba(51, 51, 51, 0.1)", // Cell line color cellBg: "#fff", // Background color of the list item cellRadius: 0, // Border-radius of the list item margin: [0, 0, 0, 0], // Outer margin of the list item (top, right, bottom, and left) padding: [12, cx(16), 12, cx(16)], // Inner margin of the list item (top, right, bottom, and left) }, /** * BrickButton variable */ brickButton: { fontSize: 12, // Font size fontColor: "#fff", // Font color bgRadius: 24, // Background border-radius bgColor: getBrandColor, // Follow the theme color bgBorder: "transparent", // Background border bgBorderWidth: 0, // Background border width loadingColor: "#fff", // Loading color loadingBackground: "rgba(0,0,0,.1)", // Loading background color }, /** * Dialog variable */ dialog: { width: cx(315), // Container width of the pop-up window bg: "#fff", // Background color of the pop-up window radius: cx(8), // Container border-radius of the pop-up window cellHeight: 56, // List height (header and footer) lineColor: "#e5e5e5", // Color of the division line titleFontSize: 18, // Title font size titleFontColor: "#333", // Title color of the top bar subTitleFontSize: 16, // Subtitle font size subTitleFontColor: "#999", // Subtitle color of the top bar cancelFontSize: 16, // Font size of cancellation at the bottom bar cancelFontColor: "#666", // Font color of cancellation at the bottom bar confirmFontSize: 16, // Font size of confirmation at the bottom bar confirmFontColor: "#333", // Font color of confirmation at the bottom bar prompt: { bg: "#f8f8f8", // Background color of the input box radius: cx(4), // Border-radius of the input box padding: "12px 16px", // Padding of the input box placeholder: "#d6d6de", // Font color of the placeholder }, }, /** * Popup variable */ popup: { cellHeight: 48, // Height of the list item cellBg: "#fff", // Background color of the list titleRadius: cx(8), // Border-radius of the header footerRadius: 0, // Border-radius of the footer bottomBg: "#f5f5f5", // Background color of the bottom bar lineColor: "#e5e5e5", // Color of the division line titleFontSize: 14, // Title size of the top bar titleFontColor: "#999", // Title color of the top bar cancelFontSize: 16, // Font size of cancellation at the bottom bar cancelFontColor: "#666", // Font color of cancellation at the bottom bar confirmFontSize: 16, // Font size of confirmation at the bottom bar confirmFontColor: "#333", // Font color of confirmation at the bottom bar }, };