@instructure/emotion 8.18.1-snapshot.3 → 8.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,35 +23,45 @@
23
23
  */
24
24
 
25
25
  import React, { useContext } from 'react'
26
- import { TextDirectionContext } from '@instructure/ui-i18n'
26
+ import PropTypes from 'prop-types'
27
27
  import { ThemeProvider } from '@emotion/react'
28
- import { ThemeOrOverride } from '../EmotionTypes'
29
- import { getTheme } from '../EmotionThemeProvider'
28
+
29
+ import { TextDirectionContext } from '@instructure/ui-i18n'
30
30
  import { DeterministicIdContextProvider } from '@instructure/ui-react-utils'
31
+
32
+ import { getTheme } from '../EmotionThemeProvider'
33
+
34
+ import type { ThemeOrOverride } from '../EmotionTypes'
31
35
  import type { DeterministicIdProviderValue } from '@instructure/ui-react-utils'
32
36
 
33
37
  type InstUIProviderProps = {
38
+ children?: React.ReactNode
39
+
40
+ /**
41
+ * A full theme or an override object
42
+ */
34
43
  theme?: ThemeOrOverride
35
- dir?: 'ltr' | 'rtl' // TODO allow "auto" too
44
+
45
+ /**
46
+ * The text direction to use in the descendants. If not provided, it uses the following in this priority order:
47
+ * - The value given in a parent `TextDirectionContext`
48
+ * - The `dir` prop of `document.documentElement` or its `direction` CSS prop
49
+ * - The default `ltr`
50
+ */
51
+ dir?: 'ltr' | 'rtl'
52
+
53
+ /**
54
+ * A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which keeps track of specific InstUI components. (generally this is used for deterministic id generation for [SSR](/#server-side-rendering))
55
+ */
36
56
  instanceCounterMap?: DeterministicIdProviderValue
37
57
  }
38
58
 
39
- /**
40
- * @typedef InstUISettingsProviderSettings
41
- * @type {object}
42
- * @property {string} theme - A full theme or an override object
43
- * @property {string} dir - The text direction to use in the descendants. If not
44
- * given it uses the following in this priority order:
45
- * - The value given in a parent `TextDirectionContext`
46
- * - The `dir` prop of `document.documentElement` or its `direction` CSS prop
47
- * - `ltr`
48
- * @property {Map<string, number>} instanceCounterMap - a Map to keep track of instances
49
- */
50
-
51
59
  /**
52
60
  * ---
53
61
  * category: components/utilities
54
62
  * ---
63
+ * @module InstUISettingsProvider
64
+ * @tsProps
55
65
  *
56
66
  * Wrapper for emotion js's [ThemeProvider](https://emotion.sh/docs/theming#themeprovider-reactcomponenttype).
57
67
  *
@@ -101,24 +111,19 @@ type InstUIProviderProps = {
101
111
  * //read our [SSR](/#server-side-rendering) guide
102
112
  * const counter = generateInstanceCounterMap()
103
113
  * counter.set("Alert", 5)
104
- * <InstUISettingsProvider instanceCounterMap={counter}
114
+ * <InstUISettingsProvider instanceCounterMap={counter}>
105
115
  * //this Alert's rendered DOM Node will have [id="Alert_5"] on it
106
116
  * <Alert>Test!</Alert>
107
117
  * </InstUISettingsProvider>
108
118
  * </InstUISettingsProvider>
109
119
  * ```
110
- *
111
- * @module InstUISettingsProvider
112
- *
113
- * @param {InstUISettingsProviderSettings} settings - A settings object
114
- * @returns {ReactElement} The settings provider
115
120
  */
