refactor(themes): refactor theme module to keep multiple themes (#440)

* refactor(themes): refactor theme module to keep multiple themes

* chore: migrate APIs to be compatible with new theme system

* test: update snapshots

* chore: migrate the path of the theme module

* feat(themes): append static methods of themes

* chore: hide custom theme when no custom content in the context

* chore: manually add flush to preload styles in html

* docs(themes): update to fit the new theme system
This commit is contained in:
witt
2021-02-03 23:11:35 +08:00
committed by GitHub
parent 87a1735206
commit 5c02bcf0e1
64 changed files with 396 additions and 501 deletions

View File

@@ -18,8 +18,10 @@ module.exports = {
'components/**/*.{ts,tsx}',
'!components/**/styles.{ts,tsx}',
'!components/**/*types.{ts,tsx}',
'!components/styles/*',
'!components/use-theme/*',
'!components/use-all-themes/*',
'!components/themes/*',
'!components/geist-provider/*',
'!components/index.ts',
'!components/utils/*',
],

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import { NormalSizes, NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
import BadgeAnchor from './badge-anchor'
interface Props {

View File

@@ -1,4 +1,4 @@
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
import { NormalTypes } from '../utils/prop-types'
type ButtonDropdownColors = {

View File

@@ -3,7 +3,7 @@ import useTheme from '../use-theme'
import withDefaults from '../utils/with-defaults'
import { NormalSizes, ButtonTypes } from '../utils/prop-types'
import { ButtonGroupContext, ButtonGroupConfig } from './button-group-context'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
interface Props {
disabled?: boolean

View File

@@ -1,4 +1,4 @@
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
import { NormalSizes, ButtonTypes } from '../utils/prop-types'
import { ButtonProps } from './button'
import { addColorAlpha } from '../utils/color'

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { useProportions } from '../utils/calculations'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
interface Props {
value?: number

View File

@@ -1,5 +1,5 @@
import { CardTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
export type CardStyles = {
color: string

View File

@@ -14,7 +14,7 @@ describe('CSSBaseline', () => {
it('should render dark mode correctly', () => {
const wrapper = render(
<GeistProvider theme={{ type: 'dark' }}>
<GeistProvider themeType="dark">
<CssBaseline />
</GeistProvider>,
)

View File

@@ -3,7 +3,7 @@ import useTheme from '../use-theme'
import withDefaults from '../utils/with-defaults'
import { DividerAlign, SnippetTypes } from '../utils/prop-types'
import { getMargin } from '../spacer/spacer'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
export type DividerTypes = SnippetTypes

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemes } from '../styles/themes'
import { GeistUIThemes } from '../themes/presets'
interface Props {
type?: NormalTypes

View File

@@ -4,16 +4,17 @@ import {
GeistUIContextParams,
UpdateToastsFunction,
} from '../utils/use-geist-ui-context'
import ThemeProvider from '../styles/theme-provider'
import { ThemeParam } from '../styles/theme-provider/theme-provider'
import ThemeProvider from './theme-provider'
import useCurrentState from '../utils/use-current-state'
import ToastContainer, { ToastWithID } from '../use-toasts/toast-container'
import { GeistUIThemes } from '../themes/presets'
export interface Props {
theme?: ThemeParam
themes?: Array<GeistUIThemes>
themeType?: string | 'dark' | 'light'
}
const GeistProvider: React.FC<PropsWithChildren<Props>> = ({ theme, children }) => {
const GeistProvider: React.FC<PropsWithChildren<Props>> = ({ themes, themeType, children }) => {
const [toasts, setToasts, toastsRef] = useCurrentState<Array<ToastWithID>>([])
const [toastHovering, setToastHovering] = useState<boolean>(false)
const updateToasts: UpdateToastsFunction<ToastWithID> = (
@@ -40,7 +41,7 @@ const GeistProvider: React.FC<PropsWithChildren<Props>> = ({ theme, children })
return (
<GeistUIContent.Provider value={initialValue}>
<ThemeProvider theme={theme}>
<ThemeProvider themes={themes} themeType={themeType}>
{children}
<ToastContainer />
</ThemeProvider>

View File

@@ -0,0 +1,44 @@
import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react'
import Themes from '../themes'
import { GeistUIThemes } from '../themes/presets'
import { ThemeContext } from '../use-theme/theme-context'
import { AllThemesConfig, AllThemesContext } from '../use-all-themes/all-themes-context'
export interface Props {
themeType?: string
themes?: Array<GeistUIThemes>
}
const ThemeProvider: React.FC<PropsWithChildren<Props>> = ({
children,
themeType,
themes = [],
}) => {
const [allThemes, setAllThemes] = useState<AllThemesConfig>({ themes: Themes.getPresets() })
const currentTheme = useMemo<GeistUIThemes>(() => {
const theme = allThemes.themes.find(item => item.type === themeType)
if (theme) return theme
return Themes.getPresetStaticTheme()
}, [allThemes, themeType])
useEffect(() => {
if (!themes?.length) return
setAllThemes(last => {
const safeThemes = themes.filter(item => Themes.isAvailableThemeType(item.type))
const nextThemes = Themes.getPresets().concat(safeThemes)
return {
...last,
themes: nextThemes,
}
})
}, [themes])
return (
<AllThemesContext.Provider value={allThemes}>
<ThemeContext.Provider value={currentTheme}>{children}</ThemeContext.Provider>
</AllThemesContext.Provider>
)
}
export default ThemeProvider

View File

@@ -1,4 +1,4 @@
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
export type BrowserColors = {
color: string

View File

@@ -1,7 +1,8 @@
/// <reference types="styled-jsx" />
export * from './styles/themes'
export * from './themes/presets'
export { default as Themes } from './themes'
export { default as useTheme } from './use-theme'
export { default as useAllThemes } from './use-all-themes'
export { default as GeistProvider } from './geist-provider'
export { default as CssBaseline } from './css-baseline'
export { default as useToasts } from './use-toasts'

View File

@@ -1,5 +1,5 @@
import { NormalSizes, NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
export type InputSize = {
heightRatio: string

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { GeistUIThemes } from '../styles/themes'
import { GeistUIThemes } from '../themes/presets'
interface Props {
command?: boolean

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import withDefaults from '../utils/with-defaults'
import { NormalSizes, NormalTypes } from 'components/utils/prop-types'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
interface Props {
size?: NormalSizes

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import withDefaults from '../utils/with-defaults'
import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemes } from '../styles/themes'
import { GeistUIThemes } from '../themes/presets'
interface Props {
type?: NormalTypes

View File

@@ -48,7 +48,7 @@ describe('Page', () => {
it('should disable dot style when in dark mode', () => {
const wrapper = mount(
<GeistProvider theme={{ type: 'dark' }}>
<GeistProvider themeType="dark">
<Page dotBackdrop />
</GeistProvider>,
)

View File

@@ -1,6 +1,6 @@
import { PageSize } from './page'
import { NormalSizes } from '../utils/prop-types'
import { GeistUIThemesLayout } from '../styles/themes'
import { GeistUIThemesLayout } from '../themes/presets'
export const getPageSize = (size: PageSize, layout: GeistUIThemesLayout): string => {
const presets: { [key in NormalSizes]: string } = {

View File

@@ -2,7 +2,7 @@ import React from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { useProportions } from '../utils/calculations'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
import { NormalTypes } from 'components/utils/prop-types'
export type ProgressColors = {

View File

@@ -1,5 +1,5 @@
import { NormalSizes } from 'components/utils/prop-types'
import { GeistUIThemes } from 'components/styles/themes'
import { GeistUIThemes } from 'components/themes/presets'
export interface SelectSize {
height: string

View File

@@ -1,5 +1,5 @@
import { SnippetTypes } from 'components/utils/prop-types'
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
export type SnippetStyles = {
color: string

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { NormalSizes } from '../utils/prop-types'
import { GeistUIThemes } from '../styles/themes'
import { GeistUIThemes } from '../themes/presets'
interface Props {
size?: NormalSizes

View File

@@ -1,210 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ThemeProvider should merge themes with custom function 1`] = `
initialize {
"0": Node {
"attribs": Object {
"class": " ",
},
"children": Array [
Node {
"data": "hello",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "p",
"namespace": "http://www.w3.org/1999/xhtml",
"next": Node {
"attribs": Object {},
"children": Array [
Node {
"data": "
p {
color: #ccc;
}
.custom-size {
font-size: inherit;
}
",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "style",
"namespace": "http://www.w3.org/1999/xhtml",
"next": null,
"parent": Node {
"children": Array [
[Circular],
[Circular],
],
"name": "root",
"next": null,
"parent": null,
"prev": null,
"type": "root",
},
"prev": [Circular],
"type": "style",
"x-attribsNamespace": Object {},
"x-attribsPrefix": Object {},
},
"parent": Node {
"children": Array [
[Circular],
Node {
"attribs": Object {},
"children": Array [
Node {
"data": "
p {
color: #ccc;
}
.custom-size {
font-size: inherit;
}
",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "style",
"namespace": "http://www.w3.org/1999/xhtml",
"next": null,
"parent": [Circular],
"prev": [Circular],
"type": "style",
"x-attribsNamespace": Object {},
"x-attribsPrefix": Object {},
},
],
"name": "root",
"next": null,
"parent": null,
"prev": null,
"type": "root",
},
"prev": null,
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
},
},
"1": Node {
"attribs": Object {},
"children": Array [
Node {
"data": "
p {
color: #ccc;
}
.custom-size {
font-size: inherit;
}
",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "style",
"namespace": "http://www.w3.org/1999/xhtml",
"next": null,
"parent": Node {
"children": Array [
Node {
"attribs": Object {
"class": " ",
},
"children": Array [
Node {
"data": "hello",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "p",
"namespace": "http://www.w3.org/1999/xhtml",
"next": [Circular],
"parent": [Circular],
"prev": null,
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
},
},
[Circular],
],
"name": "root",
"next": null,
"parent": null,
"prev": null,
"type": "root",
},
"prev": Node {
"attribs": Object {
"class": " ",
},
"children": Array [
Node {
"data": "hello",
"next": null,
"parent": [Circular],
"prev": null,
"type": "text",
},
],
"name": "p",
"namespace": "http://www.w3.org/1999/xhtml",
"next": [Circular],
"parent": Node {
"children": Array [
[Circular],
[Circular],
],
"name": "root",
"next": null,
"parent": null,
"prev": null,
"type": "root",
},
"prev": null,
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
},
},
"type": "style",
"x-attribsNamespace": Object {},
"x-attribsPrefix": Object {},
},
"_root": [Circular],
"length": 2,
"options": Object {
"decodeEntities": true,
"xml": false,
},
}
`;

View File

@@ -1,74 +0,0 @@
import React from 'react'
import { render, mount } from 'enzyme'
import { deepMergeObject } from '../theme-provider/theme-provider'
import DefaultThemes from '../themes/default'
import { GeistProvider, GeistUIThemes, Text } from 'components'
import { DeepPartial } from 'components/utils/types'
describe('ThemeProvider', () => {
it('should deep merge objects but not add new key', () => {
const sourceObject: any = {
palette: {
success: '#000',
},
arr: ['array', 10],
font: {
subFont: {
key: 'font',
},
},
}
const targetObject: any = {
palette: {
warning: '#000',
success: '#ccc',
},
arr: [5],
font: {
first: 'first',
},
}
expect(deepMergeObject(sourceObject, targetObject)).toMatchObject({
palette: {
success: '#ccc',
},
arr: [5, 'array', 10],
font: {
subFont: {
key: 'font',
},
},
})
})
it('should merge themes with custom function', () => {
const customFunc = (): DeepPartial<GeistUIThemes> => {
return {
...DefaultThemes,
palette: {
success: '#ccc',
},
}
}
const wrapper = render(
<GeistProvider theme={customFunc}>
<Text type="success">hello</Text>
</GeistProvider>,
)
expect(wrapper).toMatchSnapshot()
})
it('should warning when using the wrong merge function', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
const customFunc = () => 0 as DeepPartial<GeistUIThemes>
const wrapper = mount(
<GeistProvider theme={customFunc}>
<p>test</p>
</GeistProvider>,
)
expect(errorSpy).toHaveBeenCalled()
errorSpy.mockRestore()
wrapper.unmount()
})
})

View File

@@ -1 +0,0 @@
export { default } from './theme-provider'

View File

@@ -1,74 +0,0 @@
import React, { PropsWithChildren } from 'react'
import useTheme from '../../use-theme'
import darkTheme from '../themes/dark'
import lightTheme from '../themes/default'
import { GeistUIThemes } from '../themes/index'
import ThemeContext from '../../use-theme/theme-context'
import useWarning from '../../utils/use-warning'
import { DeepPartial } from '../../utils/types'
type PartialTheme = DeepPartial<GeistUIThemes>
export type ThemeParam = PartialTheme | ((theme: PartialTheme) => PartialTheme) | undefined
export interface Props {
theme?: ThemeParam
}
export interface MergeObject {
[key: string]: any
}
export const isObject = (target: any) => target && typeof target === 'object'
export const deepMergeObject = <T extends MergeObject>(source: T, target: T): T => {
if (!isObject(target) || !isObject(source)) return source
const sourceKeys = Object.keys(source) as Array<keyof T>
let result = {} as T
for (const key of sourceKeys) {
const sourceValue = source[key]
const targetValue = target[key]
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
result[key] = targetValue.concat(sourceValue)
} else if (isObject(sourceValue) && isObject(targetValue)) {
result[key] = deepMergeObject(sourceValue, { ...targetValue })
} else if (targetValue) {
result[key] = targetValue
} else {
result[key] = sourceValue
}
}
return result
}
const mergeTheme = (current: GeistUIThemes, custom: ThemeParam): GeistUIThemes => {
if (!custom) return current
if (typeof custom === 'function') {
const merged = custom(current)
if (!merged || typeof merged !== 'object') {
useWarning('The theme function must return object value.')
}
return merged as GeistUIThemes
}
return deepMergeObject<GeistUIThemes>(current, custom as GeistUIThemes)
}
const switchTheme = (mergedTheme: GeistUIThemes): GeistUIThemes => {
const themes: { [key in GeistUIThemes['type']]: GeistUIThemes } = {
light: lightTheme,
dark: darkTheme,
}
return { ...mergedTheme, ...themes[mergedTheme.type] }
}
const ThemeProvider: React.FC<PropsWithChildren<Props>> = ({ children, theme }) => {
const customTheme = theme
const currentTheme = useTheme()
const merged = mergeTheme(currentTheme, customTheme)
const userTheme = currentTheme.type !== merged.type ? switchTheme(merged) : merged
return <ThemeContext.Provider value={userTheme}>{children}</ThemeContext.Provider>
}
export default ThemeProvider

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { SnippetTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
interface Props {
type?: SnippetTypes

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../use-theme'
import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
export interface Props {
tag: keyof JSX.IntrinsicElements

View File

@@ -0,0 +1,3 @@
import Themes from './themes'
export default Themes

View File

@@ -1,5 +1,3 @@
import { ThemeTypes } from '../../utils/prop-types'
export interface GeistUIThemesPalette {
accents_1: string
accents_2: string
@@ -87,7 +85,7 @@ export interface GeistUIThemesBreakpoints {
}
export interface GeistUIThemes {
type: ThemeTypes
type: string
font: GeistUIThemesFont
layout: GeistUIThemesLayout
palette: GeistUIThemesPalette

View File

@@ -1,8 +1,4 @@
import {
GeistUIThemesBreakpoints,
GeistUIThemesFont,
GeistUIThemesLayout,
} from 'components/styles/themes/index'
import { GeistUIThemesBreakpoints, GeistUIThemesFont, GeistUIThemesLayout } from './index'
export const defaultFont: GeistUIThemesFont = {
sans:

View File

@@ -0,0 +1,84 @@
import { GeistUIThemes } from './presets/index'
import { DeepPartial } from '../utils/types'
import lightTheme from './presets/default'
import darkTheme from './presets/dark'
export type GeistUserTheme = DeepPartial<GeistUIThemes> & { type: string }
export const isObject = (target: unknown) => target && typeof target === 'object'
export const deepDuplicable = <T extends Record<string, unknown>>(source: T, target: T): T => {
if (!isObject(target) || !isObject(source)) return source as T
const sourceKeys = Object.keys(source) as Array<keyof T>
let result = {} as any
for (const key of sourceKeys) {
const sourceValue = source[key]
const targetValue = target[key]
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
result[key] = targetValue.concat(sourceValue)
} else if (isObject(sourceValue) && isObject(targetValue)) {
result[key] = deepDuplicable(sourceValue as Record<string, unknown>, {
...(targetValue as Record<string, unknown>),
})
} else if (targetValue) {
result[key] = targetValue
} else {
result[key] = sourceValue
}
}
return result
}
const getPresets = (): Array<GeistUIThemes> => {
return [lightTheme, darkTheme]
}
const getPresetStaticTheme = (): GeistUIThemes => {
return lightTheme
}
const isAvailableThemeType = (type?: string): boolean => {
if (!type) return false
const presetThemes = getPresets()
const hasType = presetThemes.find(theme => theme.type === type)
return !hasType
}
const isPresetTheme = (themeOrType?: GeistUserTheme | GeistUIThemes | string): boolean => {
if (!themeOrType) return false
const isType = typeof themeOrType === 'string'
const type = isType
? (themeOrType as string)
: (themeOrType as Exclude<typeof themeOrType, string>).type
return !isAvailableThemeType(type)
}
const hasUserCustomTheme = (themes: Array<GeistUIThemes> = []): boolean => {
return !!themes.find(item => isAvailableThemeType(item.type))
}
const create = (base: GeistUIThemes, custom: GeistUserTheme): GeistUIThemes => {
if (!isAvailableThemeType(custom.type)) {
throw new Error('Duplicate or unavailable theme type')
}
return deepDuplicable(base, custom) as GeistUIThemes
}
const createFromDark = (custom: GeistUserTheme) => create(darkTheme, custom)
const createFromLight = (custom: GeistUserTheme) => create(lightTheme, custom)
const Themes = {
isPresetTheme,
isAvailableThemeType,
hasUserCustomTheme,
getPresets,
getPresetStaticTheme,
create,
createFromDark,
createFromLight,
}
export default Themes

View File

@@ -15,7 +15,7 @@ const expectTooltipIsHidden = (wrapper: ReactWrapper) => {
describe('Tooltip', () => {
it('should render correctly', async () => {
const wrapper = mount(
<GeistProvider theme={{ type: 'dark' }}>
<GeistProvider themeType="dark">
<Tooltip text={<p id="test">custom-content</p>}>some tips</Tooltip>
</GeistProvider>,
)

View File

@@ -1,5 +1,5 @@
import { SnippetTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
export type TooltipColors = {
bgColor: string

View File

@@ -0,0 +1,18 @@
import React from 'react'
import Themes from '../themes/themes'
import { GeistUIThemes } from '../themes/presets'
export type AllThemesConfig = {
themes: Array<GeistUIThemes>
}
const defaultAllThemesConfig = {
themes: Themes.getPresets(),
}
export const AllThemesContext: React.Context<AllThemesConfig> = React.createContext<
AllThemesConfig
>(defaultAllThemesConfig)
export const useAllThemes = (): AllThemesConfig =>
React.useContext<AllThemesConfig>(AllThemesContext)

View File

@@ -0,0 +1,3 @@
import { useAllThemes } from './all-themes-context'
export default useAllThemes

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import useTheme from '../use-theme'
import { tuple } from '../utils/prop-types'
import { BreakpointsItem, GeistUIThemesBreakpoints } from '../styles/themes'
import { BreakpointsItem, GeistUIThemesBreakpoints } from '../themes/presets'
const breakpoints = tuple('xs', 'sm', 'md', 'lg', 'xl', 'mobile')
export type ResponsiveBreakpoint = typeof breakpoints[number]

View File

@@ -1 +1,3 @@
export { default } from './use-theme'
import { useTheme } from './theme-context'
export default useTheme

View File

@@ -1,7 +1,10 @@
import React from 'react'
import { GeistUIThemes } from '../styles/themes/index'
import defaultTheme from '../styles/themes/default'
import Themes from '../themes'
import { GeistUIThemes } from '../themes/presets/index'
const ThemeContext: React.Context<GeistUIThemes> = React.createContext<GeistUIThemes>(defaultTheme)
const defaultTheme = Themes.getPresetStaticTheme()
export default ThemeContext
export const ThemeContext: React.Context<GeistUIThemes> = React.createContext<GeistUIThemes>(
defaultTheme,
)
export const useTheme = (): GeistUIThemes => React.useContext<GeistUIThemes>(ThemeContext)

View File

@@ -1,7 +0,0 @@
import React from 'react'
import ThemeContext from './theme-context'
import { GeistUIThemes } from '../styles/themes/index'
const useTheme = (): GeistUIThemes => React.useContext<GeistUIThemes>(ThemeContext)
export default useTheme

View File

@@ -3,7 +3,7 @@ import useTheme from '../use-theme'
import { Toast, ToastAction } from './use-toast'
import Button from '../button'
import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../styles/themes'
import { GeistUIThemesPalette } from '../themes/presets'
type ToastWithID = Toast & {
id: string

View File

@@ -17,8 +17,6 @@ const normalSizes = tuple('mini', 'small', 'medium', 'large')
const normalTypes = tuple('default', 'secondary', 'success', 'warning', 'error')
const themeTypes = tuple('dark', 'light')
const snippetTypes = tuple('default', 'secondary', 'success', 'warning', 'error', 'dark', 'lite')
const cardTypes = tuple(
@@ -62,8 +60,6 @@ export type NormalSizes = typeof normalSizes[number]
export type NormalTypes = typeof normalTypes[number]
export type ThemeTypes = typeof themeTypes[number]
export type SnippetTypes = typeof snippetTypes[number]
export type CardTypes = typeof cardTypes[number]

View File

@@ -2,7 +2,7 @@
* Just customize what you need, deep merge themes by default.
*
* If you are using TypeScript, please use the following type definition.
* If you are using JavaScript, refer to https://github.com/geist-org/react/blob/master/components/styles/themes/default.ts
* If you are using JavaScript, refer to https://github.com/geist-org/react/blob/master/components/themes/presets/index.ts
*/
// import {

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Avatar, Link, Tooltip, useTheme } from 'components'
import { useConfigs } from 'lib/config-context'
const GithubURL = 'https://github.com/geist-org/react/blob/master'
const host = 'https://contributors.geist-ui.dev/api/users'
import { CONTRIBUTORS_URL, GITHUB_URL } from 'lib/constants'
const RepoMasterURL = `${GITHUB_URL}/blob/master`
export interface Contributor {
name: string
@@ -16,7 +16,7 @@ interface Props {
const getContributors = async (path: string): Promise<Array<Contributor>> => {
try {
const response = await fetch(`${host}?path=${path}`)
const response = await fetch(`${CONTRIBUTORS_URL}?path=${path}`)
if (!response.ok || response.status === 204) return []
return response.json()
} catch (e) {
@@ -28,7 +28,7 @@ const Contributors: React.FC<Props> = ({ path }) => {
const theme = useTheme()
const { isChinese } = useConfigs()
const [users, setUsers] = useState<Array<Contributor>>([])
const link = useMemo(() => `${GithubURL}/${path || '/pages'}`, [])
const link = useMemo(() => `${RepoMasterURL}/${path || '/pages'}`, [])
useEffect(() => {
let unmount = false

View File

@@ -1,25 +1,33 @@
import React, { useMemo } from 'react'
import { Button, useTheme, Select, Spacer } from 'components'
import { Button, useTheme, Select, Spacer, Themes, useAllThemes } from 'components'
import { useConfigs } from 'lib/config-context'
import useLocale from 'lib/use-locale'
import Router, { useRouter } from 'next/router'
import MoonIcon from '@geist-ui/react-icons/moon'
import SunIcon from '@geist-ui/react-icons/sun'
import UserIcon from '@geist-ui/react-icons/user'
import {
CHINESE_LANGUAGE_IDENT,
CUSTOM_THEME_TYPE,
ENGLISH_LANGUAGE_IDENT,
GITHUB_URL,
} from 'lib/constants'
const Controls: React.FC<unknown> = React.memo(({}) => {
const Controls: React.FC<unknown> = React.memo(() => {
const theme = useTheme()
const { updateCustomTheme, updateChineseState } = useConfigs()
const { themes } = useAllThemes()
const { switchTheme, updateChineseState } = useConfigs()
const { pathname } = useRouter()
const { locale } = useLocale()
const isChinese = useMemo(() => locale === 'zh-cn', [locale])
const isDark = useMemo(() => theme.type === 'dark', [theme.type])
const isChinese = useMemo(() => locale === CHINESE_LANGUAGE_IDENT, [locale])
const nextLocalePath = useMemo(() => {
const nextLocale = isChinese ? 'en-us' : 'zh-cn'
const nextLocale = isChinese ? ENGLISH_LANGUAGE_IDENT : CHINESE_LANGUAGE_IDENT
return pathname.replace(locale, nextLocale)
}, [locale, pathname])
const hasCustomTheme = useMemo(() => Themes.hasUserCustomTheme(themes), [themes])
const switchThemes = (type: 'dark' | 'light') => {
updateCustomTheme({ type })
const switchThemes = (type: string) => {
switchTheme(type)
if (typeof window === 'undefined' || !window.localStorage) return
window.localStorage.setItem('theme', type)
}
@@ -28,9 +36,8 @@ const Controls: React.FC<unknown> = React.memo(({}) => {
Router.push(nextLocalePath)
}
const redirectGithub = () => {
if (typeof window !== 'undefined') {
window.open('https://github.com/geist-org/react')
}
if (typeof window === 'undefined') return
window.open(GITHUB_URL)
}
return (
@@ -53,7 +60,7 @@ const Controls: React.FC<unknown> = React.memo(({}) => {
size="small"
pure
onChange={switchThemes}
value={isDark ? 'dark' : 'light'}
value={theme.type}
title={isChinese ? '切换主题' : 'Switch Themes'}>
<Select.Option value="light">
<span className="select-content">
@@ -65,6 +72,13 @@ const Controls: React.FC<unknown> = React.memo(({}) => {
<MoonIcon size={14} /> {isChinese ? '暗黑' : 'Dark'}
</span>
</Select.Option>
{hasCustomTheme && (
<Select.Option value={CUSTOM_THEME_TYPE}>
<span className="select-content">
<UserIcon size={14} /> {CUSTOM_THEME_TYPE}
</span>
</Select.Option>
)}
</Select>
</div>
<style jsx>{`

View File

@@ -1,14 +1,13 @@
import React, { useMemo } from 'react'
import { Text, Spacer, useTheme, Code, useToasts } from 'components'
import DefaultTheme from 'components/styles/themes/default'
import { isObject, MergeObject } from 'components/styles/theme-provider/theme-provider'
import { isObject } from 'components/themes/themes'
import { LiveEditor, LiveProvider } from 'react-live'
import makeCodeTheme from 'lib/components/playground/code-theme'
import useClipboard from 'components/utils/use-clipboard'
import CopyIcon from 'components/snippet/snippet-icon'
import { useConfigs } from 'lib/config-context'
import { CUSTOM_THEME_TYPE } from 'lib/constants'
import CopyIcon from 'components/snippet/snippet-icon'
import makeCodeTheme from 'lib/components/playground/code-theme'
import { Text, Spacer, useTheme, Code, useToasts, Themes, useClipboard } from 'components'
export const getDeepDifferents = <T extends MergeObject>(source: T, target: T): T => {
export const getDeepDifferents = <T,>(source: T, target: T): T => {
if (!isObject(target) || !isObject(source)) return target
const sourceKeys = Object.keys(source) as Array<keyof T>
@@ -29,17 +28,21 @@ export const getDeepDifferents = <T extends MergeObject>(source: T, target: T):
return result
}
const CustomizationCodes = () => {
const CustomizationCodes: React.FC<unknown> = () => {
const DefaultTheme = Themes.getPresetStaticTheme()
const theme = useTheme()
const { isChinese } = useConfigs()
const codeTheme = makeCodeTheme(theme)
const { copy } = useClipboard()
const [, setToast] = useToasts()
const deepDifferents = useMemo(() => getDeepDifferents(DefaultTheme, theme), [
DefaultTheme,
theme,
])
const deepDifferents = useMemo(
() => ({
...getDeepDifferents(DefaultTheme, theme),
type: CUSTOM_THEME_TYPE,
}),
[DefaultTheme, theme],
)
const userCodes = useMemo(() => {
return `const myTheme = ${JSON.stringify(deepDifferents, null, 2)}
@@ -48,7 +51,7 @@ const CustomizationCodes = () => {
*
* export const App = () => {
* return (
* <GeistProvider theme={myTheme}>
* <GeistProvider themes={[myTheme]} themeType="${CUSTOM_THEME_TYPE}">
* <CssBaseline />
* <YourComponent />
* </GeistProvider>

View File

@@ -1,8 +1,8 @@
import React, { useMemo } from 'react'
import { useTheme, GeistUIThemesPalette, Popover } from 'components'
import { useTheme, GeistUIThemesPalette, Popover, Themes } from 'components'
import { ColorResult, TwitterPicker } from 'react-color'
import { useConfigs } from 'lib/config-context'
import DefaultTheme from 'components/styles/themes/default'
const DefaultTheme = Themes.getPresetStaticTheme()
interface Props {
value?: string

View File

@@ -3,13 +3,13 @@ import {
Text,
Button,
useTheme,
Themes,
GeistUIThemesPalette,
GeistUIThemesExpressiveness,
GeistUIThemesLayout,
} from 'components'
import EditorColorItem from './editor-color-item'
import EditorInputItem from './editor-input-item'
import DefaultTheme from 'components/styles/themes/default'
import { useConfigs } from 'lib/config-context'
const basicColors: Array<keyof GeistUIThemesPalette> = [
@@ -71,6 +71,7 @@ const gapLayout: Array<keyof GeistUIThemesLayout> = [
const Editor = () => {
const theme = useTheme()
const DefaultTheme = Themes.getPresetStaticTheme()
const { updateCustomTheme, isChinese } = useConfigs()
const resetLayout = () => updateCustomTheme({ layout: DefaultTheme.layout })

View File

@@ -1,4 +1,4 @@
import { GeistUIThemesPalette } from 'components/styles/themes'
import { GeistUIThemesPalette } from 'components/themes/presets'
export type ColorEnum = {
[key in keyof GeistUIThemesPalette]?: string

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react'
import withDefaults from 'components/utils/with-defaults'
import useTheme from 'components/use-theme'
import { GeistUIThemes } from 'components/styles/themes'
import { GeistUIThemes } from 'components/themes/presets'
interface Props {
plain?: number | boolean

View File

@@ -1,5 +1,5 @@
import { PrismTheme } from 'prism-react-renderer'
import { GeistUIThemes } from 'components/styles/themes'
import { GeistUIThemes } from 'components/themes/presets'
const makeCodeTheme = (theme: GeistUIThemes): PrismTheme => ({
plain: {

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { GeistUIThemes } from 'components/styles/themes'
import { DeepPartial } from 'components/utils/types'
import { GeistUIThemes } from 'components'
export interface Configs {
onThemeChange?: (themes: DeepPartial<GeistUIThemes>) => void
@@ -14,6 +14,7 @@ export interface Configs {
customTheme: DeepPartial<GeistUIThemes>
updateCustomTheme: (theme: DeepPartial<GeistUIThemes>) => void
switchTheme: (type: string) => void
}
export const defaultConfigs: Configs = {
@@ -27,6 +28,7 @@ export const defaultConfigs: Configs = {
customTheme: {},
updateCustomTheme: () => {},
onThemeChange: () => {},
switchTheme: () => {},
}
export const ConfigContext = React.createContext<Configs>(defaultConfigs)

View File

@@ -3,13 +3,13 @@ import withDefaults from 'components/utils/with-defaults'
import { ConfigContext, Configs } from 'lib/config-context'
import { useRouter } from 'next/router'
import { DeepPartial } from 'components/utils/types'
import { GeistUIThemes } from 'components/styles/themes'
import { deepMergeObject } from 'components/styles/theme-provider/theme-provider'
import useCurrentState from 'components/utils/use-current-state'
import { GeistUIThemes, Themes } from 'components'
import { useTheme } from 'components'
import { CHINESE_LANGUAGE_IDENT, CUSTOM_THEME_TYPE } from './constants'
interface Props {
onThemeChange?: (themes: DeepPartial<GeistUIThemes>) => void
onThemeTypeChange?: (type: string) => void
}
const defaultProps = {}
@@ -17,24 +17,27 @@ const defaultProps = {}
export type ConfigProviderProps = Props & typeof defaultProps
const ConfigProvider: React.FC<React.PropsWithChildren<ConfigProviderProps>> = React.memo(
({ onThemeChange, children }) => {
({ onThemeChange, onThemeTypeChange, children }) => {
const theme = useTheme()
const { pathname } = useRouter()
const [isChinese, setIsChinese] = useState<boolean>(() => pathname.includes('zh-cn'))
const [isChinese, setIsChinese] = useState<boolean>(() =>
pathname.includes(CHINESE_LANGUAGE_IDENT),
)
const [scrollHeight, setScrollHeight] = useState<number>(0)
const [tabbarFixed, setTabbarFixed] = useState<boolean>(false)
const [customTheme, setCustomTheme, customThemeRef] = useCurrentState<
DeepPartial<GeistUIThemes>
>(theme)
const [customTheme, setCustomTheme] = useState<GeistUIThemes>(theme)
const updateSidebarScrollHeight = (height: number) => setScrollHeight(height)
const updateChineseState = (state: boolean) => setIsChinese(state)
const updateTabbarFixed = (state: boolean) => setTabbarFixed(state)
const updateCustomTheme = (nextTheme: DeepPartial<GeistUIThemes>) => {
const mergedTheme = deepMergeObject(customThemeRef.current, nextTheme)
const mergedTheme = Themes.create(theme, { ...nextTheme, type: CUSTOM_THEME_TYPE })
setCustomTheme(mergedTheme)
onThemeChange && onThemeChange(mergedTheme)
}
const switchTheme = (type: string) => {
onThemeTypeChange && onThemeTypeChange(type)
}
const initialValue = useMemo<Configs>(
() => ({
@@ -42,6 +45,7 @@ const ConfigProvider: React.FC<React.PropsWithChildren<ConfigProviderProps>> = R
isChinese,
tabbarFixed,
customTheme,
switchTheme,
updateCustomTheme,
updateTabbarFixed,
updateChineseState,

9
lib/constants.ts Normal file
View File

@@ -0,0 +1,9 @@
export const CUSTOM_THEME_TYPE = 'Custom'
export const CHINESE_LANGUAGE_IDENT = 'zh-cn'
export const ENGLISH_LANGUAGE_IDENT = 'en-us'
export const GITHUB_URL = 'https://github.com/geist-org/react'
export const CONTRIBUTORS_URL = 'https://contributors.geist-ui.dev/api/users'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,20 +6,21 @@ import { CssBaseline, GeistProvider, useTheme, GeistUIThemes } from 'components'
import Menu from 'lib/components/menu'
import ConfigContext from 'lib/config-provider'
import useDomClean from 'lib/use-dom-clean'
import { DeepPartial } from 'components/utils/types'
import 'inter-ui/inter.css'
const Application: NextPage<AppProps<{}>> = ({ Component, pageProps }) => {
const theme = useTheme()
const [customTheme, setCustomTheme] = useState<DeepPartial<GeistUIThemes>>({})
const themeChangeHandle = (theme: DeepPartial<GeistUIThemes>) => {
const [themeType, setThemeType] = useState<string>()
const [customTheme, setCustomTheme] = useState<GeistUIThemes>(theme)
const themeChangeHandle = (theme: GeistUIThemes) => {
setCustomTheme(theme)
setThemeType(theme.type)
}
useEffect(() => {
const theme = window.localStorage.getItem('theme')
if (theme !== 'dark') return
themeChangeHandle({ type: 'dark' })
setThemeType('dark')
}, [])
useDomClean()
@@ -62,9 +63,11 @@ const Application: NextPage<AppProps<{}>> = ({ Component, pageProps }) => {
content="initial-scale=1, maximum-scale=1, minimum-scale=1, viewport-fit=cover"
/>
</Head>
<GeistProvider theme={customTheme}>
<GeistProvider themeType={themeType} themes={[customTheme]}>
<CssBaseline />
<ConfigContext onThemeChange={themeChangeHandle}>
<ConfigContext
onThemeChange={themeChangeHandle}
onThemeTypeChange={type => setThemeType(type)}>
<Menu />
<Component {...pageProps} />
</ConfigContext>

View File

@@ -1,6 +1,22 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { CssBaseline } from 'components'
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx)
const styles = CssBaseline.flush()
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{styles}
</>
),
}
}
render() {
return (
<Html>

View File

@@ -8,7 +8,9 @@ export const meta = {
## Themes
`@geist-ui/react` now support **Dark Mode**. You can switch theme at any time through a very simple API, no third-party styles or configs.
**Geist UI** now supports a variety of themes, and it is very easy to create or inherit modifications, no third-party styles or configs.
As a basic option, there are two themes available, `light` and `dark`.
<Spacer y={2} />
@@ -27,20 +29,18 @@ export const meta = {
1. Make sure `GeistProvider` and `CssBaseline` are already on the root component.
2. Get the current theme of the page through hook `useTheme`.
3. Update the value of `theme.type`, and the theme of all components will follow automatically.
2. Update the value of `themeType`, and the theme of all components will follow automatically.
```jsx
import { CssBaseline, GeistProvider } from '@geist-ui/react'
const App = () => {
const [themeType, setThemeType] = useState('dark')
const [themeType, setThemeType] = useState('light')
const switchThemes = () => {
setThemeType(lastThemeType => (lastThemeType === 'dark' ? 'light' : 'dark'))
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
}
return (
<GeistProvider theme={{ type: themeType }}>
<GeistProvider themeType={themeType}>
<CssBaseline />
<YourComponent onClick={switchThemes} />
</GeistProvider>
@@ -52,7 +52,7 @@ const App = () => {
### Customizing theme
Customizing a theme is very simple in `@geist-ui/react`, you just need to provide a new theme `Object`,
Customizing a theme is very simple in Geist UI, you just need to provide a new theme `Object`,
and all the components will change automatically.
Here is <Link target="_blank" color href="https://github.com/geist-org/react/tree/master/examples/custom-themes">a complete sample project</Link> for reference.
@@ -64,22 +64,37 @@ Of course, if a _component_ doesn't use your customized variables, it doesn't ma
</Note>
```jsx
import { CssBaseline, GeistProvider } from '@geist-ui/react'
import { CssBaseline, GeistProvider, Themes } from '@geist-ui/react'
const myTheme = {
const myTheme1 = Themes.createFromLight({
type: 'coolTheme',
palette: {
success: '#000',
},
}
})
const App = () => (
<GeistProvider theme={myTheme}>
<GeistProvider themes={[myTheme1]} themeType="coolTheme">
<CssBaseline />
<YourAppComponent onClick={switchThemes} />
</GeistProvider>
)
```
Function `Themes.createFromLight` allows you to fork a new theme based on Light Theme,
Of course, you can also create a new theme based on the dark style: `Themes.createFromDark`,
Or create a theme based on your own theme:
```jsx
const myBaseTheme = { ... }
const myTheme2 = Themes.create(myBaseTheme, {
type: 'myTheme2',
palette: {
success: '#000',
},
})
```
<Spacer y={3} />
### View all types of Theme definitions
@@ -104,7 +119,7 @@ const myPalette: Partial<GeistUIThemesPalette> = {
}
```
If you don't use TypeScript, to learn more about preset types, see <Link color target="_blank" href="https://github.com/geist-org/react/blob/master/components/styles/themes/index.ts">here</Link>.
If you don't use TypeScript, to learn more about preset types, see <Link color target="_blank" href="https://github.com/geist-org/react/blob/master/components/themes/presets/index.ts">here</Link>.
<Spacer y={3} />
@@ -162,4 +177,19 @@ const MyComponent = () => {
}
```
<Spacer y={2} />
#### Themes APIs
`Themes` contains some static methods that are useful when working with custom themes:
- `Themes.create` - create a new theme object.
- `Themes.createFromDark` - create a new theme object based on Dark Theme.
- `Themes.createFromLight` - create a new theme object based on Light Theme.
- `Themes.isPresetTheme` - Check if a theme is the base of Geist UI.
- `Themes.isAvailableThemeType` - Check if the name of the theme is available.
- `Themes.hasUserCustomTheme` - Check if a list of themes has a custom.
- `Themes.getPresets` - Get a default list of themes.
- `Themes.getPresetStaticTheme` - Get the theme loaded by Geist UI default.
export default ({ children }) => <Layout meta={meta}>{children}</Layout>

View File

@@ -8,8 +8,9 @@ export const meta = {
## 主题
`@geist-ui/react` 现在支持 **暗黑模式**。你可以通过非常简单的 API 随时切换主题,所有的色彩和排版细节都已内置,
不需要任何配置与第三方库。
**Geist UI** 现在支持多种主题,并能以简单易用的方式随意切换或是创建新的主题,不需要任何配置与第三方库。
作为基础的选项Geist UI 内置了两种可用的基础主题,`light` 和 `dark` (亮色和暗色)。
<Spacer y={2} />
@@ -24,24 +25,22 @@ export const meta = {
</Note>
<Spacer y={1} />
**现在你可以根据如下所示步骤切换主题:**
**你可以参考如下步骤切换主题:**
1. 确保 `GeistProvider` 与 `CssBaseline` 已经添加至根节点。
2. (可选的) 通过钩子 `useTheme` 获取所有可用的主题变量
3. 更新 `theme.type` 的值,所有的组件都会随之自动变化。
2. 更新 `themeType` 的值,所有的组件都会随之自动变化
```jsx
import { CssBaseline, GeistProvider } from '@geist-ui/react'
const App = () => {
const [themeType, setThemeType] = useState('dark')
const [themeType, setThemeType] = useState('light')
const switchThemes = () => {
setThemeType(lastThemeType => (lastThemeType === 'dark' ? 'light' : 'dark'))
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
}
return (
<GeistProvider theme={{ type: themeType }}>
<GeistProvider themeType={themeType}>
<CssBaseline />
<YourComponent onClick={switchThemes} />
</GeistProvider>
@@ -53,10 +52,10 @@ const App = () => {
### 自定义主题
自定义主题样式在 `@geist-ui/react`非常简单,你只需要提供一个新的样式对象给 `GeistProvider`,所有的组件都会自然变化以适应你自定义的属性。
自定义主题样式在 Geist UI 中非常简单,你只需要提供一个新的样式对象给 `GeistProvider`,所有的组件都会自然变化以适应你自定义的属性。
这里有 <Link target="_blank" color href="https://github.com/geist-org/react/tree/master/examples/custom-themes">一个完整的示例项目</Link> 可供参考。
当然,如果一个组件未使用到你自定义的变量,它不会发生任何变化也不会重新渲染。
当然,如果一个组件未使用到你自定义的变量,它不会任何变化也不会重新渲染。
<Spacer y={1} />
<Note type="warning">
@@ -64,22 +63,36 @@ const App = () => {
</Note>
```jsx
import { CssBaseline, GeistProvider } from '@geist-ui/react'
import { CssBaseline, GeistProvider, Themes } from '@geist-ui/react'
const myTheme = {
const myTheme1 = Themes.createFromLight({
type: 'coolTheme',
palette: {
success: '#000',
},
}
})
const App = () => (
<GeistProvider theme={myTheme}>
<GeistProvider themes={[myTheme1]} themeType="coolTheme">
<CssBaseline />
<YourAppComponent onClick={switchThemes} />
</GeistProvider>
)
```
方法 `Themes.createFromLight` 允许你在 `light` (亮色主题) 的基础上继承与创建一份新的主题,
当然,你可以以 `dark` (暗色主题) 为基准创建主题:`Themes.createFromDark`,或是以你自己的、来自社区的主题为基础:
```jsx
const myBaseTheme = { ... }
const myTheme2 = Themes.create(myBaseTheme, {
type: 'myTheme2',
palette: {
success: '#000',
},
})
```
<Spacer y={3} />
### 查看主题定义的所有类型
@@ -104,7 +117,7 @@ const myPalette: Partial<GeistUIThemesPalette> = {
}
```
如果你没有使用 TypeScript想要了解更多的预置类型请看<Link color target="_blank" href="https://github.com/geist-org/react/blob/master/components/styles/themes/index.ts">这里的类型声明文件</Link>。
如果你没有使用 TypeScript想要了解更多的预置类型请看<Link color target="_blank" href="https://github.com/geist-org/react/blob/master/components/themes/presets/index.ts">这里的类型声明文件</Link>。
<Spacer y={3} />
@@ -163,4 +176,19 @@ const MyComponent = () => {
}
```
<Spacer y={2} />
#### Themes APIs
`Themes` 包含了一些静态方法 (纯函数),这在你自定义主题时会很有用:
- `Themes.create` - 创建一个新主题。
- `Themes.createFromDark` - 以暗色模式为基础创建新主题。
- `Themes.createFromLight` - 以亮色模式为基础创建新主题。
- `Themes.isPresetTheme` - 检查一个主题对象是否来自 Geist UI。
- `Themes.isAvailableThemeType` - 检查一个主题名是否可用。(是否重复)
- `Themes.hasUserCustomTheme` - 检查一组主题中是否包含自定义的主题。
- `Themes.getPresets` - 获取一组由 Geist UI 内置的默认主题。
- `Themes.getPresetStaticTheme` - 获取一个由 Geist UI 内置的默认主题 (默认加载的)。
export default ({ children }) => <Layout meta={meta}>{children}</Layout>