From bc4319ef6b6e61187c4db6d46beb91d7ac6a5442 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 17 Dec 2024 13:53:28 +0900 Subject: [PATCH] Add alpha design token types (#2564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Self Checklist - [x] I wrote a PR title in **English** and added an appropriate **label** to the PR. - [x] I wrote the commit message in **English** and to follow [**the Conventional Commits specification**](https://www.conventionalcommits.org/en/v1.0.0/). - [x] I [added the **changeset**](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md) about the changes that needed to be released. (or didn't have to) - [x] I wrote or updated **documentation** related to the changes. (or didn't have to) - [x] I wrote or updated **tests** related to the changes. (or didn't have to) - [x] I tested the changes in various browsers. (or didn't have to) - Windows: Chrome, Edge, (Optional) Firefox - macOS: Chrome, Edge, Safari, (Optional) Firefox ## Related Issue - #2148 ## Summary - 알파 버전의 디자인 토큰 타입을 추가합니다 - 알파 버전의 디자인 토큰 프로바이더와 관련 훅, 토큰 객체를 export 합니다. ## Details - `alphaTokens` 로 토큰 객체에, `AlphaTokens` 네임스페이스로 토큰 타입에 접근할 수 있도록 합니다. - `useAlphaTokens` 훅으로 자바스크립트에서 현재 테마명에 맞는 알파 버전 디자인 토큰에 접근할 수 있습니다. - `ThemeProvider` 도 `AlphaThemeProvider` 를 별도로 두어야하나 고민했지만, 이미 알파 디자인 토큰이 스타일 시트에 말려들어가있어서 `ThemeProvider` 하나만(= `AppProvider` 하나만) 사용하더라도 `useAlphaTokens` 훅을 사용할 수 있도록 해도 괜찮겠다고 생각했습니다. - 현재 토큰 객체 구조상, 타이핑할 때 Functional Semantic 토큰을 묶어서 지칭할 수 있는 용어가 필요한데 현재 디자인 시스템에는 따로 정의가 없습니다. 임시로 학습 비용이 최대한 적은 방향으로, 기존과 동일하게 SemanticToken 이라고 칭했습니다. ### Breaking change? (Yes/No) No ## References 없음 --- .changeset/old-oranges-wink.md | 5 ++ .../AlphaTokenProvider/TokenProvider.tsx | 50 +++++++++++ .../AlphaTokenProvider/TokenProvider.types.ts | 20 +++++ .../components/AlphaTokenProvider/index.ts | 5 ++ .../components/AppProvider/AppProvider.tsx | 7 +- .../ThemeProvider/ThemeProvider.tsx | 30 +++++-- .../src/components/ThemeProvider/index.ts | 1 + packages/bezier-react/src/index.ts | 2 + .../bezier-react/src/types/alpha-tokens.ts | 89 +++++++++++++++++++ packages/bezier-react/src/types/tokens.ts | 17 ++-- packages/bezier-react/src/types/utils.ts | 12 +++ 11 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 .changeset/old-oranges-wink.md create mode 100644 packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.tsx create mode 100644 packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.types.ts create mode 100644 packages/bezier-react/src/components/AlphaTokenProvider/index.ts create mode 100644 packages/bezier-react/src/types/alpha-tokens.ts create mode 100644 packages/bezier-react/src/types/utils.ts diff --git a/.changeset/old-oranges-wink.md b/.changeset/old-oranges-wink.md new file mode 100644 index 0000000000..f4aa031ecd --- /dev/null +++ b/.changeset/old-oranges-wink.md @@ -0,0 +1,5 @@ +--- +'@channel.io/bezier-react': patch +--- + +Export the `alphaTokens`, `AlphaTokens`, and `useAlphaTokens` modules. diff --git a/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.tsx b/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.tsx new file mode 100644 index 0000000000..71315db2e0 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.tsx @@ -0,0 +1,50 @@ +'use client' + +import { useMemo } from 'react' + +import { tokens } from '@channel.io/bezier-tokens/alpha' + +import { type ThemeName } from '~/src/types/tokens' +import { createContext } from '~/src/utils/react' + +import { + type ThemeSpecificTokens, + type TokenContextValue, + type TokenProviderProps, +} from './TokenProvider.types' + +const [TokenContextProvider, useTokenContext] = + // FIXME: (@ed) Remove Alpha prefix after the migration is done + createContext(null, 'AlphaTokenProvider') + +export { useTokenContext as useAlphaTokenContext } + +const tokenSet: Record = Object.freeze({ + light: { + global: tokens.global, + semantic: tokens.lightTheme, + }, + dark: { + global: tokens.global, + semantic: tokens.darkTheme, + }, +}) + +/** + * @private + */ +export function TokenProvider({ themeName, children }: TokenProviderProps) { + return ( + ({ + themeName, + tokens: tokenSet[themeName], + }), + [themeName] + )} + > + {children} + + ) +} diff --git a/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.types.ts b/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.types.ts new file mode 100644 index 0000000000..c8705ab735 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaTokenProvider/TokenProvider.types.ts @@ -0,0 +1,20 @@ +import { + type GlobalToken, + type SemanticToken, + type ThemeName, +} from '~/src/types/alpha-tokens' + +export interface ThemeSpecificTokens { + global: GlobalToken + semantic: SemanticToken +} + +export interface TokenContextValue { + themeName: ThemeName + tokens: ThemeSpecificTokens +} + +export interface TokenProviderProps { + themeName: ThemeName + children: React.ReactNode +} diff --git a/packages/bezier-react/src/components/AlphaTokenProvider/index.ts b/packages/bezier-react/src/components/AlphaTokenProvider/index.ts new file mode 100644 index 0000000000..70e772ab27 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaTokenProvider/index.ts @@ -0,0 +1,5 @@ +export { + TokenProvider as AlphaTokenProvider, + useAlphaTokenContext, +} from './TokenProvider' +export type { TokenProviderProps as AlphaTokenProviderProps } from './TokenProvider.types' diff --git a/packages/bezier-react/src/components/AppProvider/AppProvider.tsx b/packages/bezier-react/src/components/AppProvider/AppProvider.tsx index 14d9200962..5bdb1dcefa 100644 --- a/packages/bezier-react/src/components/AppProvider/AppProvider.tsx +++ b/packages/bezier-react/src/components/AppProvider/AppProvider.tsx @@ -4,6 +4,7 @@ import { useEffect } from 'react' import { getWindow } from 'ssr-window' +import { AlphaTokenProvider } from '~/src/components/AlphaTokenProvider' import { FeatureProvider } from '~/src/components/FeatureProvider' import { TokenProvider } from '~/src/components/TokenProvider' import { WindowProvider } from '~/src/components/WindowProvider' @@ -52,7 +53,11 @@ export function AppProvider({ return ( - {children} + + + {children} + + ) diff --git a/packages/bezier-react/src/components/ThemeProvider/ThemeProvider.tsx b/packages/bezier-react/src/components/ThemeProvider/ThemeProvider.tsx index a00d8b5dda..2b82dcc080 100644 --- a/packages/bezier-react/src/components/ThemeProvider/ThemeProvider.tsx +++ b/packages/bezier-react/src/components/ThemeProvider/ThemeProvider.tsx @@ -4,6 +4,10 @@ import { forwardRef } from 'react' import { Slot } from '@radix-ui/react-slot' +import { + AlphaTokenProvider, + useAlphaTokenContext, +} from '~/src/components/AlphaTokenProvider' import { TokenProvider, useTokenContext } from '~/src/components/TokenProvider' import { @@ -25,6 +29,14 @@ export function useTokens() { return useTokenContext('useTokens').tokens } +/** + * `useAlphaTokens` is a hook that returns the alpha tokens of the current theme. + * @internal + */ +export function useAlphaTokens() { + return useAlphaTokenContext('useAlphaTokens').tokens +} + /** * `ThemeProvider` is a wrapper component that provides theme context. * @@ -34,14 +46,16 @@ export const ThemeProvider = forwardRef( function ThemeProvider({ themeName, children, ...rest }, forwardedRef) { return ( - - {children} - + + + {children} + + ) } diff --git a/packages/bezier-react/src/components/ThemeProvider/index.ts b/packages/bezier-react/src/components/ThemeProvider/index.ts index fdcb4ada4d..225adf1dbf 100644 --- a/packages/bezier-react/src/components/ThemeProvider/index.ts +++ b/packages/bezier-react/src/components/ThemeProvider/index.ts @@ -5,6 +5,7 @@ export { ThemeProvider, useThemeName, useTokens, + useAlphaTokens, } from './ThemeProvider' export { type FixedThemeProviderProps, diff --git a/packages/bezier-react/src/index.ts b/packages/bezier-react/src/index.ts index b9d8875985..33a52dd5c2 100644 --- a/packages/bezier-react/src/index.ts +++ b/packages/bezier-react/src/index.ts @@ -3,6 +3,7 @@ import '~/src/styles/index.scss' /* --------------------------------- TOKENS --------------------------------- */ export { tokens } from '@channel.io/bezier-tokens' +export { tokens as alphaTokens } from '@channel.io/bezier-tokens/alpha' /* ------------------------------- COMPONENTS ------------------------------- */ export * from '~/src/components/AlphaAvatar' @@ -78,3 +79,4 @@ export * from '~/src/hooks/useKeyboardActionLockerWhileComposing' /* ---------------------------------- TYPES --------------------------------- */ export type * from '~/src/types/props' export type * from '~/src/types/tokens' +export type * as AlphaTokens from '~/src/types/alpha-tokens' diff --git a/packages/bezier-react/src/types/alpha-tokens.ts b/packages/bezier-react/src/types/alpha-tokens.ts new file mode 100644 index 0000000000..4ea4938fac --- /dev/null +++ b/packages/bezier-react/src/types/alpha-tokens.ts @@ -0,0 +1,89 @@ +import { type tokens } from '@channel.io/bezier-tokens/alpha' + +import { + type ExtractKeys, + type RemovePrefix, + type StartsWithPrefix, +} from './utils' + +// TODO: Change theme name constant to import from bezier-tokens +export type ThemeName = 'light' | 'dark' + +export type GlobalToken = typeof tokens.global +/** + * FIXME: Separate functional and semantic tokens? + */ +export type SemanticToken = typeof tokens.lightTheme | typeof tokens.darkTheme + +// NOTE: (@ed) Do not remove alpha- prefix to match CSS variable names +export type FlattenGlobalToken = ExtractKeys +export type FlattenSemanticToken = ExtractKeys< + SemanticToken[keyof SemanticToken] +> +export type FlattenAllToken = FlattenGlobalToken | FlattenSemanticToken + +export type GlobalColor = RemovePrefix< + 'alpha-color', + keyof GlobalToken['color'] +> + +/** + * Functional & Semantic color tokens + */ +export type BaseSemanticColor = RemovePrefix< + 'alpha-color', + keyof SemanticToken['color'] +> + +export type BackgroundFunctionalColor = StartsWithPrefix< + 'bg', + BaseSemanticColor +> +export type ForegroundFunctionalColor = StartsWithPrefix< + 'fg', + BaseSemanticColor +> +export type SurfaceFunctionalColor = StartsWithPrefix< + 'surface', + BaseSemanticColor +> +export type ShadowFunctionalColor = StartsWithPrefix< + 'shadow', + BaseSemanticColor +> +export type DimFunctionalColor = StartsWithPrefix<'dim', BaseSemanticColor> + +export type FunctionalColor = + | BackgroundFunctionalColor + | ForegroundFunctionalColor + | SurfaceFunctionalColor + | ShadowFunctionalColor + | DimFunctionalColor + +export type SemanticColor = StartsWithPrefix< + 'primary' | 'critical' | 'warning' | 'accent' | 'success', + BaseSemanticColor +> + +export type Color = GlobalColor | FunctionalColor | SemanticColor + +export type Radius = RemovePrefix<'alpha-radius', keyof GlobalToken['radius']> +export type Opacity = RemovePrefix< + 'alpha-opacity', + keyof GlobalToken['opacity'] +> +export type Font = RemovePrefix<'alpha-font', keyof GlobalToken['font']> +export type Typography = RemovePrefix< + 'alpha-typography', + keyof GlobalToken['typography'] +> +export type GlobalGradient = RemovePrefix< + 'alpha-gradient', + keyof GlobalToken['gradient'] +> + +export type Shadow = RemovePrefix<'alpha-shadow', keyof SemanticToken['shadow']> +export type FunctionalGradient = RemovePrefix< + 'alpha-gradient', + keyof SemanticToken['gradient'] +> diff --git a/packages/bezier-react/src/types/tokens.ts b/packages/bezier-react/src/types/tokens.ts index 9e5923a3e3..c757608785 100644 --- a/packages/bezier-react/src/types/tokens.ts +++ b/packages/bezier-react/src/types/tokens.ts @@ -1,17 +1,10 @@ import { type tokens } from '@channel.io/bezier-tokens' -type RemovePrefix< - Prefix extends string, - Value extends string, -> = Value extends `${Prefix}-${infer Rest}` ? Rest : never - -type StartsWithPrefix< - Prefix extends string, - Value extends string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars -> = Value extends `${Prefix}-${infer Rest}` ? Value : never - -type ExtractKeys = T extends Record ? K : never +import { + type ExtractKeys, + type RemovePrefix, + type StartsWithPrefix, +} from './utils' // TODO: Change theme name constant to import from bezier-tokens export type ThemeName = 'light' | 'dark' diff --git a/packages/bezier-react/src/types/utils.ts b/packages/bezier-react/src/types/utils.ts new file mode 100644 index 0000000000..1d0da6e616 --- /dev/null +++ b/packages/bezier-react/src/types/utils.ts @@ -0,0 +1,12 @@ +export type RemovePrefix< + Prefix extends string, + Value extends string, +> = Value extends `${Prefix}-${infer Rest}` ? Rest : never + +export type StartsWithPrefix< + Prefix extends string, + Value extends string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars +> = Value extends `${Prefix}-${infer Rest}` ? Value : never + +export type ExtractKeys = T extends Record ? K : never