Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ npm-debug.log*

package-lock.json

packages/react-component-library/*.tgz
21 changes: 19 additions & 2 deletions packages/design-tokens/src/__tests__/selectors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
fontSize,
shadow,
zIndex,
colorValue,
} = selectors

let wrapper: RenderResult
Expand Down Expand Up @@ -211,15 +212,31 @@ describe('color', () => {
color: ${color('neutral', '100')};
`

it('should set styles correctly', () => {
it('resolves to a themeable CSS variable with the light value as fallback', () => {
wrapper = render(<StyledComponent data-testid="test-element" />)

expect(wrapper.getByTestId('test-element')).toHaveStyleRule(
'color',
'#e2e9ee'
'var(--color-neutral-100, #e2e9ee)'
)
})
})
})

describe('colorValue', () => {
describe('when the group or shade is invalid', () => {
it('throws a friendly error', () => {
expect(() => colorValue('invalid' as ColorGroup, '400')).toThrow(
'Invalid color token'
)
})
})

describe('when the group and shade are valid', () => {
it('returns the concrete light value with no theme', () => {
expect(colorValue('neutral', '100')).toBe('#e2e9ee')
})
})
})

describe('fontSize', () => {
Expand Down
74 changes: 73 additions & 1 deletion packages/design-tokens/src/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,19 @@ export function getAnimation(index: AnimationTiming, theme?: Theme): string {
throw new Error(`Invalid animation token for index: '${index}'`)
}

export function getColor(
export function colorVariableName(
group: ColorGroup,
weight: ColorShade
): string {
return `--color-${group}-${weight}`
}

/**
* Resolve a colour token to its concrete hex value for the given theme
* (falling back to the default light theme). Use this where the value
* must be a parseable colour, e.g. when passed to `polished` helpers.
*/
export function getColorValue(
group: ColorGroup,
weight: ColorShade,
theme?: Theme
Expand All @@ -119,6 +131,66 @@ export function getColor(
)
}

/**
* Resolve a colour token. When an explicit theme is provided the concrete
* hex value is returned (preserving threaded-theme behaviour). Otherwise a
* CSS custom property reference is returned so the colour reacts to the
* theme injected by `GlobalStyleProvider`, with the light value as a static
* fallback for unprovided/SSR contexts.
*/
export function getColor(
group: ColorGroup,
weight: ColorShade,
theme?: Theme
): string {
if (theme?.colorsTokens) {
return getColorValue(group, weight, theme)
}

const fallback = getColorValue(group, weight)

return `var(${colorVariableName(group, weight)}, ${fallback})`
}

/**
* Resolve a colour token, choosing a different shade per theme mode. Use this
* to tune a colour for dark mode without changing the light appearance: the
* light shade is used in light mode and the dark shade in dark mode. Pass the
* styled-component's theme, e.g.
* `({ theme }) => colorByMode('neutral', '000', '100', theme as Theme)`.
*/
export function getColorByMode(
group: ColorGroup,
lightWeight: ColorShade,
darkWeight: ColorShade,
theme?: Theme
): string {
const weight = theme?.mode === 'dark' ? darkWeight : lightWeight

return getColor(group, weight, theme)
}

/**
* Build the `--color-*` custom property declarations for a theme, for
* injection by `GlobalStyleProvider`.
*/
export function getColorVariables(theme?: Theme): Record<string, string> {
const { color } = getTheme(theme).colorsTokens as unknown as {
color: Record<string, Record<string, { value?: string }>>
}

const entries = Object.entries(color).flatMap(([group, shades]) =>
Object.entries(shades)
.filter(([, token]) => isTokenValid(token?.value))
.map(
([weight, token]) =>
[`--color-${group}-${weight}`, token.value as string] as const
)
)

return Object.fromEntries(entries)
}

export function getTypography(size: TypographySize, theme?: Theme): string {
const value = get(
getTheme(theme).typographyTokens,
Expand Down
6 changes: 5 additions & 1 deletion packages/design-tokens/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import selectors from './selectors'
export * from './types'
export * from './flat'

export { getTheme } from './getters'
export { getTheme, getColorVariables, colorVariableName } from './getters'

export { selectors }

Expand All @@ -14,6 +14,8 @@ const {
animation,
breakpoint,
color,
colorValue,
colorByMode,
fontSize,
mediaQuery,
mq,
Expand All @@ -26,6 +28,8 @@ export {
animation,
breakpoint,
color,
colorValue,
colorByMode,
fontSize,
mediaQuery,
mq,
Expand Down
4 changes: 4 additions & 0 deletions packages/design-tokens/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
getBreakpoint,
getMediaQuery,
getColor,
getColorValue,
getColorByMode,
getShadow,
getSpacing,
getTypography,
Expand All @@ -15,6 +17,8 @@ export default {
breakpoint: getBreakpoint,
animation: getAnimation,
color: getColor,
colorValue: getColorValue,
colorByMode: getColorByMode,
fontSize: getTypography,
shadow: getShadow,
spacing: getSpacing,
Expand Down
Loading
Loading