主题
特性
- 统一管理全局样式
- 便于样式复用及拓展
- 减少过多样式 props 传递
自定义主题
你可以在本地主题配置文件中(src/config/theme.js)覆盖所有主题配置(所有主题变量可见文档最下方主题速查表),当然也可以自行在项目中拓展主题配置。
export default {// 覆盖本地默认主题变量global: {brand: '#ff0000',},switchButton: {margin: 2,width: 40,height: 24,cthumbSize: 20,},// 拓展主题配置myExtendTheme: {customKey: 'blue',},};
API
1. 注入全局主题
添加 Theme(ThemeProvider) 到应用程序的顶层,将主题传递到 React 组件树。 然后,我们就可以通过后面三种方式去访问主题对象。
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. 获取全局主题
- styled: 通过被 styled-components 中 styled 包装过的组件访问主题
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: 通过被 withTheme 高阶函数包装过的组件访问主题
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: 通过 ThemeConsumer 组件接收主题
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>);};
主题速查表
import { Platform } from 'react-native';import { CoreUtils, RatioUtils } from '../../utils';const { get } = CoreUtils;const { convertX: cx } = RatioUtils;/*** 通用辅助函数*/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]);};// 根据全局的字体基准比例调整字体大小const normalizeFont = (props, fontSize, lineHeight) => {const baseline = get(props, 'theme.global.fontSizeBase', global.fontSizeBase);return {fontSize: fontSize * baseline,lineHeight: Math.round(lineHeight * baseline), // 不为整数小米会crash};};export default {type: 'light',/*** 全局基础变量*/global: {brand: '#FF4800', // 品牌色(主题色)bgColor: '#f8f8f8', // 背景色fontSizeBase: 1, // 字体基准比例dividerColor: '#e5e5e5', // 分隔线颜色success: '#00C800', // 成功颜色warning: '#FAAE17', // 警告颜色error: '#F4182C', // 失败// info, // 信息色(暂未开放使用)// disabled, // 禁用透明度(暂未开放使用)mask: 'rgba(0, 0, 0, 0.7)', // 遮罩颜色text: {light: '#333', // 字体在 light 下的颜色dark: '#fff', // 字体在 dark 下的颜色},},/*** 字体大小变量*/text: {heading: {// type 为 heading,size 为 small 对应的字体大小small: props => normalizeFont(props, 28, 40),normal: props => normalizeFont(props, 40, 56),large: props => normalizeFont(props, 72, 100),},title: {// type 为 title,size 为 small 对应的字体大小small: props => normalizeFont(props, 16, 22),normal: props => normalizeFont(props, 17, 24),large: props => normalizeFont(props, 20, 28),},// title以上都走主要字体色#333paragraph: {// type 为 paragraph,size 为 small 对应的字体大小small: props => normalizeFont(props, 10, 14),normal: props => normalizeFont(props, 12, 17),large: props => normalizeFont(props, 14, 20),},},/*** Picker 滚动选择器变量*/picker: {fontSize: 16, // Picker 字体大小fontColor: '#000', // Picker 字体颜色dividerColor: getDividerColor, // 预留 IOS 暂不支持unitFontSize: 16, // Picker 单位大小unitFontColor: '#000', // Picker 单位颜色},/*** Button 按钮变量*/button: {margin: [0, 0, 0, 0], // 按钮容器边距(上右下左)fontSize: 10, // 字体尺寸fontColor: getTypedFontColor, // 字体颜色iconSize: 24, // Icon 大小iconColor: props => getTypedFontColor(props, props.type === 'primary'), // Icon 颜色bgWidth: null, // 按钮背景宽度,默认组件内部自适应bgHeight: null, // 按钮背景高度,默认组件内部自适应bgRadius: null, // 按钮背景圆角,默认组件内部自适应bgColor: getBrandColor, // 按钮背景色,默认跟随主色},/*** TopBar 头部栏变量*/topbar: {background: '#fff', // 头部栏背景色color: '#000', // 头部栏字体颜色(包括图标色)},/*** SwitchButton 开关变量*/switchButton: {width: 50, // 按钮宽度height: Platform.select({// 按钮高度web: 28,ios: 28,android: 14,}),thumbSize: 26, // 滑块宽高尺寸margin: Platform.select({// 滑块四周边距web: 1,ios: 1,android: 0,}),tintColor: '#e5e5e5', // 关闭情况下背景色onTintColor: '#4CD964', // 开启情况下背景色thumbTintColor: '#fff', // 关闭情况下滑块背景色onThumbTintColor: '#fff', // 开启情况下滑块背景色},/*** Slider 滑块变量*/slider: {width: null, // 默认跟随父容器(滑块宽度)trackRadius: 2, // 滑块圆角trackHeight: 4, // 滑块高度minimumTrackTintColor: getBrandColor, // 最小值颜色maximumTrackTintColor: '#e5e5e5', // 最大值颜色thumbSize: 24, // 滑块圆的尺寸thumbRadius: 14, // 滑块圆的圆角thumbTintColor: '#fff', // 滑块的颜色},/*** Checkbox 选择框变量*/checkbox: {size: 28, // Checkbox 尺寸fontColor: '#333', // Checkbox 字体颜色activeColor: '#3388FF', // Checkbox 激活时的颜色disabledColor: '#333', // Checkbox 禁用时的颜色},/*** List 列表项变量*/list: {boardBg: '#f8f8f8', // 列表的容器底色iconColor: 'rgba(51, 51, 51, 0.5)', // 图标颜色fontColor: '#333', // 标题颜色subFontColor: 'rgba(51, 51, 51, 0.5)', // 副标题颜色descFontColor: 'rgba(51, 51, 51, 0.5)', // 描述性标题颜色cellLine: 'rgba(51, 51, 51, 0.1)', // 分隔线颜色cellBg: '#fff', // 列表项背景色cellRadius: 0, // 列表项圆角margin: [0, 0, 0, 0], // 列表项外边距(上右下左)padding: [12, cx(16), 12, cx(16)], // 列表项内边距(上右下左)},/*** BrickButton 块状按钮变量*/brickButton: {fontSize: 12, // 字体大小fontColor: '#fff', // 字体颜色bgRadius: 24, // 背景圆角bgColor: getBrandColor, // 跟随主色bgBorder: 'transparent', // 背景边框bgBorderWidth: 0, // 背景边框宽度loadingColor: '#fff', // 加载颜色loadingBackground: 'rgba(0,0,0,.1)', // 加载的背景颜色},/*** Dialog 对话框变量*/dialog: {width: cx(315), // 弹窗容器宽度bg: '#fff', // 弹窗背景色radius: cx(8), // 弹窗容器圆角cellHeight: 56, // 列表高度(头部、底部)lineColor: '#e5e5e5', // 分隔线颜色titleFontSize: 18, // 标题字体大小titleFontColor: '#333', // 头部栏标题颜色subTitleFontSize: 16, // 副标题字体大小subTitleFontColor: '#999', // 头部栏副标题颜色cancelFontSize: 16, // 底部栏取消字体大小cancelFontColor: '#666', // 底部栏取消字体颜色confirmFontSize: 16, // 底部栏确认字体大小confirmFontColor: '#333', // 底部栏确认字体颜色prompt: {bg: '#f8f8f8', // 输入框背景色radius: cx(4), // 输入框圆角padding: '12px 16px', // 输入框边距placeholder: '#d6d6de', // 占位符字体颜色},},/*** Popup 弹出层变量*/popup: {cellHeight: 48, // 列表项的高度cellBg: '#fff', // 列表底色titleRadius: cx(8), // 头部圆角footerRadius: 0, // 底部圆角bottomBg: '#f5f5f5', // 底部栏底色lineColor: '#e5e5e5', // 分隔线颜色titleFontSize: 14, // 头部栏标题大小titleFontColor: '#999', // 头部栏标题颜色cancelFontSize: 16, // 底部栏取消字体大小cancelFontColor: '#666', // 底部栏取消字体颜色confirmFontSize: 16, // 底部栏确认字体大小confirmFontColor: '#333', // 底部栏确认字体颜色},};