Merge pull request #281 from zeit-ui/rc

chore: release v1.7.0
This commit is contained in:
witt
2020-06-20 13:30:27 +08:00
committed by GitHub
50 changed files with 2142 additions and 39 deletions

View File

@@ -4,6 +4,7 @@ exports[`AutoComplete should render correctly 1`] = `
<AutoComplete
className=""
clearable={false}
disableFreeSolo={false}
disableMatchWidth={false}
disabled={false}
initialValue=""

View File

@@ -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')
})
})

View File

@@ -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')
})
})

View 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('')
})
})

View File

@@ -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])

View File

@@ -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}

View File

@@ -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>"
`;

View 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()
})
})

View 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)

View 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)

View 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>

View 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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>"

View File

@@ -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')
})
})

View File

@@ -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>

View File

@@ -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)
})
})

View File

@@ -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}>

View File

@@ -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'

View File

@@ -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)
}
},
},
}

View File

@@ -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

View File

@@ -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>"
`;

View 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')
})
})

View 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

View 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)

View 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

View 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

View 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

View 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

View 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

View 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>

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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>

View 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>

View File

@@ -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."

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -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="自动分配剩余宽度。"

View File

@@ -76,6 +76,7 @@ export const meta = {
| **url** | 在浏览器地址栏显示链接 | `string` | - | - |
| **showFullLink** | 显示完整的链接而非域名 | `boolean` | - | `false` |
| **invert** | 反转所有颜色 | `boolean` | - | `false` |
| **anchorProps** | 设置 `a` 的其他属性 | `AnchorHTMLAttributes` | - | `{}` |
| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - |
</Attributes>

View 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>