Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/__docs__/src/withStyleForDocs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ const withStyleForDocs = decorator(
theme,
displayName,
ComposedComponent.componentId,
componentProps,
(componentProps as ThemeOverrideProp).themeOverride,
componentTheme
)

Expand Down
51 changes: 45 additions & 6 deletions packages/emotion/src/InstUISettingsProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,61 @@ import { getTheme } from '../getTheme'

import type { ThemeOrLegacyOverride } from '../EmotionTypes'
import type { DeterministicIdProviderValue } from '@instructure/ui-react-utils'
import type { NewThemeOverrideObject, Theme } from '@instructure/ui-themes'
declare const process: Record<string, any> | undefined

type InstUIProviderProps = {
children?: React.ReactNode

/**
* A full theme or an override object
* A full theme or an override object. The override only works for legacy
* (v11.6 or earlier) themes, for newer ones use the `themeOverride` prop.
*/
theme?: ThemeOrLegacyOverride

// TODO-theme-types: fix override typing
// TODO explain the usage of this override object. It will be deep merged into theme.themeOverride and the shape has to be a partial of the "newTheme" object
/**
* An override object for the new theming system.
* An override object for the new theming system. It will be deep merged into
* the theme. One can override primitives, semantics and individual component's
* themes, for example:
* ```js
* themeOverride={{
* semantics: {
* color: {
* stroke: {
* error: 'purple'
* }
* }
* },
* primitives: {
* color: {
* blue: {
* blue100: 'yellow'
* }
* }
* },
* components: {
* Alert: {
* background: 'brown',
* infoIconBackground: 'darkblue',
* borderWidth: '0.5rem'
* },
* Pill: {
* baseTextColor: 'purple',
* baseBorderColor: 'purple'
* }
* },
* sharedTokens: {
* focusOutline: {
* width: '0.55rem',
* infoColor: 'deeppink'
* }
* }
* }}
* ```
*/
themeOverride?: any
themeOverride?:
| NewThemeOverrideObject
| ((theme: Theme) => NewThemeOverrideObject)

/**
* @deprecated the `instanceCounterMap` prop is deprecated. You don't need to supply the
Expand Down Expand Up @@ -100,7 +139,6 @@ function InstUISettingsProvider({
* For backward compatibility reasons, the old way of passing a partial theme to the theme prop is still supported, however only for
* legacy (pre v11_7) components. Overriding the newTheme this way could break the system.
*/

let providers = (
<DeterministicIdContextProvider instanceCounterMap={instanceCounterMap}>
<ThemeProvider theme={getTheme(theme, themeOverride)}>
Expand All @@ -120,3 +158,4 @@ function InstUISettingsProvider({

export default InstUISettingsProvider
export { InstUISettingsProvider }
export type { InstUIProviderProps }
8 changes: 5 additions & 3 deletions packages/emotion/src/getComponentThemeOverride.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type {
} from './EmotionTypes'
import type { ComponentTheme } from '@instructure/shared-types'
import { ThemeOverrideProp } from './withStyle'
import { ThemeOverrideValue } from './useStyle'
import type { NewComponentTypes } from '@instructure/ui-themes'

type ComponentName = keyof ComponentOverride | undefined

Expand All @@ -51,8 +51,10 @@ const getComponentThemeOverride = (
theme: ThemeOverride,
displayName: string,
componentId?: string,
// ThemeOverrideProp is the old type, ThemeOverrideValue is the new one
themeOverride?: ThemeOverrideProp['themeOverride'] | ThemeOverrideValue,
// ThemeOverrideProp['themeOverride'] is the old type
themeOverride?:
| ThemeOverrideProp['themeOverride']
| ReturnType<NewComponentTypes[keyof NewComponentTypes]>,
componentTheme?: ComponentTheme
): Partial<ComponentTheme> => {
const name = displayName as ComponentName
Expand Down
15 changes: 11 additions & 4 deletions packages/emotion/src/getTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
import canvas from '@instructure/ui-themes'
import { isBaseTheme, mergeDeep } from '@instructure/ui-utils'

import type { BaseTheme } from '@instructure/shared-types'
import type { Theme } from '@instructure/ui-themes'

import type {
Overrides,
ThemeOrLegacyOverride,
SpecificThemeOverride
} from './EmotionTypes'
import { InstUIProviderProps } from './InstUISettingsProvider'
declare const process: Record<string, any> | undefined

/**
Expand All @@ -45,14 +46,20 @@ declare const process: Record<string, any> | undefined
* the overrides merged together.
*
* @param themeOrLegacyOverride - A full theme or an override object
* @param themeOverride - if provided, it means it's a new theming-system override. This will be merged into theme.themeOverride and will be treated separately from the old way of applying overrides. This override will be applied in the withStyle.ts decorator
* @param themeOverride - if provided, it means it's a new theming-system override.
* This will be merged into theme.themeOverride and will be treated separately
* from the old way of applying overrides. This override will be applied in the
* `withStyle.ts` decorator
* @returns A function that returns with the theme object for the [ThemeProvider](https://emotion.sh/docs/theming#themeprovider-reactcomponenttype)
* This function is called by Emotion on theme provider creation, where
* `ancestorTheme` is a theme object from an ancestor `ThemeProvider`
*/
const getTheme =
(themeOrLegacyOverride: ThemeOrLegacyOverride, themeOverride?: any) =>
(ancestorTheme = {} as BaseTheme) => {
(
themeOrLegacyOverride: ThemeOrLegacyOverride,
themeOverride?: InstUIProviderProps['themeOverride']
) =>
(ancestorTheme = {} as Theme) => {
// we need to clone the ancestor theme not to override it
let currentTheme
if (Object.keys(ancestorTheme).length === 0) {
Expand Down
5 changes: 3 additions & 2 deletions packages/emotion/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ export { useStyleLegacy } from './useStyleLegacy'
export { useStyle } from './useStyle'
export { useTheme } from './useTheme'

export type { InstUIProviderProps } from './InstUISettingsProvider'
export type { ComponentStyle, StyleObject, Overrides } from './EmotionTypes'
export type { WithStyleProps } from './withStyleLegacy'
export type { ThemeOverrideValue } from './useStyle'
export type { WithStyleProps } from './withStyle'
export type { NewThemeOverrideProp } from './useStyle'
export type {
SpacingValues,
Spacing,
Expand Down
79 changes: 37 additions & 42 deletions packages/emotion/src/useStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,98 +25,93 @@
import { useTheme } from './useTheme'
import { mergeDeep } from '@instructure/ui-utils'
import type {
SharedTokens,
NewComponentTypes,
SharedTokens,
Theme
} from '@instructure/ui-themes'

// returns the second parameter of a function
type SecondParameter<T extends (...args: any) => any> =
Parameters<T>[1] extends undefined ? never : Parameters<T>[1]

type GenerateStyleParams =
| ((componentTheme: any, params: any, sharedTokens: SharedTokens) => any)
| ((componentTheme: any, params: any) => any)
| ((componentTheme: any) => any)
type NewThemeOverrideProp<
ComponentTheme extends ReturnType<NewComponentTypes[keyof NewComponentTypes]>
> = {
themeOverride?:
| Partial<ComponentTheme>
| ((compTheme: ComponentTheme, theme: Theme) => Partial<ComponentTheme>)
}

/**
* Type for a theme override
*/
type ThemeOverrideValue =
| Partial<Theme>
type GenerateStyleFn<ComponentTheme> =
| ((
componentTheme: Theme,
currentTheme: NewComponentTypes[keyof NewComponentTypes]
) => Partial<Theme>)
componentTheme: ComponentTheme,
params: any,
sharedTokens: SharedTokens
) => any)
| ((componentTheme: ComponentTheme, params: any) => any)
| ((componentTheme: ComponentTheme) => any)

/**
* new useStyle syntax, use this with v12 themes
* new useStyle syntax, use this with v11.7+ themes
*/

// TODO: improve useStyle to handle generateStyle functions that don't
// have a theme.
const useStyle = <P extends GenerateStyleParams>(useStyleParams: {
generateStyle: P
params?: SecondParameter<P>
const useStyle = <
ComponentTheme extends ReturnType<NewComponentTypes[keyof NewComponentTypes]>,
GenerateStyle extends GenerateStyleFn<ComponentTheme>
>(useStyleParams: {
generateStyle: GenerateStyle
params?: SecondParameter<GenerateStyle>
// needs to be a string too because it might be a child component
componentId: keyof NewComponentTypes | string
themeOverride: ThemeOverrideValue | undefined
themeOverride?: NewThemeOverrideProp<ComponentTheme>['themeOverride']
displayName?: string
//in case of a child component needed to use it's parent's tokens, provide parent's name
//in case of a child component needed to use its parent's tokens, provide parent's name
useTokensFrom?: keyof NewComponentTypes
}): ReturnType<P> => {
}): ReturnType<GenerateStyle> => {
const { generateStyle, params, componentId, themeOverride } = useStyleParams
const useTokensFrom = useStyleParams.useTokensFrom
const themeInContext = useTheme() as Theme

const themeOverrideFromProvider = themeInContext.themeOverride
const componentWithTokensId = useTokensFrom ?? componentId
const componentWithTokensId =
useTokensFrom ?? (componentId as keyof NewComponentTypes)

// resolving the theming functions and applying the overrides
const primitiveOverrides = themeOverrideFromProvider?.primitives
const semanticsOverrides = themeOverrideFromProvider?.semantics
// @ts-ignore TODO-theme-types: fix typing
const sharedTokensOverrides = themeOverrideFromProvider?.sharedTokens
const componentOverridesFromSettingsProvider =
// @ts-ignore TODO-theme-types: fix typing
themeOverrideFromProvider?.components?.[
componentWithTokensId as keyof NewComponentTypes
]
themeOverrideFromProvider?.components?.[componentWithTokensId]

const primitives = mergeDeep(
themeInContext.newTheme.primitives,
primitiveOverrides
primitiveOverrides!
)

const semantics = mergeDeep(
themeInContext.newTheme.semantics?.(primitives),
semanticsOverrides
semanticsOverrides!
)

const sharedTokens = mergeDeep(
themeInContext.newTheme.sharedTokens?.(semantics),
sharedTokensOverrides
sharedTokensOverrides as Record<string, unknown>
)

const baseComponentTheme =
themeInContext.newTheme.components[
componentWithTokensId as keyof NewComponentTypes
]?.(semantics)
themeInContext.newTheme.components[componentWithTokensId]?.(semantics)

const componentThemeFromSettingsProvider = mergeDeep(
baseComponentTheme,
componentOverridesFromSettingsProvider
)
componentOverridesFromSettingsProvider as Record<string, unknown>
) as ComponentTheme

const componentTheme = mergeDeep(
componentThemeFromSettingsProvider,
// @ts-ignore TODO-theme-types: fix typing
typeof themeOverride === 'function'
? themeOverride(
componentThemeFromSettingsProvider as Theme,
themeInContext as any
)
: themeOverride
? themeOverride(componentThemeFromSettingsProvider, themeInContext)
: themeOverride!
)

// @ts-ignore TODO-theme-types: fix typing
Expand All @@ -125,4 +120,4 @@ const useStyle = <P extends GenerateStyleParams>(useStyleParams: {

export default useStyle
export { useStyle }
export type { ThemeOverrideValue }
export type { NewThemeOverrideProp }
Loading
Loading