Skip to content

Commit d13b6c1

Browse files
committed
feat(emotion): [InstUISettingsProvider] should be able to access the current theme
INSTUI-4525
1 parent 79c7349 commit d13b6c1

File tree

7 files changed

+114
-29
lines changed

7 files changed

+114
-29
lines changed

docs/guides/using-theme-overrides.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,34 @@ type: code
179179
</InstUISettingsProvider>
180180
```
181181

182+
#### Override function
183+
184+
The `InstUISettingsProvider` accepts a `function`. The override function's first parameter is the currently applied theme object. It should return a valid theme or override object.
185+
186+
```js
187+
---
188+
type: example
189+
---
190+
<div>
191+
<div>
192+
<InstUISettingsProvider theme={
193+
(theme)=>{
194+
return {
195+
componentOverrides: {
196+
Alert:{
197+
infoBorderColor:theme.colors.contrasts.red4570
198+
}
199+
}
200+
}
201+
}
202+
}
203+
>
204+
<Alert>Overridden Alert</Alert>
205+
</InstUISettingsProvider>
206+
</div>
207+
</div>
208+
```
209+
182210
### Overriding theme for a single component
183211

184212
Themeable components (that implement the [withStyle](#withStyle) decorator) accept a `themeOverride` prop. This prop let's you override the component's own theme. It accepts an object or a function.

packages/__docs__/src/Document/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class Document extends Component<DocumentProps, DocumentState> {
7676
const generateTheme = doc?.componentInstance?.generateComponentTheme
7777
const generateThemeFunctional =
7878
functionalComponentThemes[
79-
doc.id as keyof typeof functionalComponentThemes
79+
doc.id as keyof typeof functionalComponentThemes
8080
]
8181
if (
8282
generateTheme &&
@@ -173,9 +173,9 @@ class Document extends Component<DocumentProps, DocumentState> {
173173
{...props}
174174
themeOverride={{
175175
${
176-
// get random theme variable
177-
themeVariableKeys[Math.floor(Math.random() * themeVariableKeys.length)]
178-
}: 'custom value'
176+
// get random theme variable
177+
themeVariableKeys[Math.floor(Math.random() * themeVariableKeys.length)]
178+
}: 'custom value'
179179
}}
180180
/>`}
181181
/>

packages/emotion/src/EmotionTypes.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ type Overrides = {
101101
componentOverrides?: ComponentOverride
102102
}
103103

104-
type ThemeOrOverride = BaseTheme | PartialTheme | Overrides
104+
type BaseThemeOrOverride = BaseTheme | PartialTheme | Overrides
105+
106+
type ThemeOrOverride =
107+
| BaseThemeOrOverride
108+
| ((theme: BaseTheme) => BaseThemeOrOverride)
105109

106110
type Props = Record<string, unknown>
107111
type State = Record<string, unknown>
@@ -134,6 +138,7 @@ export interface StyleObject {
134138
}
135139

