Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement multi theme feature based on data attributes #1756

Merged
merged 29 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6fdefea
feat: implement alpha bezier provider (wip)
sungik-choi Nov 23, 2023
3e0fa8f
feat(styles): add color-scheme style
sungik-choi Nov 23, 2023
0b4e6ea
chore(styles): add comment
sungik-choi Nov 23, 2023
1c73be3
feat(styles): add base font color style
sungik-choi Nov 23, 2023
9935160
feat(bezier-react): implement useToken hook
sungik-choi Nov 28, 2023
244f5cf
refactor(use-token): include token set
sungik-choi Nov 28, 2023
1677c2f
feat(use-tokens): add themeName value to context value and implement …
sungik-choi Nov 28, 2023
b0fc27b
feat: implement fixed theme providers
sungik-choi Nov 28, 2023
367a0c6
feat(alpha-bezier-provider): add tooltip provider
sungik-choi Nov 28, 2023
187450b
feat(alpha-bezier-provider): consider when other root elements may ex…
sungik-choi Nov 28, 2023
52bc57d
feat(alpha-bezier-provider): add feature provider
sungik-choi Nov 28, 2023
c10dc90
refactor: rename to AlphaAppProvider
sungik-choi Nov 28, 2023
58e123a
fix(styles): change html to where selector to support shadow root hos…
sungik-choi Nov 28, 2023
7975f56
fix(styles): change data selector to affect all elements
sungik-choi Nov 28, 2023
59158f4
refactor(alpha-app-provider): rename prop
sungik-choi Nov 29, 2023
0ed795c
docs(alpha-app-provider): add jsdoc
sungik-choi Nov 29, 2023
4462f81
docs: add jsdoc
sungik-choi Nov 29, 2023
4c3c887
feat(root): export modules
sungik-choi Nov 29, 2023
45606aa
chore(changeset): add changeset
sungik-choi Nov 29, 2023
dce4b9c
fix: fix typecheck error
sungik-choi Nov 29, 2023
e96e4de
fix: fix import path
sungik-choi Nov 29, 2023
c943fa1
refactor(window-provider): rm document from window provider
sungik-choi Nov 29, 2023
3c98893
feat(alpha-app-provider): apply WindowProvider and add window prop
sungik-choi Nov 29, 2023
f2090b9
feat(alpha-app-provider): change to use ThemeProvider internally
sungik-choi Nov 29, 2023
f8befb7
feat(styles): rm root color scheme
sungik-choi Nov 29, 2023
f2faf6b
feat: back to the root element
sungik-choi Nov 29, 2023
9776221
feat(theme-provider): apply the changed design token structure
sungik-choi Nov 30, 2023
257b8d9
refactor(theme-provider): add Provider suffix
sungik-choi Dec 1, 2023
d89ea83
refactor(alpha-app-provider): change callback fn name
sungik-choi Dec 1, 2023
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
5 changes: 5 additions & 0 deletions .changeset/chilled-dots-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@channel.io/bezier-react": minor
---

Implement multi theme feature based on data attributes.
1 change: 1 addition & 0 deletions packages/bezier-react/src/features/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { Feature } from './Feature'
export { FeatureType } from './Feature'

export {
Expand Down
14 changes: 13 additions & 1 deletion packages/bezier-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import '~/src/styles/index.scss'

/* Provider */
export { default as BezierProvider } from '~/src/providers/BezierProvider'
export { default as WindowProvider, useWindow } from '~/src/providers/WindowProvider'
export * from '~/src/providers/WindowProvider'
export * from '~/src/providers/AlphaAppProvider'
export {
useThemeName,
useToken,
ThemeProvider,
LightThemeProvider,
DarkThemeProvider,
InvertedThemeProvider,
type ThemeName,
type ThemeProviderProps,
type FixedThemeProviderProps,
} from '~/src/providers/ThemeProvider'

/* Foundation */
export * from '~/src/foundation'
Expand Down
85 changes: 85 additions & 0 deletions packages/bezier-react/src/providers/AlphaAppProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useEffect } from 'react'

import {
type Feature,
FeatureProvider,
} from '~/src/features'
import { window as defaultWindow } from '~/src/utils/dom'

import { TooltipProvider } from '~/src/components/Tooltip'

import {
type ThemeName,
TokenProvider,
} from './ThemeProvider'
import { WindowProvider } from './WindowProvider'

export interface AlphaAppProviderProps {
children: React.ReactNode
/**
* Name of the theme to use for the app.
* @default 'light'
*/
themeName?: ThemeName
/**
* List of features to enable for the app.
* @default []
*/
features?: Feature[]
/**
* Window object to use for the app.
* @default window
*/
window?: Window
}

