Theme
Feature
- Unified management of global styles
- Convenient style reuse and expansion
- Reduce excessive style props passing
Custom theme
You can in the local theme configuration file(
src/config/theme.js
) cover all theme configurations(All theme variables can be seen in the theme quick reference table at the bottom of the document),Of course, you can also expand the theme configuration in the project by yourself。
export default {// Override local default theme variablesglobal: {brand: '#ff0000',},switchButton: {margin: 2,width: 40,height: 24,cthumbSize: 20,},// Expand theme configurationmyExtendTheme: {customKey: 'blue',},};
API
1. Inject the global theme
Add Theme(ThemeProvider) to the top of the application,Pass the theme to the React component tree。Then, we can access the subject 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 { actions, ReduxState } from '@models';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('deviceDataChange', data => {switch (data.type) {case 'dpData':dispatch(actions.common.responseUpdateDp(data.payload));break;default:dispatch(actions.common.deviceChange(data.payload));break;}});TYEvent.on('networkStateChange', data => {dispatch(actions.common.deviceChange(data));});class PanelComponent extends Component {static propTypes = {// eslint-disable-next-linedevInfo: 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;
2. Get global theme
- Access the theme through 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 withTheme higher-order functions
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 via 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 (<IconFontcolor={getTheme(propsWithTheme,'list.iconColor',DEFAULT_THEME.iconColor,)}{...props}/>);}}</ThemeConsumer>);};
Theme cheat sheet
import { Platform } from 'react-native';import { CoreUtils, RatioUtils } from '../../utils';const { get } = CoreUtils;const { convertX: cx } = RatioUtils;/*** General auxiliary functions*/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 ratioconst normalizeFont = (props, fontSize, lineHeight) => {const baseline = get(props, 'theme.global.fontSizeBase', global.fontSizeBase);return {fontSize: fontSize * baseline,lineHeight: Math.round(lineHeight * baseline), // Mi 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 colorfontSizeBase: 1, // Font base ratiodividerColor: '#e5e5e5', // Divider colorsuccess: '#00C800', // Success colorwarning: '#FAAE17', // Warning colorerror: '#F4182C', // failure// info, // Information color (not yet open to use)// disabled, // Disable transparency (not yet open to use)mask: 'rgba(0, 0, 0, 0.7)', // Mask colortext: {light: '#333', // The color of the font under lightdark: '#fff', // The color of the font under dark},},/*** Font size variable*/text: {heading: {// The font size corresponding to type as heading and size as smallsmall: props => normalizeFont(props, 28, 40),normal: props => normalizeFont(props, 40, 56),large: props => normalizeFont(props, 72, 100),},title: {// The font size corresponding to type as title and size as smallsmall: props => normalizeFont(props, 16, 22),normal: props => normalizeFont(props, 17, 24),large: props => normalizeFont(props, 20, 28),},paragraph: {// The font size corresponding to type as paragraph and size as smallsmall: props => normalizeFont(props, 10, 14),normal: props => normalizeFont(props, 12, 17),large: props => normalizeFont(props, 14, 20),},},/*** Picker scroll picker variable*/picker: {fontSize: 16, // Picker font sizefontColor: '#000', // Picker font colordividerColor: getDividerColor, // IOS Reserved, not currently supportedunitFontSize: 16, // Picker unit sizeunitFontColor: '#000', // Picker unit color},/*** Button variable*/button: {margin: [0, 0, 0, 0], // Button container margin (up/right/down/left)fontSize: 10, // font sizefontColor: getTypedFontColor, // font coloriconSize: 24, // Icon sizeiconColor: props => getTypedFontColor(props, props.type === 'primary'), // Icon 颜色bgWidth: null, // Button background width, internally adaptivebgHeight: null, // Button background height, internally adaptivebgRadius: null, // Button background rounded corners, internally adaptivebgColor: getBrandColor, // Button background color, follow the main color by default},/*** TopBar variable*/topbar: {background: '#fff', // TopBar backgroundcolor: '#000', // TopBar font color(include icon color)},/*** SwitchButton variable*/switchButton: {width: 50, // Button widthheight: Platform.select({// button heightweb: 28,ios: 28,android: 14,}),thumbSize: 26, // thumb height and width sizemargin: Platform.select({// thumb marginweb: 1,ios: 1,android: 0,}),tintColor: '#e5e5e5', // Background color when closedonTintColor: '#4CD964', // Background color when openedthumbTintColor: '#fff', // Thumb background color when closedonThumbTintColor: '#fff', // Thumb background color when opened},/*** Slider variable*/slider: {width: null, // Follow the parent container by default (slider width)trackRadius: 2, // Slider border radiustrackHeight: 4, // Slider heightminimumTrackTintColor: getBrandColor, // color for minimum textmaximumTrackTintColor: '#e5e5e5', // color for maximum textthumbSize: 24,thumbRadius: 14,thumbTintColor: '#fff', // thumb color},/*** Checkbox variable*/checkbox: {size: 28, // Checkbox sizefontColor: '#333', // Checkbox font coloractiveColor: '#3388FF', // Color when Checkbox is activateddisabledColor: '#333', // Color when Checkbox is disabled},/*** List variable*/list: {boardBg: '#f8f8f8', // The container background color of the listiconColor: 'rgba(51, 51, 51, 0.5)', // Icon text colorfontColor: '#333', // title colorsubFontColor: 'rgba(51, 51, 51, 0.5)', // subtitle text colordescFontColor: 'rgba(51, 51, 51, 0.5)', // description text colorcellLine: 'rgba(51, 51, 51, 0.1)', // divider line colorcellBg: '#fff', // cell background colorcellRadius: 0, // cell border radiusmargin: [0, 0, 0, 0], // cell marginpadding: [12, cx(16), 12, cx(16)], // cell padding},/*** BrickButton variable*/brickButton: {fontSize: 12,fontColor: '#fff',bgRadius: 24, // background border radiusbgColor: getBrandColor,bgBorder: 'transparent', // background borderbgBorderWidth: 0, // background border widthloadingColor: '#fff', // loading text colorloadingBackground: 'rgba(0,0,0,.1)', // loading text background},/*** Dialog variable*/dialog: {width: cx(315), // dialog container widthbg: '#fff', // dialog backgroundradius: cx(8), // dialog container border radiuscellHeight: 56, // cell height(header/footer)lineColor: '#e5e5e5', // divider line colortitleFontSize: 18, // title font sizetitleFontColor: '#333', // title font colorsubTitleFontSize: 16, // subtitle font sizesubTitleFontColor: '#999', // subtitle font colorcancelFontSize: 16, // footer cancel font sizecancelFontColor: '#666', // footer cancel font colorconfirmFontSize: 16, // footer confirm font sizeconfirmFontColor: '#333', // footer confirm font colorprompt: {bg: '#f8f8f8', // prompt backgroundradius: cx(4), // prompt border radiuspadding: '12px 16px', // prompt paddingplaceholder: '#d6d6de', // placeholder text},},/*** Popup variable*/popup: {cellHeight: 48, // cell heightcellBg: '#fff', // cell backgroundtitleRadius: cx(8), // header border radiusfooterRadius: 0, // footer border radiusbottomBg: '#f5f5f5', // footer backgroundlineColor: '#e5e5e5', // divider line colortitleFontSize: 14, // header title font sizetitleFontColor: '#999', // header title font colorcancelFontSize: 16, // footer cancel text font sizecancelFontColor: '#666', // footer cancel text font colorconfirmFontSize: 16, // footer confirm text font sizeconfirmFontColor: '#333', // footer confirm text font color},};