136140
export type {
141+
BaseThemeOrOverride,
137142
ThemeOrOverride,
138143
Overrides,
139144
ComponentOverride,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 { render } from '@testing-library/react'
26+
import { vi } from 'vitest'
27+
28+
import '@testing-library/jest-dom'
29+
import { InstUISettingsProvider } from '../index'
30+
import { canvasHighContrast } from '@instructure/ui-themes'
31+
32+
describe('<InstUISettingsProvider />', () => {
33+
it('passes the current theme if a function is passed to theme prop', async () => {
34+
const themeFn = vi.fn()
35+
render(
36+
<InstUISettingsProvider theme={canvasHighContrast}>
37+
<InstUISettingsProvider theme={themeFn}></InstUISettingsProvider>
38+
</InstUISettingsProvider>
39+
)
40+
41+
expect(themeFn).toHaveBeenCalledWith(canvasHighContrast)
42+
})
43+
})

packages/emotion/src/InstUISettingsProvider/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ function InstUISettingsProvider({
122122
InstUISettingsProvider.propTypes = {
123123
/* eslint-disable react/require-default-props */
124124
children: PropTypes.node,
125-
theme: PropTypes.object,
125+
theme: PropTypes.oneOf([PropTypes.object, PropTypes.func]),
126126
dir: PropTypes.oneOf(['ltr', 'rtl']),
127127
instanceCounterMap: PropTypes.instanceOf(Map),
128128
as: PropTypes.string

packages/emotion/src/getTheme.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,6 @@ import type {
5252
const getTheme =
5353
(themeOrOverride: ThemeOrOverride) =>
5454
(ancestorTheme = {} as BaseTheme) => {
55-
try {
56-
// If a valid InstUI theme is given, it just returns the theme.
57-
if (isBaseTheme(themeOrOverride)) {
58-
return themeOrOverride
59-
}
60-
} catch {
61-
// If the prop passed is not an Object, it will throw an error.
62-
// We are using this fail-safe here for the non-TS users,
63-
// because the whole page can break without a theme.
64-
if (process.env.NODE_ENV !== 'production') {
65-
console.warn(
66-
'The `theme` property provided to InstUISettingsProvider is not a valid InstUI theme object.\ntheme: ',
67-
themeOrOverride
68-
)
69-
}
70-
// eslint-disable-next-line no-param-reassign
71-
themeOrOverride = {}
72-
}
73-
7455
// we need to clone the ancestor theme not to override it
7556
let currentTheme
7657
if (Object.keys(ancestorTheme).length === 0) {
@@ -86,16 +67,44 @@ const getTheme =
8667
currentTheme = ancestorTheme
8768
}
8869

70+
let resolvedThemeOrOverride = themeOrOverride
71+
72+
if (typeof resolvedThemeOrOverride === 'function') {
73+
resolvedThemeOrOverride = resolvedThemeOrOverride(currentTheme)
74+
}
75+
try {
76+
// If a valid InstUI theme is given, it just returns the theme.
77+
if (isBaseTheme(resolvedThemeOrOverride)) {
78+
return resolvedThemeOrOverride
79+
}
80+
} catch {
81+
// If the prop passed is not an Object, it will throw an error.
82+
// We are using this fail-safe here for the non-TS users,
83+
// because the whole page can break without a theme.
84+
if (process.env.NODE_ENV !== 'production') {
85+
console.warn(
86+
'The `theme` property provided to InstUISettingsProvider is not a valid InstUI theme object.\ntheme: ',
87+
resolvedThemeOrOverride
88+
)
89+
}
90+
resolvedThemeOrOverride = {}
91+
}
92+
8993
const themeName = currentTheme.key
9094

9195
// we pick the overrides for the current theme from the override object
92-
const specificOverrides = (themeOrOverride as Overrides)?.themeOverrides
96+
const specificOverrides = (resolvedThemeOrOverride as Overrides)
97+
?.themeOverrides
9398
const currentThemeOverrides =
9499
(specificOverrides as SpecificThemeOverride)?.[themeName] ||
95100
specificOverrides ||
96101
{}
97102

98-
return mergeDeep(currentTheme, themeOrOverride, currentThemeOverrides)
103+
return mergeDeep(
104+
currentTheme,
105+
resolvedThemeOrOverride,
106+
currentThemeOverrides
107+
)
99108
}
100109

101110
export default getTheme

packages/emotion/src/useTheme.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import canvas from '@instructure/ui-themes'
2727
import { isEmpty } from '@instructure/ui-utils'
2828
import { ThemeRegistry } from '@instructure/theme-registry'
2929

30-
import type { ThemeOrOverride } from './EmotionTypes'
30+
import type { BaseThemeOrOverride } from './EmotionTypes'
3131

3232
/**
3333
* ---
@@ -40,7 +40,7 @@ import type { ThemeOrOverride } from './EmotionTypes'
4040
*/
4141
const useTheme = () => {
4242
// This reads the theme from Emotion's ThemeContext
43-
let theme = useEmotionTheme() as ThemeOrOverride
43+
let theme = useEmotionTheme() as BaseThemeOrOverride
4444

4545
if (isEmpty(theme)) {
4646
const globalTheme = ThemeRegistry.getCurrentTheme()

0 commit comments

Comments
 (0)