Skip to content

Commit 9cbfd35

Browse files
committed
feat(ui-avatar,emotion): add theming solution to functional components
1 parent 1c22bfc commit 9cbfd35

File tree

11 files changed

+612
-455
lines changed

11 files changed

+612
-455
lines changed

docs/contributor-docs/adding-icons.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ order: 9
1313

1414
- Double-check that the SVG size is 1920x1920.
1515

16-
```html
16+
```js
1717
---
1818
type: code
1919
---
20-
2120
<svg
2221
width="1920"
2322
height="1920"
@@ -31,7 +30,7 @@ type: code
3130

3231
- The files cannot contain [clipping paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)! Sadly, when the Designers export icons from Figma, most of the time they have a clipping path around the whole canvas. If the source code has them, manually refactor the code, e.g:
3332

34-
```html
33+
```js
3534
---
3635
type: code
3736
---
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: Themed components
3+
category: Contributor Guides
4+
order: 10
5+
---
6+
7+
## Making InstUI-like components with theming
8+
9+
InstUI uses [Emotion](https://emotion.sh/docs/introduction) under the hood to theme and style its components.
10+
If you want to read about the design behind the system and how to build `class-based` components with InstUI, please read [this](https://instructure.design/#emotion).
11+
12+
This page will show you how to build `functional` react components with InstUI.
13+
14+
### Anatomy of a functional InstUI component
15+
16+
To make similar and similarly maintainable components to InstUI, you should follow a basic structure. This is not strictly necessary but recommended and this guide will assume you do use it.
17+
18+
A fully equipped InstUI component has three files: `index.tsx`, `style.ts`, `theme.ts` and uses the `useStyles` hook.
19+
20+
Let's take a look at the simplest example possible:
21+
22+
```js
23+
---
24+
type: code
25+
---
26+
// index.tsx
27+
28+
// /** @jsx jsx */
29+
import { jsx, useStyle } from '@instructure/emotion'
30+
import generateStyle from './styles'
31+
import generateComponentTheme from './theme'
32+
const InstUIComponent = (props: PropsType)=> {
33+
const styles = useStyle({
34+
generateStyle,
35+
generateComponentTheme,
36+
componentId: "InstUIComponent_id",
37+
//any unique id
38+
params: {
39+
color: props.color,
40+
variant: props.variant,
41+
themeOverride: props.themeOverride
42+
}
43+
}
44+
)
45+
return (
46+
<div css={ styles?.root }>content</div>
47+
)
48+
}
49+
50+
export default InstUIComponent
51+
```
52+
53+
```js
54+
---
55+
type: code
56+
---
57+
// style.ts
58+
59+
const generateStyle = (
60+
componentTheme: componentThemeType,
61+
params:ParamType): AvatarStyle => {
62+
const { color, variant } = params // assuming you passed the `color` and `variant` to the useStyle hook
63+
const variantStyles = {
64+
circle: {
65+
width: '2.5em',
66+
position: 'relative',
67+
borderRadius: '100%',
68+
overflow: 'hidden' },
69+
rectangle: { width: '3em' } }
70+
const colorVariants = {
71+
default: componentTheme.defaultColor,
72+
green: componentTheme.niceGreenColor,
73+
nonThemedColor: "pink"
74+
}
75+
return {
76+
instUIComponent: { //for the root element's
77+
style label: 'instUIComponent',
78+
color: colorVariants[color],
79+
backgroundColor: componentTheme.bgColor,
80+
...variantStyles[variant]
81+
},
82+
aChildElement: {
83+
label: 'instUIComponent_aChildElement', // this label is needed. Please prefix it with the root label
84+
fontWeight: "400" //you can hardcode values. Don't need to get them from the theme necessarily .
85+
}
86+
}
87+
}
88+
export default generateStyle
89+
```
90+
91+
```js
92+
---
93+
type: code
94+
---
95+
// theme.ts
96+
97+
import type { Theme } from '@instructure/ui-themes'
98+
const generateComponentTheme = (theme: Theme) => {
99+
const { colors } = theme // the theme you are using. See instUI's theme docs as well
100+
const componentVariables ={
101+
defaultColor: colors?.contrasts?.white1010,
102+
niceGreenColor: colors.contrasts.green4570,
103+
bgColor: "purple" //this is hardcoded, but added to the theme, so it can be overridden
104+
}
105+
return { ...componentVariables }
106+
}
107+
export default generateComponentTheme
108+
```
109+
110+
Let's take a look at the key parts of the examples:
111+
112+
The `useStyle` hook calculates the styles for the component. It needs an object with:
113+
114+
- `generateStyle` function, this function contains all the `css` information (`style.ts` file in the example).
115+
- `generateComponentTheme` is an optional param. This provides variables that act as the theme of the components. These can be derived from the global theme object or hardcoded. All can be overridden.
116+
- `componentId` depends on `generateComponentTheme`. It's mandatory if `generateComponentTheme` is provided. It must be a unique string to identify the component by and used for [component level overrides](https://instructure.design/#using-theme-overrides/#Overriding%20theme%20for%20a%20specific%20component%20in%20a%20subtree).
117+
- `params` is an optional object with any data you need to pass to `generateStyle`. To enable themeOverrides on the component, you must pass the `themeOverride` prop to `params`.
118+
119+
`useStyle` returns an object with the css classes. Pass it to the DOM through emotion's `css` prop (see example).
120+
121+
#### The `generateComponentTheme`
122+
123+
The `generateComponentTheme` defines, calculates and exposes variables that are considered `component theme variables`. These variables will be used in the `generateStyle` method to "theme" the component's style. These variables are overwritable by the [various override methods](https://instructure.design/#using-theme-overrides).
124+
`generateComponentTheme` gets the `theme` as parameter. Return an object (`componentVariables`) with keys that will act as the `component theme variables`. This method will be injected to `generateStyle`.
125+
126+
#### The `generateStyle`
127+
128+
You define the css in the `generateStyle` method. It has access to the themes, defined in the `generateComponentTheme` (in the example: `componentTheme`) and the `params` which are passed to the `useStyle` hook.
129+
Note: if you set the `label` to a unique value for every css class, it makes testing and debugging much easier because emotion appends to the end of the hashed class name it generates.

packages/emotion/src/EmotionTypes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ type GenerateStyle = (
116116
state?: State
117117
) => StyleObject
118118

119+
type GenerateStyleFunctional = (
120+
componentTheme: ComponentTheme,
121+
params: Record<string, unknown>
122+
) => StyleObject
123+
119124
type ComponentStyle<Keys extends string = string> = Record<
120125
Keys,
121126
StyleObject | string | number | undefined
@@ -139,6 +144,7 @@ export type {
139144
State,
140145
GenerateComponentTheme,
141146
GenerateStyle,
147+
GenerateStyleFunctional,
142148
ComponentStyle
143149
}
144150
/*

0 commit comments

Comments
 (0)