1 | ---
|
2 | category: packages
|
3 | ---
|
4 |
|
5 | ## ui-themeable
|
6 |
|
7 |
|
8 | [![npm][npm]][npm-url]
|
9 | [![build-status][build-status]][build-status-url]
|
10 | [![MIT License][license-badge]][LICENSE]
|
11 | [![Code of Conduct][coc-badge]][coc]
|
12 |
|
13 |
|
14 | The [ui-themeable](#ui-themeable) library is meant to be used along with a [babel plugin](#babel-plugin-themeable-styles)
|
15 | to import CSS styles and generate theme variables. With this framework, each UI component can be used in
|
16 | isolation and support multiple themes, including dynamic themes provided at runtime, while still working within
|
17 | a system of components that use a [shared global theme](#canvas).
|
18 |
|
19 | ### Motivation
|
20 |
|
21 | 1. Two-tiered theme variable system: system-wide variables + component level variables. With this variable system, components can be themed, tested, and rendered in isolation from the rest of the system, and we can mitigate issues that may arise with system-wide theme updates.
|
22 |
|
23 | 2. Runtime theme application and definition: to apply user/account level themes *without using the CSS cascade*.
|
24 |
|
25 | 3. Prevent CSS Cascade bugs: All components should specify variants via props or component level theme variables only (no className or style overrides) with a clear API and should not rely on any external styles.
|
26 |
|
27 | 4. Theme variables should be accessible in both JS and CSS.
|
28 |
|
29 | 5. All component styles and variables should scoped to the component.
|
30 |
|
31 | 6. Pre-render/server-side render support (inline critical CSS).
|
32 |
|
33 |
|
34 | ### Installation
|
35 |
|
36 | ```sh
|
37 | yarn add @instructure/ui-themeable
|
38 | ```
|
39 |
|
40 | ### Usage
|
41 |
|
42 | Make a UI component [themeable](#themeable):
|
43 |
|
44 | ```js
|
45 | // Button/index.js
|
46 | import themeable from '@instructure/ui-themeable'
|
47 |
|
48 | import styles from 'styles.css'
|
49 | import theme from 'theme.js'
|
50 |
|
51 | class Button extends React.Component {
|
52 | render () {
|
53 | return <button className={styles.root}>{this.props.children}</button>
|
54 | }
|
55 | }
|
56 | export default themeable(theme, styles)(Example)
|
57 | ```
|
58 |
|
59 | Themeable components inject their themed styles into the document when they are mounted.
|
60 |
|
61 | After the initial mount, a themeable component's theme can be configured explicitly
|
62 | via its `theme` prop or passed via React context using the [ApplyTheme](#ApplyTheme) component.
|
63 |
|
64 | Themeable components register themselves with the [global theme registry](#registry)
|
65 | when they are imported into the application, so you will need to be sure to import them
|
66 | before you mount your application so that the default themed styles can be generated and injected.
|
67 |
|
68 | ### Defining variables
|
69 |
|
70 | The themeable component transforms the JS variables defined in the `theme.js` file into CSS custom properties
|
71 | that are automatically scoped and applied to the component.
|
72 |
|
73 | For example, to add a variable for the `hover` state of a `Button` component,
|
74 | the `theme.js` file might contain the following:
|
75 |
|
76 | ```js
|
77 | // Button/theme.js
|
78 | export default function generator ({ colors }) {
|
79 | return (
|
80 | background: colors.backgroundMedium,
|
81 | color: colors.textDarkest,
|
82 |
|
83 | hoverColor: colors.textLightest,
|
84 | hoverBackground: colors.backgroundDarkest
|
85 | )
|
86 | }
|
87 | ```
|
88 |
|
89 | The arguments to the generator function are the [global theme variables](#canvas). In the above example, we've defined
|
90 | the default theme for the Button component.
|
91 |
|
92 | The purpose of the generator function is to take the global variables and apply them as values to the functional
|
93 | component level variables. When coming up with names for the component level variables, try to make them describe
|
94 | how they are used in the component (vs describing the variable value).
|
95 |
|
96 | ### Supporting multiple themes
|
97 |
|
98 | If we want to make the Button transform the global theme variables differently with a another theme,
|
99 | (e.g. [canvas-high-contrast](#canvas-high-contrast)) we can make a generator for that theme:
|
100 |
|
101 | ```js
|
102 | // Button/theme.js
|
103 | ...
|
104 | generator['canvas-high-contrast'] = function ({ colors }) {
|
105 | return {
|
106 | background: colors.backgroundLightest
|
107 | }
|
108 | }
|
109 | ```
|
110 |
|
111 | This will override the default Button theme and use the global theme variable `colors.textLightest` for the
|
112 | value of its `background` theme variable instead of `colors.tiara`.
|
113 |
|
114 | The rest of the variables will pick up from the default Button theme generator (applying the global theme variables
|
115 | from the `canvas-high-contrast` theme).
|
116 |
|
117 | ### Using theme variables in CSS
|
118 |
|
119 | Note: Don't worry about scoping your CSS variables (the [ui-themeable](#ui-themeable) library will take care of that for you):
|
120 |
|
121 | ```css
|
122 | .root {
|
123 | background: var(--background);
|
124 | color: var(--color);
|
125 |
|
126 | &:hover {
|
127 | background: var(--hoverBackground);
|
128 | color: var(--hoverColor);
|
129 | }
|
130 | }
|
131 | ```
|
132 |
|
133 | ### Using theme variables in JavaScript
|
134 |
|
135 | Since the variables are defined in JS you can also access them in your component JS (e.g. `this.theme.hoverColor`) which will give
|
136 | you the theme values applied via React context with `ApplyTheme` or the `theme` prop (falling back to the defaults provided in the `theme.js` file).
|
137 |
|
138 |
|
139 | ### How it works
|
140 |
|
141 | The [babel plugin](#babel-plugin-themeable-styles) does a few things:
|
142 |
|
143 | 1. It uses the [css-modules-require-hook](https://github.com/css-modules/css-modules-require-hook)
|
144 | to namespace the class names (configurable via themeable.config.js).
|
145 | 2. It runs [postcss](https://github.com/postcss/postcss) on the contents of the `theme.css` file using plugins defined in postcss.config.js, plus [postcss-themeable-styles](#postcss-themeable-styles).
|
146 | 3. It converts the processed CSS string to a function that provides a JS template
|
147 | so that variable values from `theme.js` can be injected into the CSS
|
148 | for browsers that don't support CSS variables.
|
149 |
|
150 | The [ui-themeable](#ui-themeable) library will call the theme function and inject the resulting CSS string into the document
|
151 | when the component mounts. If the browser supports CSS variables, it will
|
152 | inject namespaced CSS variables into the CSS before adding it to the document.
|
153 |
|
154 | e.g. The following is injected into the document for browsers with CSS var support:
|
155 |
|
156 | ```css
|
157 | .list__root {
|
158 | color: var(--list__color);
|
159 | background: var(--list__background);
|
160 | }
|
161 |
|
162 | :root {
|
163 | --list__color: #8893A2;
|
164 | --list__background: #FFFFFF;
|
165 | }
|
166 | ```
|
167 |
|
168 | Whereas if the browser does not support CSS variables:
|
169 |
|
170 | ```css
|
171 | .list__root {
|
172 | color: #8893A2;
|
173 | background: #FFFFFF;
|
174 | }
|
175 | ```
|
176 |
|
177 | The [ui-themeable](#ui-themeable) library also supports runtime themes as follows:
|
178 |
|
179 | For browsers that support CSS variables, it will add variables via the
|
180 | style attribute on the component root (when the theme is changed, either
|
181 | via the theme property or via React context using the [ApplyTheme](#ApplyTheme) component).
|
182 |
|
183 | ```html
|
184 | <div style="--list-background: red">
|
185 | ```
|
186 |
|
187 | For browsers that don't support CSS variables it will update the DOM like:
|
188 |
|
189 | ```html
|
190 | <div data-theme="XYZ">
|
191 | <style type="text/css">
|
192 | [data-theme="XYZ"].list__root {
|
193 | background: red;
|
194 | }
|
195 | </style>
|
196 | </div>
|
197 | ```
|
198 |
|
199 |
|
200 | [npm]: https://img.shields.io/npm/v/@instructure/ui-themeable.svg
|
201 | [npm-url]: https://npmjs.com/package/@instructure/ui-themeable
|
202 |
|
203 | [build-status]: https://travis-ci.org/instructure/instructure-ui.svg?branch=master
|
204 | [build-status-url]: https://travis-ci.org/instructure/instructure-ui "Travis CI"
|
205 |
|
206 | [license-badge]: https://img.shields.io/npm/l/instructure-ui.svg?style=flat-square
|
207 | [license]: https://github.com/instructure/instructure-ui/blob/master/LICENSE
|
208 |
|
209 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
|
210 | [coc]: https://github.com/instructure/instructure-ui/blob/master/CODE_OF_CONDUCT.md
|
211 |
|
\ | No newline at end of file |