mirror of
https://github.com/zhigang1992/react.git
synced 2026-04-23 03:50:06 +08:00
docs: add customization page
This commit is contained in:
@@ -9,7 +9,7 @@ import SunIcon from './icons/sun'
|
||||
const Controls: React.FC<{}> = React.memo(({
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { onChange, updateChineseState } = useConfigs()
|
||||
const { updateCustomTheme, updateChineseState } = useConfigs()
|
||||
const { pathname } = useRouter()
|
||||
const { locale } = useLocale()
|
||||
const isChinese = useMemo(() => locale === 'zh-cn', [locale])
|
||||
@@ -19,9 +19,8 @@ const Controls: React.FC<{}> = React.memo(({
|
||||
return pathname.replace(locale, nextLocale)
|
||||
}, [locale, pathname])
|
||||
|
||||
const switchThemes = (val: string) => {
|
||||
const isDark = val === 'dark'
|
||||
onChange && onChange(isDark)
|
||||
const switchThemes = (type: 'dark' | 'light') => {
|
||||
updateCustomTheme({ type })
|
||||
}
|
||||
const switchLanguages = () => {
|
||||
updateChineseState(!isChinese)
|
||||
|
||||
37
lib/components/customization/colors.tsx
Normal file
37
lib/components/customization/colors.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { Card, useTheme } from 'components'
|
||||
import { CardTypes } from 'components/utils/prop-types'
|
||||
|
||||
const types = ['secondary', 'success', 'warning', 'error',
|
||||
'dark', 'alert', 'purple', 'violet', 'cyan', 'lite']
|
||||
|
||||
const Colors: React.FC<React.PropsWithChildren<{}>> = () => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className="colors">
|
||||
{types.map((type, index) => {
|
||||
return (
|
||||
<div key={`${type}-${index}`} className="color-card">
|
||||
<Card type={type as CardTypes}>{type}</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<style jsx>{`
|
||||
.colors {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.color-card {
|
||||
display: flex;
|
||||
width: 9rem;
|
||||
margin-right: ${theme.layout.gapHalf};
|
||||
margin-bottom: ${theme.layout.gapHalf};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Colors
|
||||
110
lib/components/customization/demo.tsx
Normal file
110
lib/components/customization/demo.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React, { useState } from 'react'
|
||||
import FullScreenIcon from 'lib/components/icons/full-screen'
|
||||
import FullScreenCloseIcon from 'lib/components/icons/full-screen-close'
|
||||
import Colors from './colors'
|
||||
import { useTheme, Button, Text, Code, Spacer, Link, Select, Checkbox } from 'components'
|
||||
|
||||
const Demo: React.FC<React.PropsWithChildren<{}>> = () => {
|
||||
const theme = useTheme()
|
||||
const [fullScreen, setFullScreen] = useState<boolean>(false)
|
||||
|
||||
const showMoreOrLess = () => {
|
||||
setFullScreen(last => !last)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="demo">
|
||||
<div className="action" onClick={showMoreOrLess}>
|
||||
<Button type="abort" auto>
|
||||
{fullScreen ? <FullScreenIcon /> : <FullScreenCloseIcon />}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="content">
|
||||
<Text h3>Preview</Text>
|
||||
<Text>Here's a preview of your changes to the Theme. When you save the changes,
|
||||
the entire document site will change with the theme.</Text>
|
||||
|
||||
<Text>You can download automatically generated code or share your custom theme with anyone.</Text>
|
||||
|
||||
<Spacer y={1.7} />
|
||||
<Text h4>Colors</Text>
|
||||
<Colors />
|
||||
|
||||
<Spacer y={1.7} />
|
||||
<Text h4>Typography</Text>
|
||||
<Text><Code>inline codes</Code>, <Link href="#" color>Hyperlink</Link></Text>
|
||||
<Text>Our mission is to make cloud computing accessible to everyone. We build products for developers and designers. And those who aspire to become one.</Text>
|
||||
<Text h6>Heading</Text>
|
||||
<Text h5>Heading</Text>
|
||||
<Text h4>Heading</Text>
|
||||
<Text h3>Heading</Text>
|
||||
<Text h2>Heading</Text>
|
||||
<Text h1>Heading</Text>
|
||||
|
||||
<Spacer y={1.7} />
|
||||
<Text h4>Basic Components</Text>
|
||||
<Select placeholder="Choose one" initialValue="1">
|
||||
<Select.Option value="1">Option 1</Select.Option>
|
||||
<Select.Option value="2">Option 2</Select.Option>
|
||||
</Select>
|
||||
<Spacer y={1} />
|
||||
<Button disabled auto size="small">Action</Button>
|
||||
<Spacer inline x={.5} />
|
||||
<Button auto size="small">Action</Button>
|
||||
<Spacer inline x={.5} />
|
||||
<Button auto type="secondary" size="small">Action</Button>
|
||||
<Spacer y={.5} />
|
||||
<Button>Action</Button>
|
||||
<Spacer y={1} />
|
||||
<Checkbox.Group value={['sydney']}>
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="beijing">Bei Jing</Checkbox>
|
||||
</Checkbox.Group>
|
||||
<Spacer y={4} />
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.demo {
|
||||
width: ${fullScreen ? '100%' : '35%'};
|
||||
margin-top: calc(${theme.layout.gap} * 2);
|
||||
margin-right: 20px;
|
||||
position: relative;
|
||||
border-right: 1px solid ${theme.palette.border};
|
||||
height: 100%;
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
|
||||
.action {
|
||||
position: absolute;
|
||||
right: .5rem;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: color 200ms ease-out;
|
||||
color: ${theme.palette.accents_3};
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
color: ${theme.palette.accents_6};
|
||||
}
|
||||
|
||||
.action :global(button) {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Demo
|
||||
95
lib/components/customization/editor-color-item.tsx
Normal file
95
lib/components/customization/editor-color-item.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTheme, ZeitUIThemesPalette, Popover } from 'components'
|
||||
import { ColorResult, TwitterPicker } from 'react-color'
|
||||
import { useConfigs } from 'lib/config-context'
|
||||
|
||||
interface Props {
|
||||
value?: string
|
||||
keyName: keyof ZeitUIThemesPalette
|
||||
}
|
||||
|
||||
const EditorColorItem: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
keyName,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { updateCustomTheme } = useConfigs()
|
||||
const label = `${keyName}`
|
||||
const mainColor = useMemo(() => theme.palette[keyName], [theme.palette, keyName])
|
||||
const colorChangeHandler = ({ hex }: ColorResult) => {
|
||||
updateCustomTheme({
|
||||
palette: { [keyName]: hex }
|
||||
})
|
||||
}
|
||||
|
||||
const popoverContent = (color: string) => (
|
||||
<TwitterPicker triangle="hide" color={color} onChangeComplete={colorChangeHandler} />
|
||||
)
|
||||
return (
|
||||
<Popover content={() => popoverContent(mainColor)} portalClassName="editor-popover" offset={3}>
|
||||
<div className="editor-item">
|
||||
<div className="dot-box">
|
||||
<span className="dot" />
|
||||
</div>
|
||||
{label}
|
||||
<style jsx>{`
|
||||
.editor-item {
|
||||
background-color: transparent;
|
||||
width: auto;
|
||||
padding: 0 ${theme.layout.gapHalf};
|
||||
line-height: 2rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid ${theme.palette.border};
|
||||
border-radius: ${theme.layout.radius};
|
||||
color: ${theme.palette.accents_5};
|
||||
margin-right: .75rem;
|
||||
margin-bottom: .5rem;
|
||||
cursor: pointer;
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
|
||||
:global(.editor-popover .inner) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:global(.editor-popover .twitter-picker) {
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.editor-item:hover {
|
||||
color: ${theme.palette.accents_8};
|
||||
}
|
||||
|
||||
.editor-item:hover .dot {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.dot-box, .dot {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dot-box {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-right: .75rem;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: ${mainColor};
|
||||
transform: scale(.8);
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditorColorItem
|
||||
55
lib/components/customization/editor-input-item.tsx
Normal file
55
lib/components/customization/editor-input-item.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTheme, Input, ZeitUIThemes } from 'components'
|
||||
import { useConfigs } from 'lib/config-context'
|
||||
|
||||
type Props = {
|
||||
value?: string
|
||||
groupName: keyof ZeitUIThemes
|
||||
keyName: string
|
||||
}
|
||||
|
||||
const EditorInputItem: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
groupName, keyName
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { updateCustomTheme } = useConfigs()
|
||||
const currentVal = useMemo(() => {
|
||||
const group = theme[groupName]
|
||||
const key = keyName as keyof typeof group
|
||||
return theme[groupName][key]
|
||||
}, [theme.expressiveness, keyName])
|
||||
const width = useMemo(() => `${currentVal}`.length > 15 ? '350px' : 'auto', [])
|
||||
const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateCustomTheme({
|
||||
[groupName]: { [keyName]: event.target.value },
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor-item">
|
||||
<Input value={currentVal as string} label={keyName} onChange={changeHandler} className="editor-input" />
|
||||
<style jsx>{`
|
||||
.editor-item {
|
||||
background-color: transparent;
|
||||
width: auto;
|
||||
padding: 0 ${theme.layout.gapHalf};
|
||||
line-height: 2rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: ${theme.palette.accents_5};
|
||||
margin-right: .75rem;
|
||||
margin-bottom: .5rem;
|
||||
cursor: pointer;
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
|
||||
.editor-item :global(.editor-input) {
|
||||
width: ${width};
|
||||
}
|
||||
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditorInputItem
|
||||
108
lib/components/customization/editor.tsx
Normal file
108
lib/components/customization/editor.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from 'react'
|
||||
import { Text, Button, useTheme, ZeitUIThemesPalette, ZeitUIThemesExpressiveness, ZeitUIThemesLayout } 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 ZeitUIThemesPalette> = [
|
||||
'accents_1', 'accents_2', 'accents_3', 'accents_4', 'accents_5', 'accents_6',
|
||||
'accents_7', 'accents_8', 'foreground', 'background',
|
||||
]
|
||||
const statusColors: Array<keyof ZeitUIThemesPalette> = [
|
||||
'success', 'successLight', 'successDark', 'error', 'errorLight', 'errorDark',
|
||||
'warning', 'warningLight', 'warningDark',
|
||||
]
|
||||
const otherColors: Array<keyof ZeitUIThemesPalette> = [
|
||||
'selection', 'secondary', 'border', 'code', 'cyan', 'purple', 'alert', 'violet'
|
||||
]
|
||||
const expressiveness: Array<keyof ZeitUIThemesExpressiveness> = [
|
||||
'linkStyle', 'linkHoverStyle', 'dropdownBoxShadow', 'shadowSmall',
|
||||
'shadowMedium', 'shadowLarge',
|
||||
]
|
||||
const pageLayout: Array<keyof ZeitUIThemesLayout> = [
|
||||
'pageWidth', 'pageWidthWithMargin', 'pageMargin', 'radius',
|
||||
]
|
||||
const gapLayout: Array<keyof ZeitUIThemesLayout> = [
|
||||
'gap', 'gapNegative', 'gapHalf', 'gapHalfNegative', 'gapQuarter', 'gapQuarterNegative',
|
||||
]
|
||||
|
||||
const Editor = () => {
|
||||
const theme = useTheme()
|
||||
const { updateCustomTheme } = useConfigs()
|
||||
|
||||
const resetLayout = () => updateCustomTheme({ layout: DefaultTheme.layout })
|
||||
const restColors = () => updateCustomTheme({ palette: DefaultTheme.palette })
|
||||
const resetExpressiveness = () => {
|
||||
updateCustomTheme({ expressiveness: DefaultTheme.expressiveness })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor">
|
||||
<Text h3>Colors <Button type="abort" auto size="mini" onClick={restColors}>Reset</Button></Text>
|
||||
<p className="subtitle">basic</p>
|
||||
<div className="content">
|
||||
{basicColors.map((item, index) => (
|
||||
<EditorColorItem key={`${item}-${index}`} keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
<p className="subtitle">status</p>
|
||||
<div className="content">
|
||||
{statusColors.map((item, index) => (
|
||||
<EditorColorItem key={`${item}-${index}`} keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
<p className="subtitle">others</p>
|
||||
<div className="content">
|
||||
{otherColors.map((item, index) => (
|
||||
<EditorColorItem key={`${item}-${index}`} keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Text h3>Expressiveness <Button type="abort" auto size="mini" onClick={resetExpressiveness}>Reset</Button></Text>
|
||||
<p className="subtitle">basic</p>
|
||||
<div className="content">
|
||||
{expressiveness.map((item, index) => (
|
||||
<EditorInputItem key={`${item}-${index}`} groupName="expressiveness" keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Text h3>Layout <Button type="abort" auto size="mini" onClick={resetLayout}>Reset</Button></Text>
|
||||
<p className="subtitle">basic</p>
|
||||
<div className="content">
|
||||
{pageLayout.map((item, index) => (
|
||||
<EditorInputItem key={`${item}-${index}`} groupName="layout" keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
<p className="subtitle">gaps</p>
|
||||
<div className="content">
|
||||
{gapLayout.map((item, index) => (
|
||||
<EditorInputItem key={`${item}-${index}`} groupName="layout" keyName={item} />
|
||||
))}
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.editor :global(h3) {
|
||||
margin: 2rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: ${theme.palette.accents_4};
|
||||
text-transform: uppercase;
|
||||
font-size: .75rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Editor
|
||||
38
lib/components/customization/layout.tsx
Normal file
38
lib/components/customization/layout.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
import { useTheme } from 'components'
|
||||
import Demo from './demo'
|
||||
|
||||
const CustomizationLayout: React.FC<React.PropsWithChildren<{}>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className="layout">
|
||||
<Demo />
|
||||
<div className="content">
|
||||
{children}
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.layout {
|
||||
min-height: calc(100vh - 108px);
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 0 ${theme.layout.gap};
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.demo {
|
||||
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomizationLayout
|
||||
28
lib/components/icons/full-screen-close.tsx
Normal file
28
lib/components/icons/full-screen-close.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import withDefaults from 'components/utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
}
|
||||
|
||||
export type FullScreenCloseIconProps = Props & typeof defaultProps & React.SVGAttributes<any>
|
||||
|
||||
const FullScreenCloseIcon: React.FC<React.PropsWithChildren<FullScreenCloseIconProps>> = ({
|
||||
width, height, ...props
|
||||
}) => {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width={width} height={height} {...props} stroke="currentColor" strokeWidth="1.5"
|
||||
strokeLinecap="round" strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision"
|
||||
style={{ color: 'currentColor' }}>
|
||||
<path d="M4 14h6m0 0v6m0-6l-7 7m17-11h-6m0 0V4m0 6l7-7m-7 17v-6m0 0h6m-6 0l7 7M10 4v6m0 0H4m6 0L3 3"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(FullScreenCloseIcon, defaultProps)
|
||||
27
lib/components/icons/full-screen.tsx
Normal file
27
lib/components/icons/full-screen.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import withDefaults from 'components/utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
}
|
||||
|
||||
export type FullScreenIconProps = Props & typeof defaultProps & React.SVGAttributes<any>
|
||||
|
||||
const FullScreenIcon: React.FC<React.PropsWithChildren<FullScreenIconProps>> = ({
|
||||
width, height, ...props
|
||||
}) => {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width={width} height={height} {...props} stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision" style={{ color: 'currentColor' }}>
|
||||
<path d="M15 3h6m0 0v6m0-6l-7 7M9 21H3m0 0v-6m0 6l7-7M3 9V3m0 0h6M3 3l7 7m11 5v6m0 0h-6m6 0l-7-7"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(FullScreenIcon, defaultProps)
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react'
|
||||
import { ZeitUIThemes } from 'components/styles/themes'
|
||||
import { DeepPartial } from 'components/utils/types'
|
||||
|
||||
export interface Configs {
|
||||
onChange?: Function
|
||||
onThemeChange?: Function
|
||||
isChinese?: boolean
|
||||
updateChineseState: Function
|
||||
sidebarScrollHeight: number
|
||||
@@ -9,6 +11,9 @@ export interface Configs {
|
||||
|
||||
tabbarFixed: boolean
|
||||
updateTabbarFixed: Function
|
||||
|
||||
customTheme: DeepPartial<ZeitUIThemes>
|
||||
updateCustomTheme: (theme: DeepPartial<ZeitUIThemes>) => void
|
||||
}
|
||||
|
||||
export const defaultConfigs: Configs = {
|
||||
@@ -18,6 +23,10 @@ export const defaultConfigs: Configs = {
|
||||
|
||||
tabbarFixed: false,
|
||||
updateTabbarFixed: () => {},
|
||||
|
||||
customTheme: {},
|
||||
updateCustomTheme: () => {},
|
||||
onThemeChange: () => {},
|
||||
}
|
||||
|
||||
export const ConfigContext = React.createContext<Configs>(defaultConfigs)
|
||||
|
||||
@@ -2,9 +2,14 @@ import React, { useMemo, useState } from 'react'
|
||||
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 { ZeitUIThemes } from 'components/styles/themes'
|
||||
import { deepMergeObject } from 'components/styles/theme-provider/theme-provider'
|
||||
import useCurrentState from 'components/utils/use-current-state'
|
||||
import { useTheme } from 'components'
|
||||
|
||||
interface Props {
|
||||
onChange?: Function
|
||||
onThemeChange?: Function
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
@@ -13,23 +18,33 @@ const defaultProps = {
|
||||
export type ConfigProviderProps = Props & typeof defaultProps
|
||||
|
||||
const ConfigProvider: React.FC<React.PropsWithChildren<ConfigProviderProps>> = React.memo(({
|
||||
onChange, children,
|
||||
onThemeChange, children,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { pathname } = useRouter()
|
||||
const [isChinese, setIsChinese] = useState<boolean>(() => pathname.includes('zh-cn'))
|
||||
const [scrollHeight, setScrollHeight] = useState<number>(0)
|
||||
const [tabbarFixed, setTabbarFixed] = useState<boolean>(false)
|
||||
const [customTheme, setCustomTheme, customThemeRef] = useCurrentState<DeepPartial<ZeitUIThemes>>(theme)
|
||||
|
||||
const updateSidebarScrollHeight = (height: number) => setScrollHeight(height)
|
||||
const updateChineseState = (state: boolean) => setIsChinese(state)
|
||||
const updateTabbarFixed = (state: boolean) => setTabbarFixed(state)
|
||||
const updateCustomTheme = (nextTheme: DeepPartial<ZeitUIThemes>) => {
|
||||
const mergedTheme = deepMergeObject(customThemeRef.current, nextTheme)
|
||||
setCustomTheme(mergedTheme)
|
||||
onThemeChange && onThemeChange(mergedTheme)
|
||||
}
|
||||
|
||||
const initialValue = useMemo<Configs>(() => ({
|
||||
onChange, isChinese, tabbarFixed,
|
||||
onThemeChange, isChinese, tabbarFixed,
|
||||
customTheme,
|
||||
updateCustomTheme,
|
||||
updateTabbarFixed,
|
||||
updateChineseState,
|
||||
sidebarScrollHeight: scrollHeight,
|
||||
updateSidebarScrollHeight,
|
||||
}), [onChange, scrollHeight, tabbarFixed, isChinese])
|
||||
}), [onThemeChange, scrollHeight, tabbarFixed, isChinese])
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={initialValue}>
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"General"},{"name":"text","url":"/en-us/components/text","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]}]}]
|
||||
[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"General"},{"name":"text","url":"/en-us/components/text","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]}]},{"name":"customization","children":[]}]
|
||||
|
||||
@@ -39,21 +39,11 @@ const nextConfig = {
|
||||
permanent: true,
|
||||
destination: '/en-us/guide/:path*'
|
||||
},
|
||||
{
|
||||
source: '/en-us/customization/:path*',
|
||||
permanent: true,
|
||||
destination: '/en-us/guide/:path*'
|
||||
},
|
||||
{
|
||||
source: '/zh-cn/getting-started/:path*',
|
||||
permanent: true,
|
||||
destination: '/zh-cn/guide/:path*'
|
||||
},
|
||||
{
|
||||
source: '/zh-cn/customization/:path*',
|
||||
permanent: true,
|
||||
destination: '/zh-cn/guide/:path*'
|
||||
},
|
||||
{
|
||||
source: '/zh-cn/',
|
||||
permanent: true,
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@mdx-js/loader": "^1.5.7",
|
||||
"@next/mdx": "^9.2.2",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-color": "^3.0.1",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/styled-jsx": "^2.2.8",
|
||||
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||
@@ -61,6 +62,7 @@
|
||||
"fs-extra": "^8.1.0",
|
||||
"next": "^9.3.2",
|
||||
"react": "^16.13.0",
|
||||
"react-color": "^2.18.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-live": "^2.2.2",
|
||||
"styled-jsx": "^3.2.4",
|
||||
@@ -69,4 +71,4 @@
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import Head from 'next/head'
|
||||
import { NextPage } from 'next'
|
||||
import { AppProps } from 'next/app'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { CSSBaseline, ZEITUIProvider, useTheme } from 'components'
|
||||
import { useState } from 'react'
|
||||
import { CSSBaseline, ZEITUIProvider, useTheme, ZeitUIThemes } from 'components'
|
||||
import Menu from 'lib/components/menu'
|
||||
import ConfigContext from 'lib/config-provider'
|
||||
import { DeepPartial } from 'components/utils/types'
|
||||
|
||||
const Application: NextPage<AppProps> = ({ Component, pageProps }) => {
|
||||
const theme = useTheme()
|
||||
const [themeType, setThemeType] = useState<typeof theme.type>(theme.type)
|
||||
const changeHandle = useCallback((isDark: boolean) => {
|
||||
setThemeType(isDark ? 'dark' : 'light')
|
||||
}, [])
|
||||
const [customTheme, setCustomTheme] = useState<DeepPartial<ZeitUIThemes>>({})
|
||||
const themeChangeHandle = (theme: DeepPartial<ZeitUIThemes>) => {
|
||||
setCustomTheme(theme)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -32,9 +33,9 @@ const Application: NextPage<AppProps> = ({ Component, pageProps }) => {
|
||||
<meta property="twitter:image" content="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png" />
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, viewport-fit=cover" />
|
||||
</Head>
|
||||
<ZEITUIProvider theme={{ type: themeType }}>
|
||||
<ZEITUIProvider theme={customTheme}>
|
||||
<CSSBaseline />
|
||||
<ConfigContext onChange={changeHandle}>
|
||||
<ConfigContext onThemeChange={themeChangeHandle}>
|
||||
<Menu />
|
||||
<Component {...pageProps} />
|
||||
</ConfigContext>
|
||||
|
||||
18
pages/en-us/customization/index.tsx
Normal file
18
pages/en-us/customization/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { Text, Spacer } from 'components'
|
||||
import CustomizationLayout from 'lib/components/customization/layout'
|
||||
import CustomizationEditor from 'lib/components/customization/editor'
|
||||
|
||||
const Customization = () => {
|
||||
return (
|
||||
<CustomizationLayout>
|
||||
<Spacer y={1.2} />
|
||||
<Text h2>Customization</Text>
|
||||
<Text>Custom themes is a very simple thing in ZEIT UI, click change, download or share.</Text>
|
||||
<CustomizationEditor />
|
||||
<Spacer y={3.2} />
|
||||
</CustomizationLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Customization
|
||||
@@ -11,8 +11,8 @@ const weights = {
|
||||
'guide': 1,
|
||||
'docs': 2,
|
||||
'getting-started': 3,
|
||||
'customization': 4,
|
||||
'components': 5,
|
||||
'customization': 10,
|
||||
}
|
||||
const groupWeights = {
|
||||
'快速上手': 1,
|
||||
|
||||
45
yarn.lock
45
yarn.lock
@@ -1020,6 +1020,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
|
||||
integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
|
||||
|
||||
"@icons/material@^0.2.4":
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
|
||||
integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
|
||||
|
||||
"@mapbox/rehype-prism@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/rehype-prism/-/rehype-prism-0.4.0.tgz#58714b345ec01256aa74c24762a341f6a771494e"
|
||||
@@ -1125,6 +1130,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||
|
||||
"@types/react-color@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0"
|
||||
integrity sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@^16.9.5":
|
||||
version "16.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
|
||||
@@ -4728,7 +4740,7 @@ lodash.uniq@4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
|
||||
lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@@ -4824,6 +4836,11 @@ markdown-escapes@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
|
||||
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
|
||||
|
||||
material-colors@^1.2.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
||||
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
@@ -6239,7 +6256,7 @@ prop-types-exact@1.2.0:
|
||||
object.assign "^4.1.0"
|
||||
reflect.ownkeys "^0.2.0"
|
||||
|
||||
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@@ -6360,6 +6377,18 @@ raw-body@2.4.0:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react-color@^2.18.0:
|
||||
version "2.18.0"
|
||||
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.18.0.tgz#34956f0bac394f6c3bc01692fd695644cc775ffd"
|
||||
integrity sha512-FyVeU1kQiSokWc8NPz22azl1ezLpJdUyTbWL0LPUpcuuYDrZ/Y1veOk9rRK5B3pMlyDGvTk4f4KJhlkIQNRjEA==
|
||||
dependencies:
|
||||
"@icons/material" "^0.2.4"
|
||||
lodash "^4.17.11"
|
||||
material-colors "^1.2.1"
|
||||
prop-types "^15.5.10"
|
||||
reactcss "^1.2.0"
|
||||
tinycolor2 "^1.4.1"
|
||||
|
||||
react-dom@^16.13.0:
|
||||
version "16.13.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.0.tgz#cdde54b48eb9e8a0ca1b3dc9943d9bb409b81866"
|
||||
@@ -6413,6 +6442,13 @@ react@^16.13.0:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
reactcss@^1.2.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
|
||||
integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
|
||||
dependencies:
|
||||
lodash "^4.0.1"
|
||||
|
||||
read-pkg@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
|
||||
@@ -7443,6 +7479,11 @@ tiny-emitter@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
|
||||
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
|
||||
|
||||
tinycolor2@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
|
||||
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
|
||||
|
||||
title-case@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"
|
||||
|
||||
Reference in New Issue
Block a user