mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-01 17:18:41 +08:00
@@ -4,6 +4,7 @@ exports[`AutoComplete should render correctly 1`] = `
|
||||
<AutoComplete
|
||||
className=""
|
||||
clearable={false}
|
||||
disableFreeSolo={false}
|
||||
disableMatchWidth={false}
|
||||
disabled={false}
|
||||
initialValue=""
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { AutoComplete } from 'components'
|
||||
import { nativeEvent } from 'tests/utils'
|
||||
import { act } from 'react-dom/test-utils'
|
||||
|
||||
describe('AutoComplete', () => {
|
||||
it('should render correctly', () => {
|
||||
@@ -32,11 +33,13 @@ describe('AutoComplete', () => {
|
||||
expect((input as HTMLInputElement).value).toEqual('value2')
|
||||
})
|
||||
|
||||
it('should render clear icon', () => {
|
||||
it('should render clear icon', async () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" />)
|
||||
expect(wrapper.find('svg').length).toBe(0)
|
||||
|
||||
wrapper.setProps({ clearable: true })
|
||||
await act(async () => {
|
||||
wrapper.setProps({ clearable: true })
|
||||
})
|
||||
expect(wrapper.find('svg').length).toBe(1)
|
||||
|
||||
wrapper.find('svg').simulate('click', nativeEvent)
|
||||
@@ -44,11 +47,13 @@ describe('AutoComplete', () => {
|
||||
expect((input as HTMLInputElement).value).toEqual('')
|
||||
})
|
||||
|
||||
it('should reponse width change', () => {
|
||||
it('should reponse width change', async () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" width="100px" />)
|
||||
expect(wrapper.prop('width')).toEqual('100px')
|
||||
await act(async () => {
|
||||
wrapper.setProps({ width: '200px' })
|
||||
})
|
||||
|
||||
wrapper.setProps({ width: '200px' })
|
||||
expect(wrapper.prop('width')).toEqual('200px')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react'
|
||||
import { mount, render } from 'enzyme'
|
||||
import { AutoComplete } from 'components'
|
||||
import { nativeEvent } from 'tests/utils'
|
||||
import { nativeEvent, updateWrapper } from 'tests/utils'
|
||||
import { act } from 'react-dom/test-utils'
|
||||
const mockOptions = [{ label: 'London', value: 'london' }]
|
||||
|
||||
describe('AutoComplete Search', () => {
|
||||
@@ -33,9 +34,11 @@ describe('AutoComplete Search', () => {
|
||||
expect(value).not.toEqual('london')
|
||||
})
|
||||
|
||||
it('should render searching component', () => {
|
||||
it('should render searching component', async () => {
|
||||
let wrapper = mount(<AutoComplete searching={false} options={mockOptions} />)
|
||||
wrapper.setProps({ searching: true })
|
||||
await act(async () => {
|
||||
wrapper.setProps({ searching: true })
|
||||
})
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
let dropdown = wrapper.find('.auto-complete-dropdown')
|
||||
expect(dropdown.text()).not.toContain('london')
|
||||
@@ -136,4 +139,15 @@ describe('AutoComplete Search', () => {
|
||||
const wrapper = mount(<AutoComplete options={[]} />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('value should be reset when freeSolo disabled', async () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" disableFreeSolo />)
|
||||
const input = wrapper.find('input').at(0)
|
||||
input.simulate('focus')
|
||||
input.simulate('change', { target: { value: 'test' } })
|
||||
expect((input.getDOMNode() as HTMLInputElement).value).toEqual('test')
|
||||
input.simulate('blur')
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect((input.getDOMNode() as HTMLInputElement).value).toEqual('value')
|
||||
})
|
||||
})
|
||||
|
||||
60
components/auto-complete/__tests__/use-input.test.tsx
Normal file
60
components/auto-complete/__tests__/use-input.test.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { AutoComplete, useInput } from 'components'
|
||||
|
||||
describe('UseInput', () => {
|
||||
it('should follow change with use-input', () => {
|
||||
let log = ''
|
||||
const logSpy = jest.spyOn(console, 'log').mockImplementation(msg => (log = msg))
|
||||
const MockInput: React.FC<{ value?: string }> = ({ value }) => {
|
||||
const { state, setState, bindings } = useInput('')
|
||||
useEffect(() => {
|
||||
if (value) setState(value)
|
||||
}, [value])
|
||||
useEffect(() => {
|
||||
if (state) console.log(state)
|
||||
}, [state])
|
||||
return <AutoComplete {...bindings} />
|
||||
}
|
||||
|
||||
const wrapper = mount(<MockInput />)
|
||||
wrapper.setProps({ value: 'test' })
|
||||
const input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
|
||||
|
||||
expect(input.value).toEqual('test')
|
||||
expect(log).toContain('test')
|
||||
|
||||
log = ''
|
||||
wrapper
|
||||
.find('input')
|
||||
.at(0)
|
||||
.simulate('change', { target: { value: 'test-change' } })
|
||||
expect(log).toContain('test-change')
|
||||
logSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should follow change with use-input', () => {
|
||||
const MockInput: React.FC<{ value?: string; resetValue?: boolean }> = ({
|
||||
value,
|
||||
resetValue,
|
||||
}) => {
|
||||
const { reset, setState, bindings } = useInput('')
|
||||
useEffect(() => {
|
||||
if (value) setState(value)
|
||||
}, [value])
|
||||
useEffect(() => {
|
||||
if (resetValue) reset()
|
||||
}, [resetValue])
|
||||
return <AutoComplete {...bindings} />
|
||||
}
|
||||
|
||||
const wrapper = mount(<MockInput />)
|
||||
wrapper.setProps({ value: 'test' })
|
||||
let input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
|
||||
expect(input.value).toEqual('test')
|
||||
|
||||
wrapper.setProps({ resetValue: true })
|
||||
input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
|
||||
expect(input.value).toEqual('')
|
||||
})
|
||||
})
|
||||
@@ -28,9 +28,10 @@ const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>>
|
||||
children,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { value, updateValue, size } = useAutoCompleteContext()
|
||||
const { value, updateValue, size, updateVisible } = useAutoCompleteContext()
|
||||
const selectHandler = () => {
|
||||
updateValue && updateValue(identValue)
|
||||
updateVisible && updateVisible(false)
|
||||
}
|
||||
|
||||
const isActive = useMemo(() => value === identValue, [identValue, value])
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AutoCompleteContext, AutoCompleteConfig } from './auto-complete-context
|
||||
import { NormalSizes, NormalTypes } from '../utils/prop-types'
|
||||
import Loading from '../loading'
|
||||
import { pickChild } from '../utils/collections'
|
||||
import useCurrentState from '../utils/use-current-state'
|
||||
|
||||
export type AutoCompleteOption = {
|
||||
label: string
|
||||
@@ -31,6 +32,7 @@ interface Props {
|
||||
dropdownClassName?: string
|
||||
dropdownStyle?: object
|
||||
disableMatchWidth?: boolean
|
||||
disableFreeSolo?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
@@ -41,6 +43,7 @@ const defaultProps = {
|
||||
clearable: false,
|
||||
size: 'medium' as NormalSizes,
|
||||
disableMatchWidth: false,
|
||||
disableFreeSolo: false,
|
||||
className: '',
|
||||
}
|
||||
|
||||
@@ -83,11 +86,16 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
dropdownClassName,
|
||||
dropdownStyle,
|
||||
disableMatchWidth,
|
||||
disableFreeSolo,
|
||||
...props
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [state, setState] = useState<string>(customInitialValue)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const resetTimer = useRef<number>()
|
||||
const [state, setState, stateRef] = useCurrentState<string>(customInitialValue)
|
||||
const [selectVal, setSelectVal] = useState<string>(customInitialValue)
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
|
||||
const [, searchChild] = pickChild(children, AutoCompleteSearching)
|
||||
const [, emptyChild] = pickChild(children, AutoCompleteEmpty)
|
||||
const autoCompleteItems = useMemo(() => {
|
||||
@@ -110,14 +118,24 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
|
||||
const updateValue = (val: string) => {
|
||||
if (disabled) return
|
||||
setSelectVal(val)
|
||||
onSelect && onSelect(val)
|
||||
setState(val)
|
||||
inputRef.current && inputRef.current.focus()
|
||||
}
|
||||
const updateVisible = (next: boolean) => setVisible(next)
|
||||
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setVisible(true)
|
||||
onSearch && onSearch(event.target.value)
|
||||
setState(event.target.value)
|
||||
}
|
||||
const resetInputValue = () => {
|
||||
if (!disableFreeSolo) return
|
||||
if (!state || state === '') return
|
||||
if (state !== selectVal) {
|
||||
setState(selectVal)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onChange && onChange(state)
|
||||
@@ -140,9 +158,15 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
)
|
||||
|
||||
const toggleFocusHandler = (next: boolean) => {
|
||||
clearTimeout(resetTimer.current)
|
||||
setVisible(next)
|
||||
if (next) {
|
||||
onSearch && onSearch(state)
|
||||
onSearch && onSearch(stateRef.current)
|
||||
} else {
|
||||
resetTimer.current = window.setTimeout(() => {
|
||||
resetInputValue()
|
||||
clearTimeout(resetTimer.current)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +181,7 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
<AutoCompleteContext.Provider value={initialValue}>
|
||||
<div ref={ref} className="auto-complete">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
size={size}
|
||||
status={status}
|
||||
onChange={onInputChange}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Breadcrumbs should redefined all separators 1`] = `
|
||||
"<nav class=\\"\\"><span class=\\"breadcrums-item \\">test-1</span><div class=\\"separator \\">*<style>
|
||||
.separator {
|
||||
display: inline-flex;
|
||||
margin: 0 8px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
}
|
||||
</style></div><span class=\\"breadcrums-item \\">test-2</span><style>
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: #888;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav :global(.link:hover) {
|
||||
color: rgba(0, 112, 243, 0.85);
|
||||
}
|
||||
|
||||
nav > :global(span:last-of-type) {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
nav > :global(.separator:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav :global(svg) {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
nav :global(.breadcrums-item) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style></nav>"
|
||||
`;
|
||||
|
||||
exports[`Breadcrumbs should render correctly 1`] = `
|
||||
"<nav class=\\"\\"><span class=\\"breadcrums-item \\">test-1</span><style>
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: #888;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav :global(.link:hover) {
|
||||
color: rgba(0, 112, 243, 0.85);
|
||||
}
|
||||
|
||||
nav > :global(span:last-of-type) {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
nav > :global(.separator:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav :global(svg) {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
nav :global(.breadcrums-item) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style></nav>"
|
||||
`;
|
||||
75
components/breadcrumbs/__tests__/breadcrumbs.test.tsx
Normal file
75
components/breadcrumbs/__tests__/breadcrumbs.test.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Breadcrumbs } from 'components'
|
||||
|
||||
describe('Breadcrumbs', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = mount(
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item>test-1</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should redefined all separators', () => {
|
||||
const wrapper = mount(
|
||||
<Breadcrumbs separator="*">
|
||||
<Breadcrumbs.Item>test-1</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>test-2</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(wrapper.html()).toContain('*')
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('the specified separator should be redefined', () => {
|
||||
const wrapper = mount(
|
||||
<Breadcrumbs separator="*">
|
||||
<Breadcrumbs.Item>test-1</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Separator>%</Breadcrumbs.Separator>
|
||||
<Breadcrumbs.Item>test-2</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
expect(wrapper.html()).toContain('%')
|
||||
})
|
||||
|
||||
it('should render string when href missing', () => {
|
||||
let wrapper = mount(
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item>test-1</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
let dom = wrapper.find('.breadcrums-item').at(0).getDOMNode()
|
||||
expect(dom.tagName).toEqual('SPAN')
|
||||
|
||||
wrapper = mount(
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item href="">test-1</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
dom = wrapper.find('.breadcrums-item').at(0).getDOMNode()
|
||||
expect(dom.tagName).toEqual('A')
|
||||
|
||||
wrapper = mount(
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item nextLink>test-1</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
dom = wrapper.find('.breadcrums-item').at(0).getDOMNode()
|
||||
expect(dom.tagName).toEqual('A')
|
||||
})
|
||||
|
||||
it('should trigger click event', () => {
|
||||
const handler = jest.fn()
|
||||
const wrapper = mount(
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item onClick={handler}>test-1</Breadcrumbs.Item>
|
||||
</Breadcrumbs>,
|
||||
)
|
||||
wrapper.find('.breadcrums-item').at(0).simulate('click')
|
||||
expect(handler).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
61
components/breadcrumbs/breadcrumbs-item.tsx
Normal file
61
components/breadcrumbs/breadcrumbs-item.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import Link from '../link'
|
||||
import { Props as LinkBasicProps } from '../link/link'
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import { pickChild } from '../utils/collections'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
|
||||
interface Props {
|
||||
href?: string
|
||||
nextLink?: boolean
|
||||
onClick?: (event: React.MouseEvent) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
nextLink: false,
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.AnchorHTMLAttributes<any>, keyof Props>
|
||||
type NativeLinkAttrs = Omit<NativeAttrs, keyof LinkBasicProps>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeLinkAttrs
|
||||
|
||||
const BreadcrumbsItem = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.PropsWithChildren<BreadcrumbsProps>
|
||||
>(
|
||||
(
|
||||
{ href, nextLink, onClick, children, className, ...props },
|
||||
ref: React.Ref<HTMLAnchorElement>,
|
||||
) => {
|
||||
const isLink = useMemo(() => href !== undefined || nextLink, [href, nextLink])
|
||||
const [withoutSepChildren] = pickChild(children, BreadcrumbsSeparator)
|
||||
const clickHandler = (event: React.MouseEvent) => {
|
||||
onClick && onClick(event)
|
||||
}
|
||||
|
||||
if (!isLink) {
|
||||
return (
|
||||
<span className={`breadcrums-item ${className}`} onClick={clickHandler}>
|
||||
{withoutSepChildren}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={`breadcrums-item ${className}`}
|
||||
href={href}
|
||||
onClick={clickHandler}
|
||||
ref={ref}
|
||||
{...props}>
|
||||
{withoutSepChildren}
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const MemoBreadcrumbsItem = React.memo(BreadcrumbsItem)
|
||||
|
||||
export default withDefaults(MemoBreadcrumbsItem, defaultProps)
|
||||
37
components/breadcrumbs/breadcrumbs-separator.tsx
Normal file
37
components/breadcrumbs/breadcrumbs-separator.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const BreadcrumbsSeparator: React.FC<React.PropsWithChildren<BreadcrumbsProps>> = ({
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`separator ${className}`}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.separator {
|
||||
display: inline-flex;
|
||||
margin: 0 8px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoBreadcrumbsSeparator = React.memo(BreadcrumbsSeparator)
|
||||
|
||||
export default withDefaults(MemoBreadcrumbsSeparator, defaultProps)
|
||||
114
components/breadcrumbs/breadcrumbs.tsx
Normal file
114
components/breadcrumbs/breadcrumbs.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import BreadcrumbsItem from './breadcrumbs-item'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
import { addColorAlpha } from '../utils/color'
|
||||
import { NormalSizes } from '../utils/prop-types'
|
||||
|
||||
interface Props {
|
||||
size: NormalSizes
|
||||
separator?: string | ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
size: 'medium' as NormalSizes,
|
||||
separator: '/',
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const getSize = (size: NormalSizes) => {
|
||||
const sizes: { [key in NormalSizes]: string } = {
|
||||
mini: '.75rem',
|
||||
small: '.875rem',
|
||||
medium: '1rem',
|
||||
large: '1.125rem',
|
||||
}
|
||||
return sizes[size]
|
||||
}
|
||||
|
||||
const Breadcrumbs: React.FC<React.PropsWithChildren<BreadcrumbsProps>> = ({
|
||||
size,
|
||||
separator,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const fontSize = useMemo(() => getSize(size), [size])
|
||||
const hoverColor = useMemo(() => {
|
||||
return addColorAlpha(theme.palette.link, 0.85)
|
||||
}, [theme.palette.link])
|
||||
|
||||
const childrenArray = React.Children.toArray(children)
|
||||
const withSeparatorChildren = childrenArray.map((item, index) => {
|
||||
if (!React.isValidElement(item)) return item
|
||||
const last = childrenArray[index - 1]
|
||||
const lastIsSeparator = React.isValidElement(last) && last.type === BreadcrumbsSeparator
|
||||
const currentIsSeparator = item.type === BreadcrumbsSeparator
|
||||
if (!lastIsSeparator && !currentIsSeparator && index > 0) {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<BreadcrumbsSeparator>{separator}</BreadcrumbsSeparator>
|
||||
{item}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
return (
|
||||
<nav className={className}>
|
||||
{withSeparatorChildren}
|
||||
<style jsx>{`
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: ${theme.palette.accents_4};
|
||||
font-size: ${fontSize};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav :global(.link:hover) {
|
||||
color: ${hoverColor};
|
||||
}
|
||||
|
||||
nav > :global(span:last-of-type) {
|
||||
color: ${theme.palette.accents_6};
|
||||
}
|
||||
|
||||
nav > :global(.separator:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav :global(svg) {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
nav :global(.breadcrums-item) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}</style>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
type MemoBreadcrumbsComponent<P = {}> = React.NamedExoticComponent<P> & {
|
||||
Item: typeof BreadcrumbsItem
|
||||
Separator: typeof BreadcrumbsSeparator
|
||||
}
|
||||
type ComponentProps = Partial<typeof defaultProps> &
|
||||
Omit<Props, keyof typeof defaultProps> &
|
||||
NativeAttrs
|
||||
|
||||
Breadcrumbs.defaultProps = defaultProps
|
||||
|
||||
export default React.memo(Breadcrumbs) as MemoBreadcrumbsComponent<ComponentProps>
|
||||
8
components/breadcrumbs/index.ts
Normal file
8
components/breadcrumbs/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Breadcrumbs from './breadcrumbs'
|
||||
import BreadcrumbsItem from './breadcrumbs-item'
|
||||
import BreadcrumbsSeparator from './breadcrumbs-separator'
|
||||
|
||||
Breadcrumbs.Item = BreadcrumbsItem
|
||||
Breadcrumbs.Separator = BreadcrumbsSeparator
|
||||
|
||||
export default Breadcrumbs
|
||||
@@ -213,7 +213,7 @@ initialize {
|
||||
|
||||
pre code {
|
||||
color: #000;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.25rem;
|
||||
white-space: pre;
|
||||
}
|
||||
@@ -547,7 +547,7 @@ initialize {
|
||||
|
||||
pre code {
|
||||
color: #fff;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.25rem;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ const CssBaseline: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||
|
||||
pre code {
|
||||
color: ${theme.palette.foreground};
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.25rem;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 4.166666666666667%;
|
||||
flex-basis: 4.166666666666667%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -32,6 +33,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 4.166666666666667%;
|
||||
flex-basis: 4.166666666666667%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 8.333333333333334%;
|
||||
flex-basis: 8.333333333333334%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +51,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 12.5%;
|
||||
flex-basis: 12.5%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +60,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 16.666666666666668%;
|
||||
flex-basis: 16.666666666666668%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +69,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 20.833333333333336%;
|
||||
flex-basis: 20.833333333333336%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -90,6 +96,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 4.166666666666667%;
|
||||
flex-basis: 4.166666666666667%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -97,6 +104,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 4.166666666666667%;
|
||||
flex-basis: 4.166666666666667%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +113,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 8.333333333333334%;
|
||||
flex-basis: 8.333333333333334%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +122,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 12.5%;
|
||||
flex-basis: 12.5%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +131,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 16.666666666666668%;
|
||||
flex-basis: 16.666666666666668%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +140,7 @@ exports[`Grid all breakpoint values should be supported 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 20.833333333333336%;
|
||||
flex-basis: 20.833333333333336%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
@@ -159,6 +171,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -166,6 +179,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +188,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +197,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +206,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +215,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -224,6 +242,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -231,6 +250,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +259,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +268,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +277,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +286,7 @@ exports[`Grid css value should be passed through 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
@@ -293,6 +317,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -300,6 +325,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +334,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +343,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +352,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +361,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -358,6 +388,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -365,6 +396,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +405,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +414,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +423,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +432,7 @@ exports[`Grid decimal spacing should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
@@ -427,6 +463,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -434,6 +471,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +480,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,6 +489,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +498,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,6 +507,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"item mock \\"><div class=\\"item mock \\">test<style>
|
||||
@@ -492,6 +534,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -499,6 +542,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +551,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,6 +560,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,6 +569,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +578,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"item mock \\"><div class=\\"item mock \\">test<style>
|
||||
@@ -557,6 +605,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -564,6 +613,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +622,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +631,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,6 +640,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,6 +649,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"item mock \\"><div class=\\"item mock \\">test<style>
|
||||
@@ -622,6 +676,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -629,6 +684,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,6 +693,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,6 +702,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,6 +711,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,6 +720,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -687,6 +747,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -694,6 +755,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,6 +764,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,6 +773,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,6 +782,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,6 +791,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>,<style>
|
||||
@@ -752,6 +818,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -759,6 +826,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,6 +835,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,6 +844,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,6 +853,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,6 +862,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>,<style>
|
||||
@@ -817,6 +889,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -824,6 +897,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +906,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,6 +915,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,6 +924,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,6 +933,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>,<style>
|
||||
@@ -882,6 +960,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -889,6 +968,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,6 +977,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,6 +986,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,6 +995,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,6 +1004,7 @@ exports[`Grid nested components should be supported 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
@@ -951,6 +1035,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -958,6 +1043,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,6 +1052,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,6 +1061,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,6 +1070,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -990,6 +1079,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"item mock \\">test<style>
|
||||
@@ -1016,6 +1106,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -1023,6 +1114,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1031,6 +1123,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,6 +1132,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1047,6 +1141,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,6 +1150,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -1081,6 +1177,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -1088,6 +1185,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,6 +1194,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1104,6 +1203,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,6 +1212,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1120,6 +1221,7 @@ exports[`Grid should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
@@ -1150,6 +1252,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 100%;
|
||||
flex-basis: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -1157,6 +1260,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 100%;
|
||||
flex-basis: 100%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,6 +1269,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,6 +1278,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1181,6 +1287,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,6 +1296,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"item xs mock \\">test<style>
|
||||
@@ -1215,6 +1323,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 0;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -1222,6 +1331,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 0;
|
||||
max-width: 0;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1230,6 +1340,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,6 +1349,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,6 +1358,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1254,6 +1367,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><style>
|
||||
@@ -1280,6 +1394,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -1287,6 +1402,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,6 +1411,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1303,6 +1420,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1311,6 +1429,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,6 +1438,7 @@ exports[`Grid should work correctly when size exceeds 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div>"
|
||||
|
||||
@@ -111,4 +111,10 @@ describe('Grid', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('Grid should be hidden when value is 0', () => {
|
||||
let wrapper = mount(<Grid.Container xs={0} />)
|
||||
expect(wrapper.find('.item').hasClass('xs')).toBeTruthy()
|
||||
expect(wrapper.find('.item').html()).toContain('display: none')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,19 +32,23 @@ type ItemLayoutValue = {
|
||||
grow: number
|
||||
width: string
|
||||
basis: string
|
||||
display: string
|
||||
}
|
||||
const getItemLayout = (val: BreakpointsValue): ItemLayoutValue => {
|
||||
const display = val === 0 ? 'none' : 'flex'
|
||||
if (typeof val === 'number') {
|
||||
const width = (100 / 24) * val
|
||||
const ratio = width > 100 ? '100%' : width < 0 ? '0' : `${width}%`
|
||||
return {
|
||||
grow: 0,
|
||||
display,
|
||||
width: ratio,
|
||||
basis: ratio,
|
||||
}
|
||||
}
|
||||
return {
|
||||
grow: 1,
|
||||
display,
|
||||
width: '100%',
|
||||
basis: '0',
|
||||
}
|
||||
@@ -77,11 +81,11 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
xl,
|
||||
}
|
||||
const classString = Object.keys(aligns).reduce((pre, name) => {
|
||||
if (Boolean(aligns[name]) && aligns[name] !== 0) return `${pre} ${name}`
|
||||
if (aligns[name] !== undefined && aligns[name] !== false) return `${pre} ${name}`
|
||||
return pre
|
||||
}, '')
|
||||
return classString.trim()
|
||||
}, [justify, direction, alignItems, alignContent])
|
||||
}, [justify, direction, alignItems, alignContent, xs, sm, md, lg, xl])
|
||||
|
||||
const layout = useMemo<
|
||||
{
|
||||
@@ -125,6 +129,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.xs.grow};
|
||||
max-width: ${layout.xs.width};
|
||||
flex-basis: ${layout.xs.basis};
|
||||
display: ${layout.xs.display};
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.xs.max}) {
|
||||
@@ -132,6 +137,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.xs.grow};
|
||||
max-width: ${layout.xs.width};
|
||||
flex-basis: ${layout.xs.basis};
|
||||
display: ${layout.xs.display};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +146,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.sm.grow};
|
||||
max-width: ${layout.sm.width};
|
||||
flex-basis: ${layout.sm.basis};
|
||||
display: ${layout.sm.display};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +155,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.md.grow};
|
||||
max-width: ${layout.md.width};
|
||||
flex-basis: ${layout.md.basis};
|
||||
display: ${layout.md.display};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +164,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.lg.grow};
|
||||
max-width: ${layout.lg.width};
|
||||
flex-basis: ${layout.lg.basis};
|
||||
display: ${layout.lg.display};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +173,7 @@ const GridBasicItem: React.FC<React.PropsWithChildren<GridBasicItemProps>> = ({
|
||||
flex-grow: ${layout.xl.grow};
|
||||
max-width: ${layout.xl.width};
|
||||
flex-basis: ${layout.xl.basis};
|
||||
display: ${layout.xl.display};
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
@@ -59,4 +59,15 @@ describe('Image Browser', () => {
|
||||
const wrapper = mount(<Image.Browser />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('anchor props should be passed through', () => {
|
||||
const anchorRel = 'noreferrer'
|
||||
const wrapper = mount(
|
||||
<Image.Browser url={link} anchorProps={{ rel: anchorRel }}>
|
||||
<Image src={url} />
|
||||
</Image.Browser>,
|
||||
)
|
||||
const rel = wrapper.find('a').getDOMNode().getAttribute('rel')
|
||||
expect(anchorRel).toEqual(anchorRel)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import Link from '../link'
|
||||
import { Props as LinkProps } from '../link/link'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import ImageBrowserHttpsIcon from './image-browser-https-icon'
|
||||
import { getBrowserColors, BrowserColors } from './styles'
|
||||
|
||||
type AnchorProps = Omit<React.AnchorHTMLAttributes<any>, keyof LinkProps>
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
url?: string
|
||||
showFullLink?: boolean
|
||||
invert?: boolean
|
||||
anchorProps?: AnchorProps
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
showFullLink: false,
|
||||
anchorProps: {} as AnchorProps,
|
||||
invert: false,
|
||||
}
|
||||
|
||||
@@ -42,12 +47,17 @@ const getTitle = (title: string, colors: BrowserColors) => (
|
||||
</div>
|
||||
)
|
||||
|
||||
const getAddressInput = (url: string, showFullLink: boolean, colors: BrowserColors) => (
|
||||
const getAddressInput = (
|
||||
url: string,
|
||||
showFullLink: boolean,
|
||||
colors: BrowserColors,
|
||||
anchorProps: AnchorProps,
|
||||
) => (
|
||||
<div className="address-input">
|
||||
<span className="https">
|
||||
<ImageBrowserHttpsIcon />
|
||||
</span>
|
||||
<Link href={url} title={url} target="_blank">
|
||||
<Link href={url} title={url} target="_blank" {...anchorProps}>
|
||||
{showFullLink ? url : getHostFromUrl(url)}
|
||||
</Link>
|
||||
<style jsx>{`
|
||||
@@ -94,16 +104,16 @@ const getAddressInput = (url: string, showFullLink: boolean, colors: BrowserColo
|
||||
|
||||
const ImageBrowser = React.forwardRef<HTMLDivElement, React.PropsWithChildren<ImageBrowserProps>>(
|
||||
(
|
||||
{ url, title, children, showFullLink, invert, className, ...props },
|
||||
{ url, title, children, showFullLink, invert, anchorProps, className, ...props },
|
||||
ref: React.Ref<HTMLDivElement>,
|
||||
) => {
|
||||
const theme = useTheme()
|
||||
const colors = useMemo(() => getBrowserColors(invert, theme.palette), [invert, theme.palette])
|
||||
const input = useMemo(() => {
|
||||
if (url) return getAddressInput(url, showFullLink, colors)
|
||||
if (url) return getAddressInput(url, showFullLink, colors, anchorProps)
|
||||
if (title) return getTitle(title, colors)
|
||||
return null
|
||||
}, [url, showFullLink, title, colors])
|
||||
}, [url, showFullLink, title, colors, anchorProps])
|
||||
|
||||
return (
|
||||
<div className={`bowser ${className}`} ref={ref} {...props}>
|
||||
|
||||
@@ -58,3 +58,5 @@ export { default as User } from './user'
|
||||
export { default as Page } from './page'
|
||||
export { default as Grid } from './grid'
|
||||
export { default as ButtonGroup } from './button-group'
|
||||
export { default as Breadcrumbs } from './breadcrumbs'
|
||||
export { default as Pagination } from './pagination'
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import React, { Dispatch, MutableRefObject, SetStateAction } from 'react'
|
||||
import useCurrentState from '../utils/use-current-state'
|
||||
|
||||
export type BindingsChangeTarget =
|
||||
| React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
| string
|
||||
|
||||
const useInput = (
|
||||
initialValue: string,
|
||||
): {
|
||||
@@ -10,7 +14,7 @@ const useInput = (
|
||||
reset: () => void
|
||||
bindings: {
|
||||
value: string
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
|
||||
onChange: (event: BindingsChangeTarget) => void
|
||||
}
|
||||
} => {
|
||||
const [state, setState, currentRef] = useCurrentState<string>(initialValue)
|
||||
@@ -22,8 +26,12 @@ const useInput = (
|
||||
reset: () => setState(initialValue),
|
||||
bindings: {
|
||||
value: state,
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setState(event.target.value)
|
||||
onChange: (event: BindingsChangeTarget) => {
|
||||
if (typeof event === 'object' && event.target) {
|
||||
setState(event.target.value)
|
||||
} else {
|
||||
setState(event as string)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import useTheme from '../styles/use-theme'
|
||||
import useWarning from '../utils/use-warning'
|
||||
import LinkIcon from './icon'
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
href?: string
|
||||
color?: boolean
|
||||
pure?: boolean
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Pagination should render correctly 1`] = `
|
||||
"<nav><li><button class=\\" disabled\\">prev</button><style>
|
||||
li {
|
||||
margin-right: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
text-transform: capitalize;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: var(--pagination-size);
|
||||
min-width: var(--pagination-size);
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
color: #0070f3;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
transition: all linear 200ms 0ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: rgba(0, 112, 243, 0.1);
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: bold;
|
||||
background-color: #0070f3;
|
||||
color: #fff;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: rgba(0, 112, 243, 0.8);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #888;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
button :global(svg) {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
</style></li><li><button class=\\"active \\">1</button><style>
|
||||
li {
|
||||
margin-right: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
text-transform: capitalize;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: var(--pagination-size);
|
||||
min-width: var(--pagination-size);
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
color: #0070f3;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
transition: all linear 200ms 0ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: rgba(0, 112, 243, 0.1);
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: bold;
|
||||
background-color: #0070f3;
|
||||
color: #fff;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: rgba(0, 112, 243, 0.8);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #888;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
button :global(svg) {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
</style></li><li><button class=\\" disabled\\">next</button><style>
|
||||
li {
|
||||
margin-right: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
text-transform: capitalize;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: var(--pagination-size);
|
||||
min-width: var(--pagination-size);
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
color: #0070f3;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
transition: all linear 200ms 0ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: rgba(0, 112, 243, 0.1);
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: bold;
|
||||
background-color: #0070f3;
|
||||
color: #fff;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: rgba(0, 112, 243, 0.8);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #888;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
button :global(svg) {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
</style></li></nav><style>
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-variant: tabular-nums;
|
||||
font-feature-settings: 'tnum';
|
||||
font-size: .875rem;
|
||||
--pagination-size: 2rem;
|
||||
}
|
||||
|
||||
nav :global(button:last-of-type) {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>"
|
||||
`;
|
||||
148
components/pagination/__tests__/pagination.test.tsx
Normal file
148
components/pagination/__tests__/pagination.test.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Pagination } from 'components'
|
||||
import { act } from 'react-dom/test-utils'
|
||||
import { updateWrapper } from 'tests/utils'
|
||||
|
||||
describe('Pagination', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = mount(<Pagination />)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('the specified page should be activated', async () => {
|
||||
const wrapper = mount(<Pagination count={10} initialPage={2} />)
|
||||
expect(wrapper.find('.active').text()).toEqual('2')
|
||||
await act(async () => {
|
||||
wrapper.setProps({ page: 10 })
|
||||
})
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(wrapper.find('.active').text()).toEqual('10')
|
||||
})
|
||||
|
||||
it('should trigger change event', async () => {
|
||||
let current = 1
|
||||
const handler = jest.fn().mockImplementation(val => (current = val))
|
||||
const wrapper = mount(<Pagination count={10} initialPage={2} onChange={handler} />)
|
||||
|
||||
await act(async () => {
|
||||
wrapper.setProps({ page: 10 })
|
||||
})
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(handler).toHaveBeenCalled()
|
||||
expect(current).toEqual(10)
|
||||
|
||||
const btns = wrapper.find('button')
|
||||
btns.at(0).simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(current).toEqual(9)
|
||||
|
||||
btns.at(btns.length - 1).simulate('click')
|
||||
btns.at(btns.length - 1).simulate('click')
|
||||
btns.at(btns.length - 1).simulate('click')
|
||||
btns.at(btns.length - 1).simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(current).toEqual(10)
|
||||
handler.mockRestore()
|
||||
})
|
||||
|
||||
it('the page should be rendered to follow the specified limit', async () => {
|
||||
const wrapper = mount(<Pagination count={20} limit={20} />)
|
||||
expect(wrapper.find('button').length).toBeGreaterThanOrEqual(20)
|
||||
await act(async () => {
|
||||
wrapper.setProps({ limit: 5 })
|
||||
})
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(wrapper.find('button').length).toBeLessThanOrEqual(10)
|
||||
})
|
||||
|
||||
it('should be render all pages when limit is greater than the total', async () => {
|
||||
const handler = jest.fn()
|
||||
const wrapper = mount(<Pagination count={15} limit={40} onChange={handler} />)
|
||||
expect(wrapper.find('button').length).toBeGreaterThanOrEqual(15)
|
||||
wrapper.find('button').at(10).simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
|
||||
expect(handler).toHaveBeenCalled()
|
||||
handler.mockRestore()
|
||||
})
|
||||
|
||||
it('omit pages by limit value', async () => {
|
||||
const wrapper = mount(<Pagination count={20} limit={5} />)
|
||||
const btn4 = wrapper.find('button').at(4)
|
||||
expect(btn4.text()).toEqual('4')
|
||||
btn4.simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
let btns = wrapper.find('button').map(btn => btn.text())
|
||||
expect(btns.includes('2')).not.toBeTruthy()
|
||||
expect(btns.includes('1')).toBeTruthy()
|
||||
expect(btns.includes('3')).toBeTruthy()
|
||||
expect(btns.includes('4')).toBeTruthy()
|
||||
expect(btns.includes('5')).toBeTruthy()
|
||||
expect(btns.includes('6')).not.toBeTruthy()
|
||||
expect(btns.includes('20')).toBeTruthy()
|
||||
|
||||
const btn5 = wrapper.find('button').at(5)
|
||||
expect(btn5.text()).toEqual('5')
|
||||
btn5.simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
btns = wrapper.find('button').map(btn => btn.text())
|
||||
expect(btns.includes('1')).toBeTruthy()
|
||||
expect(btns.includes('2')).not.toBeTruthy()
|
||||
expect(btns.includes('3')).not.toBeTruthy()
|
||||
expect(btns.includes('4')).toBeTruthy()
|
||||
expect(btns.includes('5')).toBeTruthy()
|
||||
expect(btns.includes('6')).toBeTruthy()
|
||||
expect(btns.includes('7')).not.toBeTruthy()
|
||||
expect(btns.includes('8')).not.toBeTruthy()
|
||||
expect(btns.includes('20')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should trigger change event when ellipsis clicked', async () => {
|
||||
let current = 20
|
||||
const handler = jest.fn().mockImplementation(val => (current = val))
|
||||
const wrapper = mount(<Pagination count={20} initialPage={20} onChange={handler} />)
|
||||
const btn = wrapper.find('svg').at(0).parents('button')
|
||||
btn.at(0).simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(handler).toHaveBeenCalled()
|
||||
expect(current).toEqual(15)
|
||||
|
||||
await act(async () => {
|
||||
wrapper.setProps({ page: 1 })
|
||||
})
|
||||
await updateWrapper(wrapper, 200)
|
||||
const lastBtn = wrapper.find('svg').at(0).parents('button')
|
||||
lastBtn.at(0).simulate('click')
|
||||
await updateWrapper(wrapper, 200)
|
||||
expect(current).toEqual(1 + 5)
|
||||
})
|
||||
|
||||
it('another SVG should be displayed when the mouse is moved in', async () => {
|
||||
const wrapper = mount(<Pagination count={20} initialPage={20} />)
|
||||
const svg = wrapper.find('svg').at(0)
|
||||
const btn = svg.parents('button')
|
||||
|
||||
const html = svg.html()
|
||||
btn.simulate('mouseEnter')
|
||||
await updateWrapper(wrapper)
|
||||
expect(html).not.toEqual(wrapper.find('svg').at(0).html())
|
||||
|
||||
btn.simulate('mouseLeave')
|
||||
await updateWrapper(wrapper)
|
||||
expect(html).toEqual(wrapper.find('svg').at(0).html())
|
||||
})
|
||||
|
||||
it('custom buttons should be display', () => {
|
||||
const wrapper = mount(
|
||||
<Pagination count={20}>
|
||||
<Pagination.Previous>custom-prev</Pagination.Previous>
|
||||
<Pagination.Next>custom-next</Pagination.Next>
|
||||
</Pagination>,
|
||||
)
|
||||
const btns = wrapper.find('button')
|
||||
expect(btns.at(0).text()).toEqual('custom-prev')
|
||||
expect(btns.at(btns.length - 1).text()).toEqual('custom-next')
|
||||
})
|
||||
})
|
||||
8
components/pagination/index.ts
Normal file
8
components/pagination/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Pagination from './pagination'
|
||||
import PaginationPrevious from './pagination-previous'
|
||||
import PaginationNext from './pagination-next'
|
||||
|
||||
Pagination.Previous = PaginationPrevious
|
||||
Pagination.Next = PaginationNext
|
||||
|
||||
export default Pagination
|
||||
18
components/pagination/pagination-context.ts
Normal file
18
components/pagination/pagination-context.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { tuple } from '../utils/prop-types'
|
||||
const paginationUpdateTypes = tuple('prev', 'next', 'click')
|
||||
|
||||
export type PaginationUpdateType = typeof paginationUpdateTypes[number]
|
||||
|
||||
export interface PaginationConfig {
|
||||
isFirst?: boolean
|
||||
isLast?: boolean
|
||||
update?: (type: PaginationUpdateType) => void
|
||||
}
|
||||
|
||||
const defaultContext = {}
|
||||
|
||||
export const PaginationContext = React.createContext<PaginationConfig>(defaultContext)
|
||||
|
||||
export const usePaginationContext = (): PaginationConfig =>
|
||||
React.useContext<PaginationConfig>(PaginationContext)
|
||||
60
components/pagination/pagination-ellipsis.tsx
Normal file
60
components/pagination/pagination-ellipsis.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react'
|
||||
import PaginationItem from './pagination-item'
|
||||
|
||||
interface Props {
|
||||
isBefore?: boolean
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
const PaginationEllipsis: React.FC<Props> = ({ isBefore, onClick }) => {
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
return (
|
||||
<PaginationItem
|
||||
onClick={e => onClick && onClick(e)}
|
||||
onMouseEnter={() => setShowMore(true)}
|
||||
onMouseLeave={() => setShowMore(false)}>
|
||||
{showMore ? (
|
||||
<svg
|
||||
className="more"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
shapeRendering="geometricPrecision">
|
||||
<path d="M13 17l5-5-5-5" />
|
||||
<path d="M6 17l5-5-5-5" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
shapeRendering="geometricPrecision">
|
||||
<circle cx="12" cy="12" r="1" fill="currentColor" />
|
||||
<circle cx="19" cy="12" r="1" fill="currentColor" />
|
||||
<circle cx="5" cy="12" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
)}
|
||||
|
||||
<style jsx>{`
|
||||
svg {
|
||||
color: currentColor;
|
||||
stroke: currentColor;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.more {
|
||||
transform: rotate(${isBefore ? '180deg' : '0deg'});
|
||||
}
|
||||
`}</style>
|
||||
</PaginationItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationEllipsis
|
||||
101
components/pagination/pagination-item.tsx
Normal file
101
components/pagination/pagination-item.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { addColorAlpha } from '../utils/color'
|
||||
|
||||
interface Props {
|
||||
active?: boolean
|
||||
disabled?: boolean
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.ButtonHTMLAttributes<any>, keyof Props>
|
||||
export type PaginationItemProps = Props & NativeAttrs
|
||||
|
||||
const PaginationItem: React.FC<React.PropsWithChildren<PaginationItemProps>> = ({
|
||||
active,
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const [hover, activeHover] = useMemo(
|
||||
() => [addColorAlpha(theme.palette.success, 0.1), addColorAlpha(theme.palette.success, 0.8)],
|
||||
[theme.palette.success],
|
||||
)
|
||||
const clickHandler = (event: React.MouseEvent) => {
|
||||
if (disabled) return
|
||||
onClick && onClick(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<button
|
||||
className={`${active ? 'active' : ''} ${disabled ? 'disabled' : ''}`}
|
||||
onClick={clickHandler}
|
||||
{...props}>
|
||||
{children}
|
||||
</button>
|
||||
<style jsx>{`
|
||||
li {
|
||||
margin-right: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
text-transform: capitalize;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: var(--pagination-size);
|
||||
min-width: var(--pagination-size);
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
color: ${theme.palette.success};
|
||||
border-radius: ${theme.layout.radius};
|
||||
background-color: ${theme.palette.background};
|
||||
transition: all linear 200ms 0ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: ${hover};
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: bold;
|
||||
background-color: ${theme.palette.success};
|
||||
color: ${theme.palette.background};
|
||||
box-shadow: ${theme.expressiveness.shadowSmall};
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: ${activeHover};
|
||||
box-shadow: ${theme.expressiveness.shadowMedium};
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: ${theme.palette.accents_4};
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled:hover {
|
||||
background-color: ${theme.palette.accents_2};
|
||||
}
|
||||
|
||||
button :global(svg) {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
`}</style>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationItem
|
||||
19
components/pagination/pagination-next.tsx
Normal file
19
components/pagination/pagination-next.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import PaginationItem from './pagination-item'
|
||||
import { usePaginationContext } from './pagination-context'
|
||||
|
||||
export type PaginationNextProps = React.ButtonHTMLAttributes<any>
|
||||
|
||||
const PaginationNext: React.FC<React.PropsWithChildren<PaginationNextProps>> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const { update, isLast } = usePaginationContext()
|
||||
return (
|
||||
<PaginationItem onClick={() => update && update('next')} disabled={isLast} {...props}>
|
||||
{children}
|
||||
</PaginationItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationNext
|
||||
104
components/pagination/pagination-pages.tsx
Normal file
104
components/pagination/pagination-pages.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react'
|
||||
import PaginationItem from './pagination-item'
|
||||
import PaginationEllipsis from './pagination-ellipsis'
|
||||
|
||||
interface Props {
|
||||
limit: number
|
||||
count: number
|
||||
current: number
|
||||
setPage: Dispatch<SetStateAction<number>>
|
||||
}
|
||||
|
||||
const PaginationPages: React.FC<Props> = ({ limit, count, current, setPage }) => {
|
||||
const showPages = useMemo(() => {
|
||||
const oddLimit = limit % 2 === 0 ? limit - 1 : limit
|
||||
return oddLimit - 2
|
||||
}, [limit])
|
||||
const middleNumber = (showPages + 1) / 2
|
||||
|
||||
const [showBeforeEllipsis, showAfterEllipsis] = useMemo(() => {
|
||||
const showEllipsis = count > limit
|
||||
return [
|
||||
showEllipsis && current > middleNumber + 1,
|
||||
showEllipsis && current < count - middleNumber,
|
||||
]
|
||||
}, [current, showPages, middleNumber, count, limit])
|
||||
const pagesArray = useMemo(() => [...new Array(showPages)], [showPages])
|
||||
|
||||
const renderItem = useCallback(
|
||||
(value: number, active: number) => (
|
||||
<PaginationItem
|
||||
key={`pagination-item-${value}`}
|
||||
active={value === active}
|
||||
onClick={() => setPage(value)}>
|
||||
{value}
|
||||
</PaginationItem>
|
||||
),
|
||||
[],
|
||||
)
|
||||
const startPages = pagesArray.map((_, index) => {
|
||||
const value = index + 2
|
||||
return renderItem(value, current)
|
||||
})
|
||||
const middlePages = pagesArray.map((_, index) => {
|
||||
const middleIndexNumber = middleNumber - (index + 1)
|
||||
const value = current - middleIndexNumber
|
||||
return (
|
||||
<PaginationItem
|
||||
key={`pagination-middle-${index}`}
|
||||
active={index + 1 === middleNumber}
|
||||
onClick={() => setPage(value)}>
|
||||
{value}
|
||||
</PaginationItem>
|
||||
)
|
||||
})
|
||||
const endPages = pagesArray.map((_, index) => {
|
||||
const value = count - (showPages - index)
|
||||
return renderItem(value, current)
|
||||
})
|
||||
if (count <= limit) {
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
return (
|
||||
<>
|
||||
{[...new Array(count)].map((_, index) => {
|
||||
const value = index + 1
|
||||
return (
|
||||
<PaginationItem
|
||||
key={`pagination-item-${value}`}
|
||||
active={value === current}
|
||||
onClick={() => setPage(value)}>
|
||||
{value}
|
||||
</PaginationItem>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
/* eslint-enable */
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{renderItem(1, current)}
|
||||
{showBeforeEllipsis && (
|
||||
<PaginationEllipsis
|
||||
key="pagination-ellipsis-before"
|
||||
isBefore
|
||||
onClick={() => setPage(last => (last - 5 >= 1 ? last - 5 : 1))}
|
||||
/>
|
||||
)}
|
||||
{showBeforeEllipsis && showAfterEllipsis
|
||||
? middlePages
|
||||
: showBeforeEllipsis
|
||||
? endPages
|
||||
: startPages}
|
||||
{showAfterEllipsis && (
|
||||
<PaginationEllipsis
|
||||
key="pagination-ellipsis-after"
|
||||
onClick={() => setPage(last => (last + 5 <= count ? last + 5 : count))}
|
||||
/>
|
||||
)}
|
||||
{renderItem(count, current)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationPages
|
||||
19
components/pagination/pagination-previous.tsx
Normal file
19
components/pagination/pagination-previous.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import PaginationItem from './pagination-item'
|
||||
import { usePaginationContext } from './pagination-context'
|
||||
|
||||
export type PaginationNextProps = React.ButtonHTMLAttributes<any>
|
||||
|
||||
const PaginationPrevious: React.FC<React.PropsWithChildren<PaginationNextProps>> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const { update, isFirst } = usePaginationContext()
|
||||
return (
|
||||
<PaginationItem onClick={() => update && update('prev')} disabled={isFirst} {...props}>
|
||||
{children}
|
||||
</PaginationItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationPrevious
|
||||
142
components/pagination/pagination.tsx
Normal file
142
components/pagination/pagination.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import PaginationPrevious from './pagination-previous'
|
||||
import PaginationNext from './pagination-next'
|
||||
import PaginationPages from './pagination-pages'
|
||||
import { PaginationContext, PaginationConfig, PaginationUpdateType } from './pagination-context'
|
||||
import useCurrentState from '../utils/use-current-state'
|
||||
import { pickChild } from '../utils/collections'
|
||||
import { NormalSizes } from '../utils/prop-types'
|
||||
|
||||
interface Props {
|
||||
size?: NormalSizes
|
||||
page?: number
|
||||
initialPage?: number
|
||||
count?: number
|
||||
limit?: number
|
||||
onChange?: (val: number) => void
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
size: 'medium' as NormalSizes,
|
||||
initialPage: 1,
|
||||
count: 1,
|
||||
limit: 7,
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type PaginationProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
type PaginationSize = {
|
||||
font: string
|
||||
width: string
|
||||
}
|
||||
|
||||
const getPaginationSizes = (size: NormalSizes) => {
|
||||
const sizes: { [key in NormalSizes]: PaginationSize } = {
|
||||
mini: {
|
||||
font: '.75rem',
|
||||
width: '1.25rem',
|
||||
},
|
||||
small: {
|
||||
font: '.75rem',
|
||||
width: '1.65rem',
|
||||
},
|
||||
medium: {
|
||||
font: '.875rem',
|
||||
width: '2rem',
|
||||
},
|
||||
large: {
|
||||
font: '1rem',
|
||||
width: '2.4rem',
|
||||
},
|
||||
}
|
||||
return sizes[size]
|
||||
}
|
||||
|
||||
const Pagination: React.FC<React.PropsWithChildren<PaginationProps>> = ({
|
||||
page: customPage,
|
||||
initialPage,
|
||||
count,
|
||||
limit,
|
||||
size,
|
||||
children,
|
||||
onChange,
|
||||
}) => {
|
||||
const [page, setPage, pageRef] = useCurrentState(initialPage)
|
||||
const [, prevChildren] = pickChild(children, PaginationPrevious)
|
||||
const [, nextChildren] = pickChild(children, PaginationNext)
|
||||
|
||||
const [prevItem, nextItem] = useMemo(() => {
|
||||
const hasChildren = (c: any) => React.Children.count(c) > 0
|
||||
const prevDefault = <PaginationPrevious>prev</PaginationPrevious>
|
||||
const nextDefault = <PaginationNext>next</PaginationNext>
|
||||
return [
|
||||
hasChildren(prevChildren) ? prevChildren : prevDefault,
|
||||
hasChildren(nextChildren) ? nextChildren : nextDefault,
|
||||
]
|
||||
}, [prevChildren, nextChildren])
|
||||
const { font, width } = useMemo(() => getPaginationSizes(size), [size])
|
||||
|
||||
const update = (type: PaginationUpdateType) => {
|
||||
if (type === 'prev' && pageRef.current > 1) {
|
||||
setPage(last => last - 1)
|
||||
}
|
||||
if (type === 'next' && pageRef.current < count) {
|
||||
setPage(last => last + 1)
|
||||
}
|
||||
}
|
||||
const values = useMemo<PaginationConfig>(
|
||||
() => ({
|
||||
isFirst: page <= 1,
|
||||
isLast: page >= count,
|
||||
update,
|
||||
}),
|
||||
[page],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
onChange && onChange(page)
|
||||
}, [page])
|
||||
useEffect(() => {
|
||||
if (customPage !== undefined) {
|
||||
setPage(customPage)
|
||||
}
|
||||
}, [customPage])
|
||||
|
||||
return (
|
||||
<PaginationContext.Provider value={values}>
|
||||
<nav>
|
||||
{prevItem}
|
||||
<PaginationPages count={count} current={page} limit={limit} setPage={setPage} />
|
||||
{nextItem}
|
||||
</nav>
|
||||
<style jsx>{`
|
||||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-variant: tabular-nums;
|
||||
font-feature-settings: 'tnum';
|
||||
font-size: ${font};
|
||||
--pagination-size: ${width};
|
||||
}
|
||||
|
||||
nav :global(button:last-of-type) {
|
||||
margin-right: 0;
|
||||
}
|
||||
`}</style>
|
||||
</PaginationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
type MemoPaginationComponent<P = {}> = React.NamedExoticComponent<P> & {
|
||||
Previous: typeof PaginationPrevious
|
||||
Next: typeof PaginationNext
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> &
|
||||
Omit<Props, keyof typeof defaultProps> &
|
||||
NativeAttrs
|
||||
|
||||
Pagination.defaultProps = defaultProps
|
||||
|
||||
export default React.memo(Pagination) as MemoPaginationComponent<ComponentProps>
|
||||
@@ -61,6 +61,28 @@ describe('Radio Group', () => {
|
||||
changeHandler.mockRestore()
|
||||
})
|
||||
|
||||
it('the radio value should be support number', () => {
|
||||
let value = ''
|
||||
const changeHandler = jest.fn().mockImplementation(val => (value = val))
|
||||
const wrapper = mount(
|
||||
<Radio.Group onChange={changeHandler}>
|
||||
<Radio value={5}>Option 1</Radio>
|
||||
<Radio value={10}>Option 2</Radio>
|
||||
</Radio.Group>,
|
||||
)
|
||||
|
||||
wrapper
|
||||
.find('input')
|
||||
.at(0)
|
||||
.simulate('change', {
|
||||
...nativeEvent,
|
||||
target: { checked: true },
|
||||
})
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(value).toEqual(5)
|
||||
changeHandler.mockRestore()
|
||||
})
|
||||
|
||||
it('should ignore events when disabled', () => {
|
||||
const changeHandler = jest.fn()
|
||||
const wrapper = mount(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface RadioConfig {
|
||||
updateState?: (value: string) => void
|
||||
updateState?: (value: string | number) => void
|
||||
disabledAll: boolean
|
||||
value?: string
|
||||
value?: string | number
|
||||
inGroup: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import { RadioContext } from './radio-context'
|
||||
import { NormalSizes } from 'components/utils/prop-types'
|
||||
|
||||
interface Props {
|
||||
value?: string
|
||||
initialValue?: string
|
||||
value?: string | number
|
||||
initialValue?: string | number
|
||||
disabled?: boolean
|
||||
size?: NormalSizes
|
||||
onChange?: (value: string) => void
|
||||
onChange?: (value: string | number) => void
|
||||
className?: string
|
||||
useRow?: boolean
|
||||
}
|
||||
@@ -44,8 +44,8 @@ const RadioGroup: React.FC<React.PropsWithChildren<RadioGroupProps>> = ({
|
||||
useRow,
|
||||
...props
|
||||
}) => {
|
||||
const [selfVal, setSelfVal] = useState<string | undefined>(initialValue)
|
||||
const updateState = (nextValue: string) => {
|
||||
const [selfVal, setSelfVal] = useState<string | number | undefined>(initialValue)
|
||||
const updateState = (nextValue: string | number) => {
|
||||
setSelfVal(nextValue)
|
||||
onChange && onChange(nextValue)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface RadioEvent {
|
||||
|
||||
interface Props {
|
||||
checked?: boolean
|
||||
value?: string
|
||||
value?: string | number
|
||||
size?: NormalSizes
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
@@ -77,7 +77,7 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({
|
||||
}
|
||||
setSelfChecked(!selfChecked)
|
||||
if (inGroup) {
|
||||
updateState && updateState(radioValue as string)
|
||||
updateState && updateState(radioValue as string | number)
|
||||
}
|
||||
onChange && onChange(selfEvent)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
@@ -39,6 +40,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +58,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +67,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +76,7 @@ exports[`Select Multiple should render correctly 1`] = `
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
flex-basis: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style></div><div class=\\"icon\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.25em\\" height=\\"1.25em\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" fill=\\"none\\" shape-rendering=\\"geometricPrecision\\"><path d=\\"M6 9l6 6 6-6\\"></path><style>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zeit-ui/react",
|
||||
"version": "1.6.3",
|
||||
"version": "1.7.0-canary.5",
|
||||
"main": "dist/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -40,10 +40,25 @@ AutoComplete control of input field.
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Only allow selected"
|
||||
desc="You can only change the value of the input by select."
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return <AutoComplete disableFreeSolo options={options} initialValue="sydney" />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
desc="Update the contents of drop-down list based on input."
|
||||
title="search"
|
||||
scope={{ AutoComplete, useState }}
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
@@ -51,7 +66,7 @@ AutoComplete control of input field.
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = useState()
|
||||
const [options, setOptions] = React.useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
@@ -216,6 +231,31 @@ AutoComplete control of input field.
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Creatable"
|
||||
desc="Add an entry to be selected for any value."
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = React.useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
const createOptions = [{
|
||||
value: currentValue, label: 'Add "' + currentValue + '"'
|
||||
}]
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
const optionsWithCreatable = relatedOptions.length !== 0 ? relatedOptions : createOptions
|
||||
setOptions(optionsWithCreatable)
|
||||
}
|
||||
return <AutoComplete options={options} clearable disableFreeSolo placeholder="Enter here" onSearch={searchHandler} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/auto-complete.mdx">
|
||||
<Attributes.Title>AutoComplete.Props</Attributes.Title>
|
||||
|
||||
@@ -235,6 +275,7 @@ AutoComplete control of input field.
|
||||
| **dropdownClassName** | className of dropdown box | `string` | - | - |
|
||||
| **dropdownStyle** | style of dropdown box | `object` | - | - |
|
||||
| **disableMatchWidth** | disable Option from follow parent width | `boolean` | - | `false` |
|
||||
| **disableFreeSolo** | only values can be changed through Select | `boolean` | - | `false` |
|
||||
| ... | native props | `InputHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="AutoComplete.Option">AutoComplete.Item</Attributes.Title>
|
||||
|
||||
136
pages/en-us/components/breadcrumbs.mdx
Normal file
136
pages/en-us/components/breadcrumbs.mdx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Breadcrumbs, Spacer } from 'components'
|
||||
import NextLink from 'next/link'
|
||||
import Home from '@zeit-ui/react-icons/home'
|
||||
import Inbox from '@zeit-ui/react-icons/inbox'
|
||||
|
||||
export const meta = {
|
||||
title: 'breadcrumbs',
|
||||
group: 'Navigation',
|
||||
}
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
Show where users are in the application.
|
||||
|
||||
<Playground
|
||||
scope={{ Breadcrumbs }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Sizes"
|
||||
scope={{ Breadcrumbs }}
|
||||
code={`
|
||||
<>
|
||||
<Breadcrumbs size="mini">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="small">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="medium">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="large">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Separator"
|
||||
desc="Custom separator in characters."
|
||||
scope={{ Breadcrumbs, Spacer }}
|
||||
code={`
|
||||
<>
|
||||
<Breadcrumbs separator="-">
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Spacer y={.5} />
|
||||
<Breadcrumbs separator=">">
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Separator>:</Breadcrumbs.Separator>
|
||||
<Breadcrumbs.Item href="">Components</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Basic</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Button</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Icons"
|
||||
desc="Show more information with icons."
|
||||
scope={{ Breadcrumbs, Home, Inbox }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item><Home /></Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href=""><Inbox /> Inbox</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="With NextJS"
|
||||
desc="Example for use with `next.js`."
|
||||
scope={{ Breadcrumbs, NextLink }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<NextLink href="/">
|
||||
<Breadcrumbs.Item nextLink>Home</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<NextLink href="/en-us/components">
|
||||
<Breadcrumbs.Item nextLink>Components</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<Breadcrumbs.Item>Breadcrumbs</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/breadcrumbs.mdx">
|
||||
<Attributes.Title>Breadcrumbs.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **separator** | separator string | `string` | - | `/` |
|
||||
| **size** | breadcrumbs size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Item.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **href** | link address | `string` | - | - |
|
||||
| **nextLink** | in `next.js` route | `boolean` | - | `false` |
|
||||
| **onClick** | click event | `(event: MouseEvent) => void` | - | - |
|
||||
| ... | native props | `AnchorHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Separator.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>NormalSizes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
@@ -79,6 +79,26 @@ and very small size. Of course, it still supports dynamic props and custom break
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Hide elements"
|
||||
desc="Hide elements when unit size is 0."
|
||||
scope={{ Grid, Card }}
|
||||
code={`
|
||||
() => {
|
||||
const MockItem = () => {
|
||||
return <Card shadow style={{ width: '100%', height: '50px' }} />
|
||||
}
|
||||
return (
|
||||
<Grid.Container gap={2} justify="center">
|
||||
<Grid xs={12} sm={0}><MockItem /></Grid>
|
||||
<Grid xs={12} sm={0}><MockItem /></Grid>
|
||||
<Grid xs={24}><MockItem /></Grid>
|
||||
<Grid xs={24}><MockItem /></Grid>
|
||||
</Grid.Container>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Auto width"
|
||||
desc="Auto fill remaining width."
|
||||
|
||||
@@ -31,7 +31,7 @@ Display image content.
|
||||
desc="Add a browser style wrapper to the image."
|
||||
scope={{ Image, Display, Code }}
|
||||
code={`
|
||||
<Image.Browser url="https://react.zeit-ui.co/en-us/guide/introduction" >
|
||||
<Image.Browser url="https://react.zeit-ui.co/en-us/guide/introduction" anchorProps={{ rel: 'nofollow' }}>
|
||||
<Image width="540" height="246" src="https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png" />
|
||||
</Image.Browser>
|
||||
`} />
|
||||
@@ -77,6 +77,7 @@ Display image content.
|
||||
| **url** | show url on browser address input | `string` | - | - |
|
||||
| **showFullLink** | show full url | `boolean` | - | `false` |
|
||||
| **invert** | invert colors | `boolean` | - | `false` |
|
||||
| **anchorProps** | props of element `a` | `AnchorHTMLAttributes` | - | `{}` |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
</Attributes>
|
||||
|
||||
108
pages/en-us/components/pagination.mdx
Normal file
108
pages/en-us/components/pagination.mdx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Pagination, Spacer } from 'components'
|
||||
import ChevronRight from '@zeit-ui/react-icons/chevronRight'
|
||||
import ChevronLeft from '@zeit-ui/react-icons/chevronLeft'
|
||||
import ChevronRightCircle from '@zeit-ui/react-icons/chevronRightCircle'
|
||||
import ChevronLeftCircle from '@zeit-ui/react-icons/chevronLeftCircle'
|
||||
import ChevronRightCircleFill from '@zeit-ui/react-icons/chevronRightCircleFill'
|
||||
import ChevronLeftCircleFill from '@zeit-ui/react-icons/chevronLeftCircleFill'
|
||||
|
||||
export const meta = {
|
||||
title: 'Pagination',
|
||||
group: 'Navigation',
|
||||
}
|
||||
|
||||
## Pagination
|
||||
|
||||
Navigation and identification between multiple pages.
|
||||
|
||||
<Playground
|
||||
scope={{ Pagination }}
|
||||
code={`
|
||||
<Pagination count={20} initialPage={3} />
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Limit"
|
||||
desc="The maximum number of pages that can be displayed"
|
||||
scope={{ Pagination }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={10} limit={10} />
|
||||
<Pagination count={5} />
|
||||
<Pagination count={10} initialPage={6} limit={5} />
|
||||
<Pagination count={10} initialPage={6} />
|
||||
<Pagination count={30} initialPage={6} limit={10} />
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Icon"
|
||||
desc="Customize buttons as icons."
|
||||
scope={{ Spacer, Pagination, ChevronRight, ChevronLeft, ChevronRightCircle, ChevronLeftCircle, ChevronRightCircleFill, ChevronLeftCircleFill }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRight /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeft /></Pagination.Previous>
|
||||
</Pagination>
|
||||
<Spacer y={.5} />
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRightCircle /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeftCircle /></Pagination.Previous>
|
||||
</Pagination>
|
||||
<Spacer y={.5} />
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRightCircleFill /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeftCircleFill /></Pagination.Previous>
|
||||
</Pagination>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Sizes"
|
||||
desc="Pagination of different sizes."
|
||||
scope={{ Pagination }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={20} size="mini" />
|
||||
<Pagination count={20} size="small" />
|
||||
<Pagination count={20} size="medium" />
|
||||
<Pagination count={20} size="large" />
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/pagination.mdx">
|
||||
<Attributes.Title>Pagination.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **initialPage** | the page selected by default | `number` | - | 1 |
|
||||
| **page** | current page | `number` | - | 1 |
|
||||
| **count** | the total number of pages | `number` | - | 1 |
|
||||
| **limit** | limit of display page | `number` | - | 7 |
|
||||
| **size** | pagination size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
|
||||
| **onChange** | change event | `(page: number) => void` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Pagination.Previous.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `ButtonHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Pagination.Next.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | native props | `ButtonHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>NormalSizes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
@@ -26,6 +26,21 @@ export const meta = {
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="只允许选择输入"
|
||||
desc="只通过 Select 事件更改值。"
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const options = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
return <AutoComplete disableFreeSolo options={options} initialValue="sydney" />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
desc="禁用所有的交互。"
|
||||
title="禁用"
|
||||
@@ -217,6 +232,32 @@ export const meta = {
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="可创建的"
|
||||
desc="为任意值添加待选条目。"
|
||||
scope={{ AutoComplete }}
|
||||
code={`
|
||||
() => {
|
||||
const allOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
{ label: 'Sydney', value: 'sydney' },
|
||||
{ label: 'Shanghai', value: 'shanghai' },
|
||||
]
|
||||
const [options, setOptions] = React.useState()
|
||||
const searchHandler = (currentValue) => {
|
||||
const createOptions = [{
|
||||
value: currentValue, label: 'Add "' + currentValue + '"'
|
||||
}]
|
||||
if (!currentValue) return setOptions([])
|
||||
const relatedOptions = allOptions.filter(item => item.value.includes(currentValue))
|
||||
const optionsWithCreatable = relatedOptions.length !== 0 ? relatedOptions : createOptions
|
||||
setOptions(optionsWithCreatable)
|
||||
}
|
||||
return <AutoComplete options={options} clearable disableFreeSolo placeholder="Enter here" onSearch={searchHandler} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/zh-cn/components/auto-complete.mdx">
|
||||
<Attributes.Title>AutoComplete.Props</Attributes.Title>
|
||||
|
||||
@@ -236,6 +277,7 @@ export const meta = {
|
||||
| **dropdownClassName** | 自定义下拉框的类名 | `string` | - | - |
|
||||
| **dropdownStyle** | 自定义下拉框的样式 | `object` | - | - |
|
||||
| **disableMatchWidth** | 禁止 Option 跟随父元素的宽度 | `boolean` | - | `false` |
|
||||
| **disableFreeSolo** | 只允许通过 Select 事件更改值 (禁止 Input 输入随意值) | `boolean` | - | `false` |
|
||||
| ... | 原生属性 | `InputHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="AutoComplete.Option">AutoComplete.Item</Attributes.Title>
|
||||
|
||||
136
pages/zh-cn/components/breadcrumbs.mdx
Normal file
136
pages/zh-cn/components/breadcrumbs.mdx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Breadcrumbs, Spacer } from 'components'
|
||||
import NextLink from 'next/link'
|
||||
import Home from '@zeit-ui/react-icons/home'
|
||||
import Inbox from '@zeit-ui/react-icons/inbox'
|
||||
|
||||
export const meta = {
|
||||
title: '面包屑 Breadcrumbs',
|
||||
group: '导航',
|
||||
}
|
||||
|
||||
## Breadcrumbs / 面包屑导航
|
||||
|
||||
显示用户在应用中的层级位置。
|
||||
|
||||
<Playground
|
||||
scope={{ Breadcrumbs }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="大小"
|
||||
scope={{ Breadcrumbs }}
|
||||
code={`
|
||||
<>
|
||||
<Breadcrumbs size="mini">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="small">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="medium">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs size="large">
|
||||
<Breadcrumbs.Item href="">Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item>Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="分隔符"
|
||||
desc="定制字符中的分隔符。"
|
||||
scope={{ Breadcrumbs, Spacer }}
|
||||
code={`
|
||||
<>
|
||||
<Breadcrumbs separator="-">
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Catalog</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
<Spacer y={.5} />
|
||||
<Breadcrumbs separator=">">
|
||||
<Breadcrumbs.Item>Home</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Separator>:</Breadcrumbs.Separator>
|
||||
<Breadcrumbs.Item href="">Components</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href="">Basic</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Button</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="图标"
|
||||
desc="以图标表达更多信息。"
|
||||
scope={{ Breadcrumbs, Home, Inbox }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.Item><Home /></Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item href=""><Inbox /> Inbox</Breadcrumbs.Item>
|
||||
<Breadcrumbs.Item >Page</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="结合 NextJS"
|
||||
desc="与 `next.js` 结合使用的示例。"
|
||||
scope={{ Breadcrumbs, NextLink }}
|
||||
code={`
|
||||
<Breadcrumbs>
|
||||
<NextLink href="/">
|
||||
<Breadcrumbs.Item nextLink>Home</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<NextLink href="/en-us/components">
|
||||
<Breadcrumbs.Item nextLink>Components</Breadcrumbs.Item>
|
||||
</NextLink>
|
||||
<Breadcrumbs.Item>Breadcrumbs</Breadcrumbs.Item>
|
||||
</Breadcrumbs>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/zh-cn/components/breadcrumbs.mdx">
|
||||
<Attributes.Title>Breadcrumbs.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **separator** | 分隔符 | `string` | - | `/` |
|
||||
| **size** | 面包屑导航的大小 | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Item.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **href** | 链接地址 | `string` | - | - |
|
||||
| **nextLink** | 是否为 `next.js` 路由 | `boolean` | - | `false` |
|
||||
| **onClick** | 点击事件 | `(event: MouseEvent) => void` | - | - |
|
||||
| ... | 原生属性 | `AnchorHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Breadcrumbs.Separator.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>NormalSizes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
@@ -79,6 +79,26 @@ export const meta = {
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="隐藏元素"
|
||||
desc="当单位大小为 0 时会自动隐藏当前元素。"
|
||||
scope={{ Grid, Card }}
|
||||
code={`
|
||||
() => {
|
||||
const MockItem = () => {
|
||||
return <Card shadow style={{ width: '100%', height: '50px' }} />
|
||||
}
|
||||
return (
|
||||
<Grid.Container gap={2} justify="center">
|
||||
<Grid xs={12} sm={0}><MockItem /></Grid>
|
||||
<Grid xs={12} sm={0}><MockItem /></Grid>
|
||||
<Grid xs={24}><MockItem /></Grid>
|
||||
<Grid xs={24}><MockItem /></Grid>
|
||||
</Grid.Container>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="自动宽度"
|
||||
desc="自动分配剩余宽度。"
|
||||
|
||||
@@ -76,6 +76,7 @@ export const meta = {
|
||||
| **url** | 在浏览器地址栏显示链接 | `string` | - | - |
|
||||
| **showFullLink** | 显示完整的链接而非域名 | `boolean` | - | `false` |
|
||||
| **invert** | 反转所有颜色 | `boolean` | - | `false` |
|
||||
| **anchorProps** | 设置 `a` 的其他属性 | `AnchorHTMLAttributes` | - | `{}` |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
</Attributes>
|
||||
|
||||
109
pages/zh-cn/components/pagination.mdx
Normal file
109
pages/zh-cn/components/pagination.mdx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Pagination, Spacer } from 'components'
|
||||
import ChevronRight from '@zeit-ui/react-icons/chevronRight'
|
||||
import ChevronLeft from '@zeit-ui/react-icons/chevronLeft'
|
||||
import ChevronRightCircle from '@zeit-ui/react-icons/chevronRightCircle'
|
||||
import ChevronLeftCircle from '@zeit-ui/react-icons/chevronLeftCircle'
|
||||
import ChevronRightCircleFill from '@zeit-ui/react-icons/chevronRightCircleFill'
|
||||
import ChevronLeftCircleFill from '@zeit-ui/react-icons/chevronLeftCircleFill'
|
||||
|
||||
export const meta = {
|
||||
title: '分页 Pagination',
|
||||
group: '导航',
|
||||
}
|
||||
|
||||
## Pagination / 分页
|
||||
|
||||
多个页面之间的导航与鉴别。
|
||||
|
||||
<Playground
|
||||
scope={{ Pagination }}
|
||||
desc="默认的分页会自动收缩与扩展。"
|
||||
code={`
|
||||
<Pagination count={20} initialPage={3} />
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Limit"
|
||||
desc="调整最大可展示页面数。"
|
||||
scope={{ Pagination }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={10} limit={10} />
|
||||
<Pagination count={5} />
|
||||
<Pagination count={10} initialPage={6} limit={5} />
|
||||
<Pagination count={10} initialPage={6} />
|
||||
<Pagination count={30} initialPage={6} limit={10} />
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Icon"
|
||||
desc="用图标定制按钮。"
|
||||
scope={{ Spacer, Pagination, ChevronRight, ChevronLeft, ChevronRightCircle, ChevronLeftCircle, ChevronRightCircleFill, ChevronLeftCircleFill }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRight /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeft /></Pagination.Previous>
|
||||
</Pagination>
|
||||
<Spacer y={.5} />
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRightCircle /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeftCircle /></Pagination.Previous>
|
||||
</Pagination>
|
||||
<Spacer y={.5} />
|
||||
<Pagination count={5}>
|
||||
<Pagination.Next><ChevronRightCircleFill /></Pagination.Next>
|
||||
<Pagination.Previous><ChevronLeftCircleFill /></Pagination.Previous>
|
||||
</Pagination>
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Sizes"
|
||||
desc="不同大小的分页器。"
|
||||
scope={{ Pagination }}
|
||||
code={`
|
||||
<>
|
||||
<Pagination count={20} size="mini" />
|
||||
<Pagination count={20} size="small" />
|
||||
<Pagination count={20} size="medium" />
|
||||
<Pagination count={20} size="large" />
|
||||
</>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/zh-cn/components/pagination.mdx">
|
||||
<Attributes.Title>Pagination.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **initialPage** | 初始选中的页面 | `number` | - | 1 |
|
||||
| **page** | 当前页码 | `number` | - | 1 |
|
||||
| **count** | 页码数量 | `number` | - | 1 |
|
||||
| **limit** | 一次可展示页面的最大值 | `number` | - | 7 |
|
||||
| **size** | 组件的大小 | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
|
||||
| **onChange** | 分页器的事件 | `(page: number) => void` | - | - |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Pagination.Previous.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | 原生属性 | `ButtonHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Pagination.Next.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| ... | 原生属性 | `ButtonHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>NormalSizes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user