116
121
  function InstUISettingsProvider({
117
122
  children,
118
123
  theme = {},
119
124
  dir,
120
125
  instanceCounterMap
121
- }: React.PropsWithChildren<InstUIProviderProps>) {
126
+ }: InstUIProviderProps) {
122
127
  const finalDir = dir || useContext(TextDirectionContext)
123
128
 
124
129
  if (process.env.NODE_ENV !== 'production' && finalDir === 'auto') {
@@ -138,10 +143,18 @@ function InstUISettingsProvider({
138
143
  )
139
144
  }
140
145
 
146
+ InstUISettingsProvider.propTypes = {
147
+ /* eslint-disable react/require-default-props */
148
+ children: PropTypes.node,
149
+ theme: PropTypes.object,
150
+ dir: PropTypes.oneOf(['ltr', 'rtl']),
151
+ instanceCounterMap: PropTypes.instanceOf(Map)
152
+ /* eslint-enable react/require-default-props */
153
+ }
154
+
141
155
  InstUISettingsProvider.defaultProps = {
142
- theme: {},
143
- dir: undefined,
144
- instanceCounterMap: undefined
156
+ theme: {}
145
157
  }
158
+
146
159
  export default InstUISettingsProvider
147
160
  export { InstUISettingsProvider }
@@ -27,7 +27,7 @@ import type {
27
27
  Overrides,
28
28
  ComponentOverride
29
29
  } from './EmotionTypes'
30
- import type { ComponentTheme } from '@instructure/shared-types'
30
+ import type { BaseTheme, ComponentTheme } from '@instructure/shared-types'
31
31
  import type { WithStyleProps } from './withStyle'
32
32
 
33
33
  type ComponentName = keyof ComponentOverride | undefined
@@ -43,26 +43,46 @@ type ComponentName = keyof ComponentOverride | undefined
43
43
  * @param {*} displayName - Name of the component
44
44
  * @param {*} componentId - componentId of the component
45
45
  * @param {*} props - The component's props object
46
+ * @param {*} componentTheme - The component's default theme
46
47
  * @returns {object} The calculated theme override object
47
48
  */
48
49
  const getComponentThemeOverride = (
49
50
  theme: ThemeOrOverride,
50
51
  displayName: string,
51
52
  componentId?: string,
52
- props?: { [k: string]: unknown } & WithStyleProps
53
- ): ComponentTheme => {
54
- let componentOverride: ComponentTheme = {}
55
- const overrides = (theme as Overrides).componentOverrides
53
+ props?: { [k: string]: unknown } & WithStyleProps,
54
+ componentTheme?: ComponentTheme
55
+ ): Partial<ComponentTheme> => {
56
56
  const name = displayName as ComponentName
57
57
  const id = componentId as ComponentName
58
58
 
59
- if (overrides) {
60
- componentOverride = (name && overrides[name]) || (id && overrides[id]) || {}
59
+ const { themeOverride } = props as WithStyleProps
60
+ const { componentOverrides } = theme as Overrides
61
+
62
+ let overridesFromTheme: Partial<ComponentTheme> = {}
63
+ let overrideFromComponent: Partial<ComponentTheme> = {}
64
+
65
+ if (componentOverrides) {
66
+ overridesFromTheme =
67
+ (name && componentOverrides[name]) || (id && componentOverrides[id]) || {}
68
+ }
69
+
70
+ if (themeOverride) {
71
+ if (typeof themeOverride === 'function') {
72
+ overrideFromComponent = themeOverride(
73
+ componentTheme || {},
74
+ // the `theme` technically could be a partial theme / override object too,
75
+ // but we want to display all possible options
76
+ theme as BaseTheme
77
+ )
78
+ } else {
79
+ overrideFromComponent = themeOverride
80
+ }
61
81
  }
62
82
 
63
83
  return {
64
- ...componentOverride,
65
- ...(props?.themeOverride ?? {})
84
+ ...overridesFromTheme,
85
+ ...overrideFromComponent
66
86
  }
67
87
  }
68
88
 
package/src/withStyle.tsx CHANGED
@@ -68,7 +68,9 @@ type WithStylePrivateProps<
68
68
  }
69
69
 
70
70
  type ThemeOverrideProp<Theme extends ComponentTheme | null = ComponentTheme> = {
71
- themeOverride?: Partial<Theme>
71
+ themeOverride?:
72
+ | Partial<Theme>
73
+ | ((componentTheme: Theme, currentTheme: BaseTheme) => Partial<Theme>)
72
74
  }
73
75
 
74
76
  type WithStyleProps<
@@ -90,7 +92,7 @@ const defaultValues = {
90
92
  *
91
93
  * A decorator or higher order component that makes a component themeable.
92
94
  *
93
- * It adds a `makeStyles` function and the generated `styles` object to the decorated Component's props.
95
+ * It adds a `makeStyles` function and the generated `styles` object to the decorated Component's props. If it has an own theme, it also adds the `themeOverride` prop to the component.
94
96
  *
95
97
  * As a HOC:
96
98
  *
@@ -99,7 +101,6 @@ const defaultValues = {
99
101
  * import generateStyle from './styles'
100
102
  * import generateComponentTheme from './theme'
101
103
  *
102
- *
103
104
  * export default withStyle(generateStyle, generateComponentTheme)(ExampleComponent)
104
105
  * ```
105
106
  *
@@ -112,10 +113,12 @@ const defaultValues = {
112
113
  * [InstUISettingsProvider](#InstUISettingsProvider) component, and/or set
113
114
  * explicitly via its `themeOverride` prop.
114
115
  *
115
- * InstUISettingsProvider provides a theme object with [global theme variables](#canvas).
116
+ * InstUISettingsProvider provides a theme object with global theme variables (e.g. the [canvas theme](/#canvas)).
116
117
  * These variables are mapped to the component's own variables in `theme.js` (see [@instructure/emotion](#emotion) package documentation for more info).
117
118
  *
118
- * With the `themeOverride` prop you can directly set/override the component theme variables declared in theme.js.
119
+ * With the `themeOverride` prop you can directly set/override the component theme variables declared in theme.js. It accepts an object or a function. The function has the component's theme and the currently active main theme as its parameter.
120
+ *
121
+ * See more about the overrides on the [Using theme overrides](/#using-theme-overrides) docs page.
119
122
  *
120
123
  * ```js
121
124
  * // ExampleComponent/theme.js
@@ -142,6 +145,12 @@ const defaultValues = {
142
145
  * }}>
143
146
  * {// component theme override}
144
147
  * <ExampleComponent themeOverride={{ hoverColor: '#eee' }} />
148
+ *
149
+ * {// component theme override with function}
150
+ * <ExampleComponent themeOverride={(componentTheme, currentTheme) => ({
151
+ * hoverBackground: componentTheme.background,
152
+ * activeBackground: currentTheme.colors.backgroundBrand
153
+ * })} />
145
154
  * </InstUISettingsProvider>
146
155
  * ```
147
156
  *
@@ -181,26 +190,27 @@ const withStyle = decorator(
181
190
  `Manually passing the "makeStyles" property is not allowed on the ${displayName} component. Styles are calculated by the @withStyle decorator.`
182
191
  )
183
192
  }
193
+
184
194
  const componentProps: Props = {
185
195
  ...ComposedComponent.defaultProps,
186
196
  ...props,
187
197
  ...defaultValues
188
198
  }
189
199
 
200
+ let componentTheme: ComponentTheme =
201
+ typeof generateComponentTheme === 'function'
202
+ ? generateComponentTheme(theme as BaseTheme)
203
+ : {}
204
+
190
205
  const themeOverride = getComponentThemeOverride(
191
206
  theme,
192
207
  displayName,
193
208
  ComposedComponent.componentId,
194
- componentProps
209
+ componentProps,
210
+ componentTheme
195
211
  )
196
212
 
197
- const componentTheme: ComponentTheme =
198
- typeof generateComponentTheme === 'function'
199
- ? {
200
- ...generateComponentTheme(theme as BaseTheme),
201
- ...themeOverride
202
- }
203
- : {}
213
+ componentTheme = { ...componentTheme, ...themeOverride }
204
214
 
205
215
  const [styles, setStyles] = useState(
206
216
  generateStyle ? generateStyle(componentTheme, componentProps, {}) : {}