/**
* `AlphaAppProvider` is a required wrapper component that provides context for the app.
*
* @example
*
* ```tsx
* import React from 'react'
* import { createRoot } from 'react-dom/client'
* import { AlphaAppProvider } from '@channel.io/bezier-react'
*
* const container = document.getElementById('root')
* const root = createRoot(container)
*
* root.render(
* <AlphaAppProvider themeName="light">
* <App />
* </AlphaAppProvider>,
* )
* ```
*/
export function AlphaAppProvider({
children,
themeName = 'light',
features = [],
window = defaultWindow,
}: AlphaAppProviderProps) {
useEffect(function updateRootThemeDataAttribute() {
const rootElement = window.document.documentElement

Check warning on line 63 in packages/bezier-react/src/providers/AlphaAppProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/AlphaAppProvider.tsx#L62-L63

Added lines #L62 - L63 were not covered by tests
// TODO: Change data attribute constant to import from bezier-tokens
rootElement.setAttribute('data-bezier-theme', themeName)
return function cleanup() {
rootElement.removeAttribute('data-bezier-theme')

Check warning on line 67 in packages/bezier-react/src/providers/AlphaAppProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/AlphaAppProvider.tsx#L65-L67

Added lines #L65 - L67 were not covered by tests
}
}, [
window,
themeName,
])

return (

Check warning on line 74 in packages/bezier-react/src/providers/AlphaAppProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/AlphaAppProvider.tsx#L74

Added line #L74 was not covered by tests
<WindowProvider window={window}>
<FeatureProvider features={features}>
<TokenProvider themeName={themeName}>
<TooltipProvider>
{ children }
</TooltipProvider>
</TokenProvider>
</FeatureProvider>
</WindowProvider>
)
}
14 changes: 4 additions & 10 deletions packages/bezier-react/src/providers/BezierProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ import {
type ThemeVarsAdditionalType,
} from '~/src/foundation'

import {
document as defaultDocument,
window as defaultWindow,
} from '~/src/utils/dom'
import { window as defaultWindow } from '~/src/utils/dom'

import { TooltipProvider } from '~/src/components/Tooltip'

import WindowProvider from './WindowProvider'
import { WindowProvider } from './WindowProvider'

