Skip to content

Commit f02f151

Browse files
HerrTopimatyasf
authored andcommitted
feat(ui-avatar,emotion): add theming solution to functional components
1 parent 9c257f4 commit f02f151

File tree

9 files changed

+268
-452
lines changed

9 files changed

+268
-452
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 to 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+
```html
23+
---
24+
type: code
25+
---
26+
27+
// index.tsx /** @jsx jsx */ import { jsx, useStyle } from
28+
'@instructure/emotion' import generateStyle from './styles' import
29+
generateComponentTheme from './theme' const InstUIComponent = (props:PropsType)
30+
=> { const styles = useStyle({ generateStyle, generateComponentTheme, params:
31+
{color:props.color, variant:props.variant}, componentId: "InstUIComponent_id" //
32+
any unique id }) return (
33+
<div css="{styles?.root}">content</div>
34+
) } export default InstUIComponent
35+
```
36+
37+
```html
38+
---
39+
type: code
40+
---
41+
42+
// style.ts const generateStyle = ( componentTheme: componentThemeType, params:
43+
ParamType ): AvatarStyle => { const { color, variant } = params // assuming you
44+
passed the `color` and `variant` to the useStyle hook const variantStyles = {
45+
circle: { width: '2.5em', position: 'relative', borderRadius: '100%', overflow:
46+
'hidden' }, rectangle: { width: '3em' } } const colorVariants = { default:
47+
componentTheme.defaultColor, green: componentTheme.niceGreenColor,
48+
nonThemedColor: "pink" } return { instUIComponent: { //for the root element's
49+
style label: 'instUIComponent', color: colorVariants[color], backgroundColor:
50+
componentTheme.bgColor, ...variantStyles[variant], } aChildElement: { label:
51+
'instUIComponent_aChildElement', // this label is needed. Please prefix it with
52+
the root label fontWeight: "400" //you can hardcode values. Don't need to get
53+
them from the team necessarily . } } export default generateStyle
54+
```
55+
56+
```html
57+
---
58+
type: code
59+
---
60+
61+
// theme.ts import type { Theme } from '@instructure/ui-themes' const
62+
generateComponentTheme = (theme: Theme) => { const { colors } = theme // the
63+
theme you are using. See instUI's theme docs as well const componentVariables =
64+
{ defaultColor: colors?.contrasts?.white1010, niceGreenColor:
65+
colors.contrasts.green4570, bgColor:"purple" //this is hardcoded, but added to
66+
the theme, so it can be overridden } return { ...componentVariables } } export
67+
default generateComponentTheme
68+
```
69+
70+
Let's take a look at the key parts of the above example:
71+
72+
The `useStyle` hook calculates the styles for the component. It needs an object with:
73+
74+
- `generateStyle` function, this function contains all the `css` information (`style.ts` file in the example).
75+
- `componentId`. This must be a unique string to identify the component by. It is used for [component level overrides](https://instructure.design/#using-theme-overrides/#Overriding%20theme%20for%20a%20specific%20component%20in%20a%20subtree)
76+
- `generateComponentTheme` is an optional param. This provides themed

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
/*

packages/emotion/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export {
3737
mapSpacingToShorthand
3838
} from './styleUtils'
3939

40+
export { useStyle } from './useStyle'
41+
4042
export type { ComponentStyle, StyleObject, Overrides } from './EmotionTypes'
4143
export type { WithStyleProps } from './withStyle'
4244
export type {

packages/emotion/src/useStyle.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import { useTheme } from './useTheme'
26+
import { getComponentThemeOverride } from './getComponentThemeOverride'
27+
import type { BaseTheme, ComponentTheme } from '@instructure/shared-types'
28+
29+
// returns the second parameter of a function
30+
type SecondParameter<T extends (...args: any) => any> =
31+
Parameters<T>[1] extends undefined ? never : Parameters<T>[1]
32+
33+
const useStyle = <P extends (theme: any, params: any) => any>(params: {
34+
generateStyle: P
35+
params?: SecondParameter<P>
36+
generateComponentTheme: (theme: BaseTheme) => ComponentTheme
37+
componentId: string
38+
displayName?: string
39+
}): ReturnType<P> => {
40+
const { generateStyle, generateComponentTheme, componentId, displayName } =
41+
params
42+
const theme = useTheme()
43+
const baseComponentTheme = generateComponentTheme
44+
? generateComponentTheme(theme as BaseTheme)
45+
: {}
46+
47+
const themeOverride = getComponentThemeOverride(
48+
theme,
49+
displayName ? displayName : componentId || '',
50+
componentId,
51+
params,
52+
baseComponentTheme
53+
)
54+
55+
const componentTheme = { ...baseComponentTheme, ...themeOverride }
56+
57+
return generateStyle(componentTheme, params ? params : {})
58+
}
59+
60+
export default useStyle
61+
export { useStyle }

0 commit comments

Comments
 (0)