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

custom theme #72

Closed
wants to merge 1 commit into from
Closed
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
File renamed without changes.
3 changes: 2 additions & 1 deletion src/components/ThemeProvider/ThemeProvider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export default {

export const Default = () => {
const { theme, setTheme } = useTheme()

const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
setTheme(theme)
}

return <Button label="Toggle theme" onClick={toggleTheme} />
Expand Down
69 changes: 40 additions & 29 deletions src/components/ThemeProvider/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,36 @@ import {
useState,
} from 'react'

const THEMES = ['dark', 'light'] as const
export interface Color {
r: number
g: number
b: number
}

export type Theme = (typeof THEMES)[number]
export interface Theme {
foreground: Color
background: Color
backgroundBackdrop: Color,
backgroundRaised: Color,
statusPositive: Color
statusNegative: Color
statusWarning: Color
statusInfo: Color
primaryButton: string
}

const DEFAULT_THEME: Theme = {
foreground: { r: 255, g: 255, b: 255 },
background: { r: 0, g: 0, b: 0 },
backgroundBackdrop: { r: 34, g: 34, b: 34 },
backgroundRaised: { r: 54, g: 54, b: 54 },
statusPositive: { r: 31, g: 194, b: 102 },
statusNegative: { r: 194, g: 80, b: 31 },
statusWarning: { r: 244, g: 176, b: 62 },
statusInfo: { r: 0, g: 118, b: 204 },
primaryButton: 'linear-gradient(89.69deg, #4411E1 0.27%, #7537F9 99.73%)'
}

const DEFAULT_THEME = 'dark'
const THEME_ATTR = 'data-theme'
const STORAGE_KEY = '@sequence.theme'

interface ThemeContextValue {
Expand All @@ -25,18 +49,13 @@ interface ThemeProviderProps {
}

const getTheme = (): Theme => {
const persistedTheme = localStorage.getItem(STORAGE_KEY) as Theme
const persistedThemeRaw = localStorage.getItem(STORAGE_KEY)

if (THEMES.includes(persistedTheme)) {
if (persistedThemeRaw) {
const persistedTheme = JSON.parse(persistedThemeRaw) as Theme
return persistedTheme
}

// else if (matchMedia(`(prefers-color-scheme: light)`).matches) {
// return 'light'
// } else if (matchMedia(`(prefers-color-scheme: dark)`).matches) {
// return 'dark'
// }

return DEFAULT_THEME
}

Expand All @@ -45,6 +64,7 @@ const ThemeContext = createContext<ThemeContextValue | null>(null)
export const ThemeProvider = (props: PropsWithChildren<ThemeProviderProps>) => {
const [theme, setTheme] = useState<Theme>(props.theme || getTheme())


useEffect(() => {
// Add is-apple class
;/Mac/.test(window.navigator.userAgent) &&
Expand All @@ -53,32 +73,23 @@ export const ThemeProvider = (props: PropsWithChildren<ThemeProviderProps>) => {

// Allow prop theme override
useEffect(() => {
if (props.theme && THEMES.includes(props.theme)) {
if (props.theme) {
setTheme(props.theme)
}
}, [props.theme])

// Set the data-theme attribtute on the document root element
useEffect(() => {
const root = document.querySelector(':root')

if (root) {
root.setAttribute(THEME_ATTR, theme)
}
}, [theme])

// Create the context value
const value: ThemeContextValue = useMemo(() => {
return {
theme,
setTheme: (mode: Theme) => {
if (THEMES.includes(mode)) {
// Save to local storage
localStorage.setItem(STORAGE_KEY, mode)

// Set the theme state which will cause a re-render
setTheme(mode)
}
setTheme: (newTheme: Theme) => {
const themeString = JSON.stringify(newTheme)
// Save to local storage
localStorage.setItem(STORAGE_KEY, themeString)

// Set the theme state which will cause a re-render
setTheme(newTheme)
},
}
}, [theme])
Expand Down
1 change: 1 addition & 0 deletions src/components/ThemeProvider/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { ThemeProvider, useTheme } from './ThemeProvider'
export type { Theme, Color } from './ThemeProvider'
10 changes: 5 additions & 5 deletions src/css/vars.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
createGlobalThemeContract,
} from '@vanilla-extract/css'

import { Theme } from '~/components/ThemeProvider'
import { capitalize } from '~/helpers'
import { ColorScheme, tokens } from '~/tokens'
import { getColors, getTokens, NetworkColors } from '~/tokens'

import { mapVarName } from './utils'
import { getColorSchemes } from '~/tokens/color'

type MapTokens<P extends string, T> = {
[K in keyof T & string as `${P}${Capitalize<K>}`]: string
Expand All @@ -21,8 +23,6 @@
}, {}) as MapTokens<P, T>
}

type NetworkColors = typeof tokens.colors.network

const mapNetworkColors = <
T extends NetworkColors,
K extends keyof NetworkColors,
Expand All @@ -37,14 +37,14 @@
)
}

const { colors, ...baseTokens } = tokens

Check failure on line 40 in src/css/vars.css.ts

View workflow job for this annotation

GitHub Actions / Test

Cannot find name 'tokens'.

export const baseVars = createGlobalThemeContract(baseTokens, mapVarName)

createGlobalTheme(':root', baseVars, baseTokens)

const makeColorScheme = (mode: ColorScheme = 'light') => {
const colorSchemeTokens = colors.colorSchemes[mode]
const makeColorScheme = (theme: Theme) => {
const colorSchemeTokens = getColorSchemes(theme)

return {
colors: {
Expand All @@ -61,11 +61,11 @@
}

export const colorSchemeVars = createGlobalThemeContract(
makeColorScheme(),

Check failure on line 64 in src/css/vars.css.ts

View workflow job for this annotation

GitHub Actions / Test

Expected 1 arguments, but got 0.
mapVarName
)

for (const colorScheme of Object.keys(colors.colorSchemes) as ColorScheme[]) {

Check failure on line 68 in src/css/vars.css.ts

View workflow job for this annotation

GitHub Actions / Test

Cannot find name 'ColorScheme'.
createGlobalTheme(
`[data-theme="${colorScheme}"]`,
colorSchemeVars,
Expand Down
171 changes: 73 additions & 98 deletions src/tokens/color.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Theme, Color } from '~/components/ThemeProvider'

export type NetworkColors = typeof networkColors

export interface ColorTokens {
base: BaseColors
context: ContextColors
network: typeof networkColors
network: NetworkColors
gradient: Gradients
colorSchemes: ColorSchemes
colorSchemes: ColorSchemeTokens
}

export type ColorScheme = 'dark' | 'light'

interface ColorSchemeTokens {
background: BackgroundColors
border: BorderColors
Expand All @@ -17,8 +19,6 @@ interface ColorSchemeTokens {
// context: ContextColors // ContextColors are global and not color scheme specific
}

type ColorSchemes<T = ColorSchemeTokens> = { [key in ColorScheme]: T }

interface BaseColors {
black: string
white: string
Expand Down Expand Up @@ -69,68 +69,46 @@ interface Gradients {
secondary: string
}

const backgroundColors: ColorSchemes<BackgroundColors> = {
dark: {
primary: 'rgba(0, 0, 0, 1)',
secondary: 'rgba(255, 255, 255, 0.1)',
contrast: 'rgba(0, 0, 0, 0.5)',
muted: 'rgba(255, 255, 255, 0.05)',
control: 'rgba(255, 255, 255, 0.25)',
inverse: 'rgba(255, 255, 255, 1)',
backdrop: 'rgba(34, 34, 34, 0.9)',
overlay: 'rgba(0, 0, 0, 0.7)',
raised: 'rgba(54, 54, 54, 0.7)',
},
light: {
primary: 'rgba(244, 244, 244, 1)',
secondary: 'rgba(0, 0, 0, 0.1)',
contrast: 'rgba(244, 244, 244, 0.5)',
muted: 'rgba(0, 0, 0, 0.05)',
control: 'rgba(0, 0, 0, 0.25)',
inverse: 'rgba(0, 0, 0, 1)',
backdrop: 'rgba(221, 221, 221, 0.9)',
overlay: 'rgba(244, 244, 244, 0.7)',
raised: 'rgba(192, 192, 192, 0.7)',
},
const makeRGBA = (color: Color, alpha: number) => {
return `rgba(${color.r},${color.g},${color.b},${alpha})`
}

const borderColors: ColorSchemes<BorderColors> = {
dark: {
normal: 'rgba(255, 255, 255, 0.25)',
focus: 'rgba(255, 255, 255, 0.5)',
},
light: {
normal: 'rgba(0, 0, 0, 0.25)',
focus: 'rgba(0, 0, 0, 0.5)',
},
const getBackgroundColors = (theme: Theme): BackgroundColors => {
return ({
primary: makeRGBA(theme.background, 1),
secondary: makeRGBA(theme.foreground, 1),
contrast: makeRGBA(theme.background, 0.5),
muted: makeRGBA(theme.foreground, 0.05),
control: makeRGBA(theme.foreground, 0.25),
inverse: makeRGBA(theme.foreground, 1),
backdrop: makeRGBA(theme.backgroundBackdrop, 0.9),
overlay: makeRGBA(theme.background, 0.7),
raised: makeRGBA(theme.backgroundRaised, 0.7),
})
}

const buttonColors: ColorSchemes<ButtonColors> = {
dark: {
glass: 'rgba(255, 255, 255, 0.15)',
emphasis: 'rgba(0, 0, 0, 0.5)',
inverse: 'rgba(255, 255, 255, 0.8)',
},
light: {
glass: 'rgba(0, 0, 0, 0.15)',
emphasis: 'rgba(255, 255, 255, 0.5)',
inverse: 'rgba(0, 0, 0, 0.8)',
},
const getBorderColors = (theme: Theme): BorderColors => {
return ({
normal: makeRGBA(theme.foreground, 0.25),
focus: makeRGBA(theme.foreground, 0.5),
})
}

const textColors: ColorSchemes<TextColors> = {
dark: {
'100': 'rgba(255, 255, 255, 1)',
'80': 'rgba(255, 255, 255, 0.8)',
'50': 'rgba(255, 255, 255, 0.5)',
inverse100: 'rgba(0, 0, 0, 1)',
},
light: {
'100': 'rgba(0, 0, 0, 1)',
'80': 'rgba(0, 0, 0, 0.8)',
'50': 'rgba(0, 0, 0, 0.5)',
inverse100: 'rgba(255, 255, 255, 1)',
},
const getButtonColors = (theme: Theme): ButtonColors => {
return ({
glass: makeRGBA(theme.foreground, 0.15),
emphasis: makeRGBA(theme.background, 0.5),
inverse: makeRGBA(theme.foreground, 0.8),
})
}

const getTextColors = (theme: Theme): TextColors => {
return ({
'100': makeRGBA(theme.foreground, 1),
'80': makeRGBA(theme.foreground, 0.8),
'50': makeRGBA(theme.foreground, 0.5),
inverse100: makeRGBA(theme.background, 1),
})
}

// ContextColors are global and not color scheme specific
Expand All @@ -141,16 +119,17 @@ const contextColors: ContextColors = {
warning: '#F4B03E',
}

// Gradients are global and not color scheme specific
const gradients: Gradients = {
backdrop: `linear-gradient(
243.18deg,
rgba(86, 52, 189, 0.85) 0%,
rgba(49, 41, 223, 0.85) 63.54%,
rgba(7, 98, 149, 0.85) 100%
)`,
primary: `linear-gradient(89.69deg, #4411E1 0.27%, #7537F9 99.73%)`,
secondary: `linear-gradient(32.51deg, #951990 -15.23%, #3A35B1 48.55%, #20A8B0 100%)`,
const getGradients = (theme: Theme): Gradients => {
return ({
backdrop: `linear-gradient(
243.18deg,
rgba(86, 52, 189, 0.85) 0%,
rgba(49, 41, 223, 0.85) 63.54%,
rgba(7, 98, 149, 0.85) 100%
)`,
primary: theme.primaryButton,
secondary: `linear-gradient(32.51deg, #951990 -15.23%, #3A35B1 48.55%, #20A8B0 100%)`,
})
}

const networkColors = {
Expand Down Expand Up @@ -180,30 +159,26 @@ const networkColors = {
},
}

const colorSchemes: ColorSchemes = {
dark: {
background: backgroundColors.dark,
border: borderColors.dark,
button: buttonColors.dark,
text: textColors.dark,
},
light: {
background: backgroundColors.light,
border: borderColors.light,
button: buttonColors.light,
text: textColors.light,
},
}

export const colors: ColorTokens = {
base: {
black: '#000000',
white: '#ffffff',
inherit: 'inherit',
transparent: 'transparent',
},
context: contextColors,
gradient: gradients,
network: networkColors,
colorSchemes,
export const getColorSchemes = (theme: Theme): ColorSchemeTokens => {
return ({
background: getBackgroundColors(theme),
border: getBorderColors(theme),
button: getButtonColors(theme),
text: getTextColors(theme),
})
}

export const getColors = (theme: Theme): ColorTokens => {
return ({
base: {
black: '#000000',
white: '#ffffff',
inherit: 'inherit',
transparent: 'transparent',
},
context: contextColors,
gradient: getGradients(theme),
network: networkColors,
colorSchemes: getColorSchemes(theme),
})
}
Loading
Loading