interface BezierProviderProps {
foundation: Foundation & GlobalStyleProp
Expand All @@ -29,13 +26,10 @@ function BezierProvider({
foundation,
children,
themeVarsScope,
externalWindow,
externalWindow = defaultWindow,
}: BezierProviderProps) {
return (
<WindowProvider
window={externalWindow ?? defaultWindow}
document={externalWindow?.document ?? defaultDocument}
>
<WindowProvider window={externalWindow}>
<FoundationProvider foundation={foundation}>
<TooltipProvider>
<GlobalStyle foundation={foundation} />
Expand Down
151 changes: 151 additions & 0 deletions packages/bezier-react/src/providers/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, {
forwardRef,
useMemo,
} from 'react'

import { tokens } from '@channel.io/bezier-tokens'
import { Slot } from '@radix-ui/react-slot'

import { createContext } from '~/src/utils/react'

type Tokens = typeof tokens
type GlobalTokens = Tokens['global']
type SemanticTokens = Omit<Tokens, 'global'>

interface ThemedTokenSet {
global: GlobalTokens
semantic: SemanticTokens[keyof SemanticTokens]
}

// TODO: Change theme name constant to import from bezier-tokens
export type ThemeName = 'light' | 'dark'

interface TokenContextValue {
themeName: ThemeName
tokens: ThemedTokenSet
}

const [TokenContextProvider, useTokenContext] = createContext<TokenContextValue | null>(null, 'TokenProvider')

Check warning on line 28 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L28

Added line #L28 was not covered by tests

const tokenSet: Record<ThemeName, ThemedTokenSet> = Object.freeze({

Check warning on line 30 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L30

Added line #L30 was not covered by tests
light: {
global: tokens.global,
semantic: tokens.lightTheme,
},
dark: {
global: tokens.global,
semantic: tokens.darkTheme,
},
})

interface TokenProviderProps {
themeName: ThemeName
children: React.ReactNode
}

/**
* @private For internal use only.
*/
export function TokenProvider({
themeName,
children,
}: TokenProviderProps) {
return (
<TokenContextProvider value={useMemo(() => ({

Check warning on line 54 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L53-L54

Added lines #L53 - L54 were not covered by tests
themeName,
tokens: tokenSet[themeName],
}), [themeName])}
>
{ children }
</TokenContextProvider>
)
}

/**
* `useThemeName` is a hook that returns the current theme name.
*/
export function useThemeName() {
return useTokenContext('useThemeName').themeName

Check warning on line 68 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L68

Added line #L68 was not covered by tests
}

/**
* `useToken` is a hook that returns the design token for the current theme.
*/
export function useToken() {
return useTokenContext('useToken').tokens

Check warning on line 75 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L75

Added line #L75 was not covered by tests
}

export interface ThemeProviderProps {
themeName: ThemeName
children: React.ReactElement
}

export type FixedThemeProviderProps = Omit<ThemeProviderProps, 'themeName'>

/**
* `ThemeProvider` is a wrapper component that provides theme context.
*/
export const ThemeProvider = forwardRef<HTMLElement, ThemeProviderProps>(function ThemeProvider({

Check warning on line 88 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L88

Added line #L88 was not covered by tests
themeName,
children,
}, forwardedRef) {
return (

Check warning on line 92 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L92

Added line #L92 was not covered by tests
<TokenProvider themeName={themeName}>
<Slot
ref={forwardedRef}
// TODO: Change data attribute constant to import from bezier-tokens
data-bezier-theme={themeName}
>
{ children }
</Slot>
</TokenProvider>
)
})

/**
* `LightThemeProvider` is a wrapper component that provides light theme context.
*/
export const LightThemeProvider = forwardRef<HTMLElement, FixedThemeProviderProps>(function LightTheme({

Check warning on line 108 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L108

Added line #L108 was not covered by tests
children,
}, forwardedRef) {
return (

Check warning on line 111 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L111

Added line #L111 was not covered by tests
<ThemeProvider
ref={forwardedRef}
themeName="light"
>
{ children }
</ThemeProvider>
)
})

/**
* `DarkThemeProvider` is a wrapper component that provides dark theme context.
*/
export const DarkThemeProvider = forwardRef<HTMLElement, FixedThemeProviderProps>(function DarkTheme({

Check warning on line 124 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L124

Added line #L124 was not covered by tests
children,
}, forwardedRef) {
return (

Check warning on line 127 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L127

Added line #L127 was not covered by tests
<ThemeProvider
ref={forwardedRef}
themeName="dark"
>
{ children }
</ThemeProvider>
)
})

/**
* `InvertedThemeProvider` is a wrapper component that provides inverted theme context.
*/
export const InvertedThemeProvider = forwardRef<HTMLElement, FixedThemeProviderProps>(function InvertedTheme({

Check warning on line 140 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L140

Added line #L140 was not covered by tests
children,
}, forwardedRef) {
return (

Check warning on line 143 in packages/bezier-react/src/providers/ThemeProvider.tsx

View check run for this annotation

Codecov / codecov/patch

packages/bezier-react/src/providers/ThemeProvider.tsx#L143

Added line #L143 was not covered by tests
<ThemeProvider
ref={forwardedRef}
themeName={useThemeName() === 'light' ? 'dark' : 'light'}
>
{ children }
</ThemeProvider>
)
})
13 changes: 3 additions & 10 deletions packages/bezier-react/src/providers/WindowProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,27 @@ interface WindowProviderProps extends PropsWithChildren {
* @required
*/
window: Window

/**
* injected document
* @required
*/
document: Document
}

/**
* A Provider that provides window and document object
* you can use this provider to inject an external window
*/
function WindowProvider({ window, document, children }: WindowProviderProps) {
export function WindowProvider({ window, children }: WindowProviderProps) {
const document = window.document
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BezierProvider의 로직을 보면, document가 window.document와 동일하므로 제거

const rootElement = document.body

const value = useMemo(() => ({
window,
document,
rootElement,
}), [
document,
window,
document,
rootElement,
])

return (
<WindowContextProvider value={value}>{ children }</WindowContextProvider>
)
}

export default WindowProvider
14 changes: 13 additions & 1 deletion packages/bezier-react/src/styles/_base.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
html {
:where(:root, :host) {
font-size: 62.5%; // 10/16 = 0.625. Make REM calculations easier.
font-family: var(--font-family-sans-kr);
color: var(--txt-black-darkest);
color-scheme: light;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

누락된 기본 텍스트 컬러 추가

}

// TODO: Change data attribute constant to import from bezier-tokens
[data-bezier-theme='light'] {
color-scheme: light;
}

// TODO: Change data attribute constant to import from bezier-tokens
[data-bezier-theme='dark'] {
color-scheme: dark;
}

:lang(ja) {
Expand Down