From 67ee18e5615a3eff3f503ab6a6b4ca3c79810b19 Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 30 May 2020 21:44:44 +0800 Subject: [PATCH 01/28] feat(breadcrumbs): add component docs(breadcrumbs): add docs fix(breadcrumbs): fix item types error --- .../__tests__/breadcrumbs.test.tsx | 0 components/breadcrumbs/breadcrumbs-context.ts | 12 ++ components/breadcrumbs/breadcrumbs-item.tsx | 74 +++++++++++++ .../breadcrumbs/breadcrumbs-separator.tsx | 40 +++++++ components/breadcrumbs/breadcrumbs.tsx | 88 +++++++++++++++ components/breadcrumbs/index.ts | 8 ++ components/index.ts | 1 + components/link/link.tsx | 2 +- lib/data/metadata-en-us.json | 2 +- pages/en-us/components/breadcrumbs.mdx | 104 ++++++++++++++++++ 10 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 components/breadcrumbs/__tests__/breadcrumbs.test.tsx create mode 100644 components/breadcrumbs/breadcrumbs-context.ts create mode 100644 components/breadcrumbs/breadcrumbs-item.tsx create mode 100644 components/breadcrumbs/breadcrumbs-separator.tsx create mode 100644 components/breadcrumbs/breadcrumbs.tsx create mode 100644 components/breadcrumbs/index.ts create mode 100644 pages/en-us/components/breadcrumbs.mdx diff --git a/components/breadcrumbs/__tests__/breadcrumbs.test.tsx b/components/breadcrumbs/__tests__/breadcrumbs.test.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/components/breadcrumbs/breadcrumbs-context.ts b/components/breadcrumbs/breadcrumbs-context.ts new file mode 100644 index 000000000..0e653db6e --- /dev/null +++ b/components/breadcrumbs/breadcrumbs-context.ts @@ -0,0 +1,12 @@ +import React from 'react' + +export interface BreadcrumbsConfig { + separator?: string +} + +const defaultContext = {} + +export const BreadcrumbsContext = React.createContext(defaultContext) + +export const useBreadcrumbsContext = (): BreadcrumbsConfig => + React.useContext(BreadcrumbsContext) diff --git a/components/breadcrumbs/breadcrumbs-item.tsx b/components/breadcrumbs/breadcrumbs-item.tsx new file mode 100644 index 000000000..fddad183a --- /dev/null +++ b/components/breadcrumbs/breadcrumbs-item.tsx @@ -0,0 +1,74 @@ +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' +import { useBreadcrumbsContext } from './breadcrumbs-context' + +interface Props { + href?: string + nextLink?: boolean + onClick?: (event: React.MouseEvent) => void + className?: string +} + +const defaultProps = { + nextLink: false, + className: '', +} + +type NativeAttrs = Omit, keyof Props> +type NativeLinkAttrs = Omit +export type BreadcrumbsProps = Props & typeof defaultProps & NativeLinkAttrs + +const BreadcrumbsItem = React.forwardRef< + HTMLAnchorElement, + React.PropsWithChildren +>( + ( + { href, nextLink, onClick, children, className, ...props }, + ref: React.Ref, + ) => { + const { separator } = useBreadcrumbsContext() + const isLink = useMemo(() => href !== undefined || nextLink, [href, nextLink]) + const [withoutSepChildren, sepChildren] = pickChild(children, BreadcrumbsSeparator) + const composeSeparator = useMemo(() => { + if (React.Children.count(sepChildren) > 0) return sepChildren + return {separator} + }, [separator]) + + const clickHandler = (event: React.MouseEvent) => { + onClick && onClick(event) + } + + if (!isLink) { + return ( + <> + + {withoutSepChildren} + + {composeSeparator} + + ) + } + + return ( + <> + + {withoutSepChildren} + + {composeSeparator} + + ) + }, +) + +const MemoBreadcrumbsItem = React.memo(BreadcrumbsItem) + +export default withDefaults(MemoBreadcrumbsItem, defaultProps) diff --git a/components/breadcrumbs/breadcrumbs-separator.tsx b/components/breadcrumbs/breadcrumbs-separator.tsx new file mode 100644 index 000000000..82d2cf17b --- /dev/null +++ b/components/breadcrumbs/breadcrumbs-separator.tsx @@ -0,0 +1,40 @@ +import React from 'react' +// import useTheme from '../styles/use-theme' +import withDefaults from '../utils/with-defaults' + +interface Props { + className?: string +} + +const defaultProps = { + className: '', +} + +type NativeAttrs = Omit, keyof Props> +export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs + +const BreadcrumbsSeparator: React.FC> = ({ + children, + className, +}) => { + // const theme = useTheme() + + return ( +
+ {children} + +
+ ) +} + +const MemoBreadcrumbsSeparator = React.memo(BreadcrumbsSeparator) + +export default withDefaults(MemoBreadcrumbsSeparator, defaultProps) diff --git a/components/breadcrumbs/breadcrumbs.tsx b/components/breadcrumbs/breadcrumbs.tsx new file mode 100644 index 000000000..063d68f3e --- /dev/null +++ b/components/breadcrumbs/breadcrumbs.tsx @@ -0,0 +1,88 @@ +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 { BreadcrumbsContext } from './breadcrumbs-context' + +interface Props { + separator?: string | ReactNode + className?: string +} + +const defaultProps = { + separator: '/', + className: '', +} + +type NativeAttrs = Omit, keyof Props> +export type BreadcrumbsProps = Props & typeof defaultProps & NativeAttrs + +const Breadcrumbs: React.FC> = ({ + separator, + children, + className, +}) => { + const theme = useTheme() + const initialValue = useMemo( + () => ({ + separator, + }), + [separator], + ) + + return ( + + + + ) +} + +type MemoBreadcrumbsComponent

= React.NamedExoticComponent

& { + Item: typeof BreadcrumbsItem + Separator: typeof BreadcrumbsSeparator +} +type ComponentProps = Partial & + Omit & + NativeAttrs + +Breadcrumbs.defaultProps = defaultProps + +export default React.memo(Breadcrumbs) as MemoBreadcrumbsComponent diff --git a/components/breadcrumbs/index.ts b/components/breadcrumbs/index.ts new file mode 100644 index 000000000..fe5edd80d --- /dev/null +++ b/components/breadcrumbs/index.ts @@ -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 diff --git a/components/index.ts b/components/index.ts index 506ae9205..647a4b894 100644 --- a/components/index.ts +++ b/components/index.ts @@ -58,3 +58,4 @@ 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' diff --git a/components/link/link.tsx b/components/link/link.tsx index 39cb17227..887f377ce 100644 --- a/components/link/link.tsx +++ b/components/link/link.tsx @@ -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 diff --git a/lib/data/metadata-en-us.json b/lib/data/metadata-en-us.json index ded3353cf..cd546e6a3 100644 --- a/lib/data/metadata-en-us.json +++ b/lib/data/metadata-en-us.json @@ -1 +1 @@ -[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"use-media-query","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}] +[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"breadcrumbs","url":"/en-us/components/breadcrumbs","index":100,"group":"Navigation"},{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"use-media-query","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}] diff --git a/pages/en-us/components/breadcrumbs.mdx b/pages/en-us/components/breadcrumbs.mdx new file mode 100644 index 000000000..1bf8dd4ab --- /dev/null +++ b/pages/en-us/components/breadcrumbs.mdx @@ -0,0 +1,104 @@ +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. + + + Home + Catalog + Page + +`} /> + + + + Home + Catalog + Page + + + + + Home + : + + Components + Basic + Button + + +`} /> + + + + Inbox + Page + +`} /> + + + + Home + + + Components + + Breadcrumbs + +`} /> + + +Breadcrumbs.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| **separator** | separator string | `string` | - | `/` | +| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - | + +Breadcrumbs.Item.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| **href** | link address | `string` | - | - | +| **nextLink** | in `next.js` route | `boolean` | - | `false` | +| **onClick** | click event | `(event: MouseEvent) => void` | - | - | +| **onClick** | click event | `(event: MouseEvent) => void` | - | - | +| ... | native props | `AnchorHTMLAttributes` | `'id', 'className', ...` | - | + +Breadcrumbs.Separator.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - | + + + +export default ({ children }) => {children} From fd1fad19d35db702a440f6b9aac16aa430a71374 Mon Sep 17 00:00:00 2001 From: unix Date: Sun, 31 May 2020 08:04:28 +0800 Subject: [PATCH 02/28] test(breadcrumbs): add testcase docs: add api for breadcrumbs --- .../__snapshots__/breadcrumbs.test.tsx.snap | 101 +++++++++++++++++ .../__tests__/breadcrumbs.test.tsx | 77 +++++++++++++ pages/en-us/components/breadcrumbs.mdx | 1 - pages/zh-cn/components/breadcrumbs.mdx | 103 ++++++++++++++++++ 4 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap create mode 100644 pages/zh-cn/components/breadcrumbs.mdx diff --git a/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap b/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap new file mode 100644 index 000000000..f2c7c3f2e --- /dev/null +++ b/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Breadcrumbs should redefined all separators 1`] = ` +"

" +`; + +exports[`Breadcrumbs should render correctly 1`] = ` +"" +`; diff --git a/components/breadcrumbs/__tests__/breadcrumbs.test.tsx b/components/breadcrumbs/__tests__/breadcrumbs.test.tsx index e69de29bb..45ac9a087 100644 --- a/components/breadcrumbs/__tests__/breadcrumbs.test.tsx +++ b/components/breadcrumbs/__tests__/breadcrumbs.test.tsx @@ -0,0 +1,77 @@ +import React from 'react' +import { mount } from 'enzyme' +import { Breadcrumbs } from 'components' + +describe('Breadcrumbs', () => { + it('should render correctly', () => { + const wrapper = mount( + + test-1 + , + ) + expect(wrapper.html()).toMatchSnapshot() + expect(() => wrapper.unmount()).not.toThrow() + }) + + it('should redefined all separators', () => { + const wrapper = mount( + + test-1 + test-2 + , + ) + expect(wrapper.html()).toMatchSnapshot() + expect(wrapper.html()).toContain('*') + expect(() => wrapper.unmount()).not.toThrow() + }) + + it('the specified separator should be redefined', () => { + const wrapper = mount( + + + test-1 + % + + test-2 + , + ) + expect(wrapper.html()).toContain('%') + }) + + it('should render string when href missing', () => { + let wrapper = mount( + + test-1 + , + ) + let dom = wrapper.find('.breadcrums-item').at(0).getDOMNode() + expect(dom.tagName).toEqual('SPAN') + + wrapper = mount( + + test-1 + , + ) + dom = wrapper.find('.breadcrums-item').at(0).getDOMNode() + expect(dom.tagName).toEqual('A') + + wrapper = mount( + + test-1 + , + ) + 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( + + test-1 + , + ) + wrapper.find('.breadcrums-item').at(0).simulate('click') + expect(handler).toHaveBeenCalled() + }) +}) diff --git a/pages/en-us/components/breadcrumbs.mdx b/pages/en-us/components/breadcrumbs.mdx index 1bf8dd4ab..04c01a30a 100644 --- a/pages/en-us/components/breadcrumbs.mdx +++ b/pages/en-us/components/breadcrumbs.mdx @@ -90,7 +90,6 @@ Show where users are in the application. | **href** | link address | `string` | - | - | | **nextLink** | in `next.js` route | `boolean` | - | `false` | | **onClick** | click event | `(event: MouseEvent) => void` | - | - | -| **onClick** | click event | `(event: MouseEvent) => void` | - | - | | ... | native props | `AnchorHTMLAttributes` | `'id', 'className', ...` | - | Breadcrumbs.Separator.Props diff --git a/pages/zh-cn/components/breadcrumbs.mdx b/pages/zh-cn/components/breadcrumbs.mdx new file mode 100644 index 000000000..8c5799b36 --- /dev/null +++ b/pages/zh-cn/components/breadcrumbs.mdx @@ -0,0 +1,103 @@ +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 / 面包屑导航 + +显示用户在应用中的层级位置。 + + + Home + Catalog + Page + +`} /> + + + + Home + Catalog + Page + + + + + Home + : + + Components + Basic + Button + + +`} /> + + + + Inbox + Page + +`} /> + + + + Home + + + Components + + Breadcrumbs + +`} /> + + +Breadcrumbs.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| **separator** | 分隔符 | `string` | - | `/` | +| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - | + +Breadcrumbs.Item.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| **href** | 链接地址 | `string` | - | - | +| **nextLink** | 是否为 `next.js` 路由 | `boolean` | - | `false` | +| **onClick** | 点击事件 | `(event: MouseEvent) => void` | - | - | +| ... | 原生属性 | `AnchorHTMLAttributes` | `'id', 'className', ...` | - | + +Breadcrumbs.Separator.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - | + + + +export default ({ children }) => {children} From 296e13027783ef5bed2d2466d29e6864f95e1388 Mon Sep 17 00:00:00 2001 From: unix Date: Sun, 31 May 2020 08:33:46 +0800 Subject: [PATCH 03/28] feat(breadcrumbs): add size --- components/breadcrumbs/breadcrumbs.tsx | 17 +++++++++- pages/en-us/components/breadcrumbs.mdx | 43 +++++++++++++++++++++++--- pages/zh-cn/components/breadcrumbs.mdx | 35 +++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/components/breadcrumbs/breadcrumbs.tsx b/components/breadcrumbs/breadcrumbs.tsx index 063d68f3e..c68ea72b5 100644 --- a/components/breadcrumbs/breadcrumbs.tsx +++ b/components/breadcrumbs/breadcrumbs.tsx @@ -4,13 +4,16 @@ import BreadcrumbsItem from './breadcrumbs-item' import BreadcrumbsSeparator from './breadcrumbs-separator' import { addColorAlpha } from '../utils/color' import { BreadcrumbsContext } from './breadcrumbs-context' +import { NormalSizes } from 'components/utils/prop-types' interface Props { + size: NormalSizes separator?: string | ReactNode className?: string } const defaultProps = { + size: 'medium' as NormalSizes, separator: '/', className: '', } @@ -18,7 +21,18 @@ const defaultProps = { type NativeAttrs = Omit, 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> = ({ + size, separator, children, className, @@ -30,6 +44,7 @@ const Breadcrumbs: React.FC> = ({ }), [separator], ) + const fontSize = useMemo(() => getSize(size), [size]) return ( @@ -41,7 +56,7 @@ const Breadcrumbs: React.FC> = ({ padding: 0; line-height: inherit; color: ${theme.palette.accents_4}; - font-size: 1rem; + font-size: ${fontSize}; box-sizing: border-box; display: flex; align-items: center; diff --git a/pages/en-us/components/breadcrumbs.mdx b/pages/en-us/components/breadcrumbs.mdx index 04c01a30a..248386c7a 100644 --- a/pages/en-us/components/breadcrumbs.mdx +++ b/pages/en-us/components/breadcrumbs.mdx @@ -19,10 +19,38 @@ Show where users are in the application. Home Catalog - Page + Page `} /> + + + Home + Catalog + Page + + + Home + Catalog + Page + + + Home + Catalog + Page + + + Home + Catalog + Page + + +`} /> + Home Catalog - Page + Page @@ -42,7 +70,7 @@ Show where users are in the application. Components Basic - Button + Button `} /> @@ -55,7 +83,7 @@ Show where users are in the application. Inbox - Page + Page `} /> @@ -81,6 +109,7 @@ Show where users are in the application. | Attribute | Description | Type | Accepted values | Default | ---------- | ---------- | ---- | -------------- | ------ | | **separator** | separator string | `string` | - | `/` | +| **size** | breadcrumbs size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` | | ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - | Breadcrumbs.Item.Props @@ -98,6 +127,12 @@ Show where users are in the application. | ---------- | ---------- | ---- | -------------- | ------ | | ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - | +NormalSizes + +```ts +type NormalSizes = 'mini' | 'small' | 'medium' | 'large' +``` + export default ({ children }) => {children} diff --git a/pages/zh-cn/components/breadcrumbs.mdx b/pages/zh-cn/components/breadcrumbs.mdx index 8c5799b36..cec2d5922 100644 --- a/pages/zh-cn/components/breadcrumbs.mdx +++ b/pages/zh-cn/components/breadcrumbs.mdx @@ -23,6 +23,34 @@ export const meta = { `} /> + + + Home + Catalog + Page + + + Home + Catalog + Page + + + Home + Catalog + Page + + + Home + Catalog + Page + + +`} /> + Breadcrumbs.Item.Props @@ -98,6 +127,12 @@ export const meta = { | ---------- | ---------- | ---- | -------------- | ------ | | ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - | +NormalSizes + +```ts +type NormalSizes = 'mini' | 'small' | 'medium' | 'large' +``` + export default ({ children }) => {children} From 48f16d7fbfcd9abea7537f7414a8f6c990a24aee Mon Sep 17 00:00:00 2001 From: unix Date: Mon, 1 Jun 2020 08:57:24 +0800 Subject: [PATCH 04/28] refactor(breadcrumbs): auto assign separator to crumbs item docs(breadcrumbs): update docs --- .../__snapshots__/breadcrumbs.test.tsx.snap | 130 ++++++++---------- .../__tests__/breadcrumbs.test.tsx | 6 +- components/breadcrumbs/breadcrumbs-context.ts | 12 -- components/breadcrumbs/breadcrumbs-item.tsx | 37 ++--- .../breadcrumbs/breadcrumbs-separator.tsx | 3 - components/breadcrumbs/breadcrumbs.tsx | 97 +++++++------ lib/data/metadata-zh-cn.json | 2 +- pages/en-us/components/breadcrumbs.mdx | 6 +- pages/zh-cn/components/breadcrumbs.mdx | 6 +- 9 files changed, 130 insertions(+), 169 deletions(-) delete mode 100644 components/breadcrumbs/breadcrumbs-context.ts diff --git a/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap b/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap index f2c7c3f2e..58a621cc6 100644 --- a/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap +++ b/components/breadcrumbs/__tests__/__snapshots__/breadcrumbs.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Breadcrumbs should redefined all separators 1`] = ` -"" + nav :global(.breadcrums-item) { + display: inline-flex; + align-items: center; + } + " `; exports[`Breadcrumbs should render correctly 1`] = ` -"" + nav :global(.breadcrums-item) { + display: inline-flex; + align-items: center; + } + " `; diff --git a/components/breadcrumbs/__tests__/breadcrumbs.test.tsx b/components/breadcrumbs/__tests__/breadcrumbs.test.tsx index 45ac9a087..b0ff8cf6c 100644 --- a/components/breadcrumbs/__tests__/breadcrumbs.test.tsx +++ b/components/breadcrumbs/__tests__/breadcrumbs.test.tsx @@ -28,10 +28,8 @@ describe('Breadcrumbs', () => { it('the specified separator should be redefined', () => { const wrapper = mount( - - test-1 - % - + test-1 + % test-2 , ) diff --git a/components/breadcrumbs/breadcrumbs-context.ts b/components/breadcrumbs/breadcrumbs-context.ts deleted file mode 100644 index 0e653db6e..000000000 --- a/components/breadcrumbs/breadcrumbs-context.ts +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' - -export interface BreadcrumbsConfig { - separator?: string -} - -const defaultContext = {} - -export const BreadcrumbsContext = React.createContext(defaultContext) - -export const useBreadcrumbsContext = (): BreadcrumbsConfig => - React.useContext(BreadcrumbsContext) diff --git a/components/breadcrumbs/breadcrumbs-item.tsx b/components/breadcrumbs/breadcrumbs-item.tsx index fddad183a..2ab1e95c6 100644 --- a/components/breadcrumbs/breadcrumbs-item.tsx +++ b/components/breadcrumbs/breadcrumbs-item.tsx @@ -4,7 +4,6 @@ import React, { useMemo } from 'react' import withDefaults from '../utils/with-defaults' import { pickChild } from '../utils/collections' import BreadcrumbsSeparator from './breadcrumbs-separator' -import { useBreadcrumbsContext } from './breadcrumbs-context' interface Props { href?: string @@ -30,41 +29,29 @@ const BreadcrumbsItem = React.forwardRef< { href, nextLink, onClick, children, className, ...props }, ref: React.Ref, ) => { - const { separator } = useBreadcrumbsContext() const isLink = useMemo(() => href !== undefined || nextLink, [href, nextLink]) - const [withoutSepChildren, sepChildren] = pickChild(children, BreadcrumbsSeparator) - const composeSeparator = useMemo(() => { - if (React.Children.count(sepChildren) > 0) return sepChildren - return {separator} - }, [separator]) - + const [withoutSepChildren] = pickChild(children, BreadcrumbsSeparator) const clickHandler = (event: React.MouseEvent) => { onClick && onClick(event) } if (!isLink) { return ( - <> - - {withoutSepChildren} - - {composeSeparator} - + + {withoutSepChildren} + ) } return ( - <> - - {withoutSepChildren} - - {composeSeparator} - + + {withoutSepChildren} + ) }, ) diff --git a/components/breadcrumbs/breadcrumbs-separator.tsx b/components/breadcrumbs/breadcrumbs-separator.tsx index 82d2cf17b..80e12f5b2 100644 --- a/components/breadcrumbs/breadcrumbs-separator.tsx +++ b/components/breadcrumbs/breadcrumbs-separator.tsx @@ -1,5 +1,4 @@ import React from 'react' -// import useTheme from '../styles/use-theme' import withDefaults from '../utils/with-defaults' interface Props { @@ -17,8 +16,6 @@ const BreadcrumbsSeparator: React.FC> children, className, }) => { - // const theme = useTheme() - return (
{children} diff --git a/components/breadcrumbs/breadcrumbs.tsx b/components/breadcrumbs/breadcrumbs.tsx index c68ea72b5..7850b4de5 100644 --- a/components/breadcrumbs/breadcrumbs.tsx +++ b/components/breadcrumbs/breadcrumbs.tsx @@ -3,8 +3,7 @@ import useTheme from '../styles/use-theme' import BreadcrumbsItem from './breadcrumbs-item' import BreadcrumbsSeparator from './breadcrumbs-separator' import { addColorAlpha } from '../utils/color' -import { BreadcrumbsContext } from './breadcrumbs-context' -import { NormalSizes } from 'components/utils/prop-types' +import { NormalSizes } from '../utils/prop-types' interface Props { size: NormalSizes @@ -38,55 +37,67 @@ const Breadcrumbs: React.FC> = ({ className, }) => { const theme = useTheme() - const initialValue = useMemo( - () => ({ - separator, - }), - [separator], - ) 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 ( + + {separator} + {item} + + ) + } + return item + }) return ( - - - + nav :global(.breadcrums-item) { + display: inline-flex; + align-items: center; + } + `} + ) } diff --git a/lib/data/metadata-zh-cn.json b/lib/data/metadata-zh-cn.json index 124e1e118..51e0f0229 100644 --- a/lib/data/metadata-zh-cn.json +++ b/lib/data/metadata-zh-cn.json @@ -1 +1 @@ -[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}] +[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"面包屑 Breadcrumbs","url":"/zh-cn/components/breadcrumbs","index":100,"group":"导航"},{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}] diff --git a/pages/en-us/components/breadcrumbs.mdx b/pages/en-us/components/breadcrumbs.mdx index 248386c7a..ded12ee8d 100644 --- a/pages/en-us/components/breadcrumbs.mdx +++ b/pages/en-us/components/breadcrumbs.mdx @@ -64,10 +64,8 @@ Show where users are in the application. - - Home - : - + Home + : Components Basic Button diff --git a/pages/zh-cn/components/breadcrumbs.mdx b/pages/zh-cn/components/breadcrumbs.mdx index cec2d5922..2fc0dc173 100644 --- a/pages/zh-cn/components/breadcrumbs.mdx +++ b/pages/zh-cn/components/breadcrumbs.mdx @@ -64,10 +64,8 @@ export const meta = { - - Home - : - + Home + : Components Basic Button From 7fe7f3dc02eae7f4b1d9a457f3773b79a68bf30a Mon Sep 17 00:00:00 2001 From: unix Date: Mon, 1 Jun 2020 09:26:00 +0800 Subject: [PATCH 05/28] fix(auto-complete): keep focus of input after select event --- components/auto-complete/auto-complete.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/auto-complete/auto-complete.tsx b/components/auto-complete/auto-complete.tsx index cb5b55179..1703403f4 100644 --- a/components/auto-complete/auto-complete.tsx +++ b/components/auto-complete/auto-complete.tsx @@ -86,8 +86,10 @@ const AutoComplete: React.FC> = ({ ...props }) => { const ref = useRef(null) + const inputRef = useRef(null) const [state, setState] = useState(customInitialValue) const [visible, setVisible] = useState(false) + const [, searchChild] = pickChild(children, AutoCompleteSearching) const [, emptyChild] = pickChild(children, AutoCompleteEmpty) const autoCompleteItems = useMemo(() => { @@ -112,6 +114,10 @@ const AutoComplete: React.FC> = ({ if (disabled) return onSelect && onSelect(val) setState(val) + if (inputRef.current) { + inputRef.current.focus() + setVisible(false) + } } const updateVisible = (next: boolean) => setVisible(next) const onInputChange = (event: React.ChangeEvent) => { @@ -157,6 +163,7 @@ const AutoComplete: React.FC> = ({
Date: Wed, 3 Jun 2020 03:35:07 +0800 Subject: [PATCH 06/28] feat(auto-complete): add control for free solo test(auto-complete): add testcase for free solo --- .../__snapshots__/index.test.tsx.snap | 1 + .../auto-complete/__tests__/index.test.tsx | 13 +++++--- .../auto-complete/__tests__/search.test.tsx | 20 +++++++++++-- .../auto-complete/auto-complete-item.tsx | 3 +- components/auto-complete/auto-complete.tsx | 30 +++++++++++++++---- 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/components/auto-complete/__tests__/__snapshots__/index.test.tsx.snap b/components/auto-complete/__tests__/__snapshots__/index.test.tsx.snap index 1aaf2097e..eaf1c54ba 100644 --- a/components/auto-complete/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/auto-complete/__tests__/__snapshots__/index.test.tsx.snap @@ -4,6 +4,7 @@ exports[`AutoComplete should render correctly 1`] = ` { 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() 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() expect(wrapper.prop('width')).toEqual('100px') + await act(async () => { + wrapper.setProps({ width: '200px' }) + }) - wrapper.setProps({ width: '200px' }) expect(wrapper.prop('width')).toEqual('200px') }) }) diff --git a/components/auto-complete/__tests__/search.test.tsx b/components/auto-complete/__tests__/search.test.tsx index 6b55b910c..13b325f99 100644 --- a/components/auto-complete/__tests__/search.test.tsx +++ b/components/auto-complete/__tests__/search.test.tsx @@ -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() - 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() expect(() => wrapper.unmount()).not.toThrow() }) + + it('value should be reset when freeSolo disabled', async () => { + const wrapper = mount() + 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') + }) }) diff --git a/components/auto-complete/auto-complete-item.tsx b/components/auto-complete/auto-complete-item.tsx index 63b102b61..6d6fb08cc 100644 --- a/components/auto-complete/auto-complete-item.tsx +++ b/components/auto-complete/auto-complete-item.tsx @@ -28,9 +28,10 @@ const AutoCompleteItem: React.FC> 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]) diff --git a/components/auto-complete/auto-complete.tsx b/components/auto-complete/auto-complete.tsx index 1703403f4..0e51a45ea 100644 --- a/components/auto-complete/auto-complete.tsx +++ b/components/auto-complete/auto-complete.tsx @@ -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,14 @@ const AutoComplete: React.FC> = ({ dropdownClassName, dropdownStyle, disableMatchWidth, + disableFreeSolo, ...props }) => { const ref = useRef(null) const inputRef = useRef(null) - const [state, setState] = useState(customInitialValue) + const resetTimer = useRef() + const [state, setState, stateRef] = useCurrentState(customInitialValue) + const [selectVal, setSelectVal] = useState(customInitialValue) const [visible, setVisible] = useState(false) const [, searchChild] = pickChild(children, AutoCompleteSearching) @@ -112,18 +118,24 @@ const AutoComplete: React.FC> = ({ const updateValue = (val: string) => { if (disabled) return + setSelectVal(val) onSelect && onSelect(val) setState(val) - if (inputRef.current) { - inputRef.current.focus() - setVisible(false) - } + inputRef.current && inputRef.current.focus() } const updateVisible = (next: boolean) => setVisible(next) const onInputChange = (event: React.ChangeEvent) => { + 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) @@ -146,9 +158,15 @@ const AutoComplete: React.FC> = ({ ) 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) } } From dd975e8e28ec9408e04b8e55455142d0f6baf88c Mon Sep 17 00:00:00 2001 From: unix Date: Wed, 3 Jun 2020 03:35:47 +0800 Subject: [PATCH 07/28] docs(auto-complete): add docs for creatable --- pages/en-us/components/auto-complete.mdx | 45 ++++++++++++++++++++++-- pages/zh-cn/components/auto-complete.mdx | 42 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/pages/en-us/components/auto-complete.mdx b/pages/en-us/components/auto-complete.mdx index 89e3d91c0..38f6c6302 100644 --- a/pages/en-us/components/auto-complete.mdx +++ b/pages/en-us/components/auto-complete.mdx @@ -40,10 +40,25 @@ AutoComplete control of input field. } `} /> + { + const options = [ + { label: 'London', value: 'london' }, + { label: 'Sydney', value: 'sydney' }, + { label: 'Shanghai', value: 'shanghai' }, + ] + return +} +`} /> + { 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. } `} /> + { + 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.Props @@ -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', ...` | - | AutoComplete.Item diff --git a/pages/zh-cn/components/auto-complete.mdx b/pages/zh-cn/components/auto-complete.mdx index d83ab3098..f3fc33e8c 100644 --- a/pages/zh-cn/components/auto-complete.mdx +++ b/pages/zh-cn/components/auto-complete.mdx @@ -26,6 +26,21 @@ export const meta = { } `} /> + { + const options = [ + { label: 'London', value: 'london' }, + { label: 'Sydney', value: 'sydney' }, + { label: 'Shanghai', value: 'shanghai' }, + ] + return +} +`} /> + + + { + 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.Props @@ -236,6 +277,7 @@ export const meta = { | **dropdownClassName** | 自定义下拉框的类名 | `string` | - | - | | **dropdownStyle** | 自定义下拉框的样式 | `object` | - | - | | **disableMatchWidth** | 禁止 Option 跟随父元素的宽度 | `boolean` | - | `false` | +| **disableFreeSolo** | 只允许通过 Select 事件更改值 (禁止 Input 输入随意值) | `boolean` | - | `false` | | ... | 原生属性 | `InputHTMLAttributes` | `'id', 'className', ...` | - | AutoComplete.Item From a6f47580216dc0299353d493ffb7739e7522b966 Mon Sep 17 00:00:00 2001 From: unix Date: Wed, 3 Jun 2020 05:37:01 +0800 Subject: [PATCH 08/28] chore: release v1.7.0-canary.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70e87d863..991573843 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zeit-ui/react", - "version": "1.6.3", + "version": "1.7.0-canary.0", "main": "dist/index.js", "module": "esm/index.js", "types": "dist/index.d.ts", From 64f15df79ad3a57c464014e6055553d9404fe9c5 Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 6 Jun 2020 05:39:23 +0800 Subject: [PATCH 09/28] feat(use-input): compatible with components of non-standed events --- components/input/use-input.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/components/input/use-input.ts b/components/input/use-input.ts index c64564ed1..c80792f0d 100644 --- a/components/input/use-input.ts +++ b/components/input/use-input.ts @@ -1,6 +1,10 @@ import React, { Dispatch, MutableRefObject, SetStateAction } from 'react' import useCurrentState from '../utils/use-current-state' +export type BindingsChangeTarget = + | React.ChangeEvent + | string + const useInput = ( initialValue: string, ): { @@ -10,7 +14,7 @@ const useInput = ( reset: () => void bindings: { value: string - onChange: (event: React.ChangeEvent) => void + onChange: (event: BindingsChangeTarget) => void } } => { const [state, setState, currentRef] = useCurrentState(initialValue) @@ -22,8 +26,12 @@ const useInput = ( reset: () => setState(initialValue), bindings: { value: state, - onChange: (event: React.ChangeEvent) => { - setState(event.target.value) + onChange: (event: BindingsChangeTarget) => { + if (typeof event === 'object' && event.target) { + setState(event.target.value) + } else { + setState(event as string) + } }, }, } From 83dc104a8230301caf28e09b77a4b38f6a0951ee Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 6 Jun 2020 05:46:33 +0800 Subject: [PATCH 10/28] test(use-input): add hooks-bsaed testcase --- .../__tests__/use-input.test.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 components/auto-complete/__tests__/use-input.test.tsx diff --git a/components/auto-complete/__tests__/use-input.test.tsx b/components/auto-complete/__tests__/use-input.test.tsx new file mode 100644 index 000000000..21ebd95c6 --- /dev/null +++ b/components/auto-complete/__tests__/use-input.test.tsx @@ -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 + } + + const wrapper = mount() + 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 + } + + const wrapper = mount() + 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('') + }) +}) From 16f69c6dc0424a69e4965952ef4c7ff9a23ca569 Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 6 Jun 2020 05:53:28 +0800 Subject: [PATCH 11/28] chore: release v1.7.0-canary.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 991573843..b78e61954 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zeit-ui/react", - "version": "1.7.0-canary.0", + "version": "1.7.0-canary.1", "main": "dist/index.js", "module": "esm/index.js", "types": "dist/index.d.ts", From f10a559eaf1c2ca29c9fe87d3f38b81f7ceb15ac Mon Sep 17 00:00:00 2001 From: unix Date: Tue, 9 Jun 2020 08:37:56 +0800 Subject: [PATCH 12/28] feat(pagination): add component --- components/index.ts | 1 + components/pagination/index.ts | 8 + components/pagination/pagination-context.ts | 18 +++ components/pagination/pagination-ellipsis.tsx | 60 ++++++++ components/pagination/pagination-item.tsx | 101 +++++++++++++ components/pagination/pagination-next.tsx | 19 +++ components/pagination/pagination-pages.tsx | 104 +++++++++++++ components/pagination/pagination-previous.tsx | 19 +++ components/pagination/pagination.tsx | 142 ++++++++++++++++++ 9 files changed, 472 insertions(+) create mode 100644 components/pagination/index.ts create mode 100644 components/pagination/pagination-context.ts create mode 100644 components/pagination/pagination-ellipsis.tsx create mode 100644 components/pagination/pagination-item.tsx create mode 100644 components/pagination/pagination-next.tsx create mode 100644 components/pagination/pagination-pages.tsx create mode 100644 components/pagination/pagination-previous.tsx create mode 100644 components/pagination/pagination.tsx diff --git a/components/index.ts b/components/index.ts index 647a4b894..517e9432b 100644 --- a/components/index.ts +++ b/components/index.ts @@ -59,3 +59,4 @@ 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' diff --git a/components/pagination/index.ts b/components/pagination/index.ts new file mode 100644 index 000000000..9a6011225 --- /dev/null +++ b/components/pagination/index.ts @@ -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 diff --git a/components/pagination/pagination-context.ts b/components/pagination/pagination-context.ts new file mode 100644 index 000000000..8342ec758 --- /dev/null +++ b/components/pagination/pagination-context.ts @@ -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(defaultContext) + +export const usePaginationContext = (): PaginationConfig => + React.useContext(PaginationContext) diff --git a/components/pagination/pagination-ellipsis.tsx b/components/pagination/pagination-ellipsis.tsx new file mode 100644 index 000000000..e4d759db6 --- /dev/null +++ b/components/pagination/pagination-ellipsis.tsx @@ -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 = ({ isBefore, onClick }) => { + const [showMore, setShowMore] = useState(false) + + return ( + onClick && onClick(e)} + onMouseEnter={() => setShowMore(true)} + onMouseLeave={() => setShowMore(false)}> + {showMore ? ( + + + + + ) : ( + + + + + + )} + + + + ) +} + +export default PaginationEllipsis diff --git a/components/pagination/pagination-item.tsx b/components/pagination/pagination-item.tsx new file mode 100644 index 000000000..e4b728349 --- /dev/null +++ b/components/pagination/pagination-item.tsx @@ -0,0 +1,101 @@ +import React, { useMemo } from 'react' +import useTheme from '../styles/use-theme' +import { addColorAlpha } from 'components/utils/color' + +interface Props { + active?: boolean + disabled?: boolean + onClick?: (e: React.MouseEvent) => void +} + +type NativeAttrs = Omit, keyof Props> +export type PaginationItemProps = Props & NativeAttrs + +const PaginationItem: React.FC> = ({ + 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 ( +
  • + + +
  • + ) +} + +export default PaginationItem diff --git a/components/pagination/pagination-next.tsx b/components/pagination/pagination-next.tsx new file mode 100644 index 000000000..d01f893fe --- /dev/null +++ b/components/pagination/pagination-next.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import PaginationItem from './pagination-item' +import { usePaginationContext } from './pagination-context' + +export type PaginationNextProps = React.ButtonHTMLAttributes + +const PaginationNext: React.FC> = ({ + children, + ...props +}) => { + const { update, isLast } = usePaginationContext() + return ( + update && update('next')} disabled={isLast} {...props}> + {children} + + ) +} + +export default PaginationNext diff --git a/components/pagination/pagination-pages.tsx b/components/pagination/pagination-pages.tsx new file mode 100644 index 000000000..50a24a182 --- /dev/null +++ b/components/pagination/pagination-pages.tsx @@ -0,0 +1,104 @@ +import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react' +import PaginationItem from './pagination-item' +import PaginationEllipsis from 'components/pagination/pagination-ellipsis' + +interface Props { + limit: number + count: number + current: number + setPage: Dispatch> +} + +const PaginationPages: React.FC = ({ 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) => ( + setPage(value)}> + {value} + + ), + [], + ) + 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 ( + setPage(value)}> + {value} + + ) + }) + 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 ( + setPage(value)}> + {value} + + ) + })} + + ) + /* eslint-enable */ + } + return ( + <> + {renderItem(1, current)} + {showBeforeEllipsis && ( + setPage(last => (last - 5 >= 1 ? last - 5 : 1))} + /> + )} + {showBeforeEllipsis && showAfterEllipsis + ? middlePages + : showBeforeEllipsis + ? endPages + : startPages} + {showAfterEllipsis && ( + setPage(last => (last + 5 <= count ? last + 5 : count))} + /> + )} + {renderItem(count, current)} + + ) +} + +export default PaginationPages diff --git a/components/pagination/pagination-previous.tsx b/components/pagination/pagination-previous.tsx new file mode 100644 index 000000000..79b2bf961 --- /dev/null +++ b/components/pagination/pagination-previous.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import PaginationItem from './pagination-item' +import { usePaginationContext } from './pagination-context' + +export type PaginationNextProps = React.ButtonHTMLAttributes + +const PaginationPrevious: React.FC> = ({ + children, + ...props +}) => { + const { update, isFirst } = usePaginationContext() + return ( + update && update('prev')} disabled={isFirst} {...props}> + {children} + + ) +} + +export default PaginationPrevious diff --git a/components/pagination/pagination.tsx b/components/pagination/pagination.tsx new file mode 100644 index 000000000..80727857e --- /dev/null +++ b/components/pagination/pagination.tsx @@ -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, 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> = ({ + 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 = prev + const nextDefault = next + 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( + () => ({ + isFirst: page <= 1, + isLast: page >= count, + update, + }), + [page], + ) + + useEffect(() => { + onChange && onChange(page) + }, [page]) + useEffect(() => { + if (customPage !== undefined) { + setPage(customPage) + } + }, [customPage]) + + return ( + + + + + ) +} + +type MemoPaginationComponent

    = React.NamedExoticComponent

    & { + Previous: typeof PaginationPrevious + Next: typeof PaginationNext +} + +type ComponentProps = Partial & + Omit & + NativeAttrs + +Pagination.defaultProps = defaultProps + +export default React.memo(Pagination) as MemoPaginationComponent From c30e80774f50791232c717d01c457165b56d4a32 Mon Sep 17 00:00:00 2001 From: unix Date: Tue, 9 Jun 2020 08:38:11 +0800 Subject: [PATCH 13/28] docs(pagination): add docs for pagination --- lib/data/metadata-en-us.json | 2 +- lib/data/metadata-zh-cn.json | 2 +- pages/en-us/components/pagination.mdx | 108 +++++++++++++++++++++++++ pages/zh-cn/components/pagination.mdx | 109 ++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 pages/en-us/components/pagination.mdx create mode 100644 pages/zh-cn/components/pagination.mdx diff --git a/lib/data/metadata-en-us.json b/lib/data/metadata-en-us.json index cd546e6a3..edc4c2238 100644 --- a/lib/data/metadata-en-us.json +++ b/lib/data/metadata-en-us.json @@ -1 +1 @@ -[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"breadcrumbs","url":"/en-us/components/breadcrumbs","index":100,"group":"Navigation"},{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"use-media-query","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}] +[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"breadcrumbs","url":"/en-us/components/breadcrumbs","index":100,"group":"Navigation"},{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"Pagination","url":"/en-us/components/pagination","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"use-media-query","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}] diff --git a/lib/data/metadata-zh-cn.json b/lib/data/metadata-zh-cn.json index 51e0f0229..059d54af0 100644 --- a/lib/data/metadata-zh-cn.json +++ b/lib/data/metadata-zh-cn.json @@ -1 +1 @@ -[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"面包屑 Breadcrumbs","url":"/zh-cn/components/breadcrumbs","index":100,"group":"导航"},{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}] +[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"面包屑 Breadcrumbs","url":"/zh-cn/components/breadcrumbs","index":100,"group":"导航"},{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"分页 Pagination","url":"/zh-cn/components/pagination","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}] diff --git a/pages/en-us/components/pagination.mdx b/pages/en-us/components/pagination.mdx new file mode 100644 index 000000000..3c7fd857a --- /dev/null +++ b/pages/en-us/components/pagination.mdx @@ -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. + + +`} /> + + + + + + + + +`} /> + + + + + + + + + + + + + + + + + +`} /> + + + + + + + +`} /> + + +Pagination.Props + +| 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', ...` | - | + +Pagination.Previous.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | native props | `ButtonHTMLAttributes` | `'id', 'className', ...` | - | + +Pagination.Next.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | native props | `ButtonHTMLAttributes` | `'id', 'className', ...` | - | + +NormalSizes + +```ts +type NormalSizes = 'mini' | 'small' | 'medium' | 'large' +``` + + + +export default ({ children }) => {children} diff --git a/pages/zh-cn/components/pagination.mdx b/pages/zh-cn/components/pagination.mdx new file mode 100644 index 000000000..66721c06b --- /dev/null +++ b/pages/zh-cn/components/pagination.mdx @@ -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 / 分页 + +多个页面之间的导航与鉴别。 + + +`} /> + + + + + + + + +`} /> + + + + + + + + + + + + + + + + + +`} /> + + + + + + + +`} /> + + +Pagination.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| **initialPage** | 初始选中的页面 | `number` | - | 1 | +| **page** | 当前页码 | `number` | - | 1 | +| **count** | 页码数量 | `number` | - | 1 | +| **limit** | 一次可展示页面的最大值 | `number` | - | 7 | +| **size** | 组件的大小 | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` | +| **onChange** | 分页器的事件 | `(page: number) => void` | - | - | +| ... | 原生属性 | `HTMLAttributes` | `'id', 'className', ...` | - | + +Pagination.Previous.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | 原生属性 | `ButtonHTMLAttributes` | `'id', 'className', ...` | - | + +Pagination.Next.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | 原生属性 | `ButtonHTMLAttributes` | `'id', 'className', ...` | - | + +NormalSizes + +```ts +type NormalSizes = 'mini' | 'small' | 'medium' | 'large' +``` + + + +export default ({ children }) => {children} From 7919d1c731049ff48894d49db426694622e0bdf9 Mon Sep 17 00:00:00 2001 From: unix Date: Tue, 9 Jun 2020 10:14:08 +0800 Subject: [PATCH 14/28] test(pagination): add testcase --- .../__snapshots__/pagination.test.tsx.snap | 189 ++++++++++++++++++ .../pagination/__tests__/pagination.test.tsx | 148 ++++++++++++++ components/pagination/pagination-item.tsx | 2 +- components/pagination/pagination-pages.tsx | 2 +- 4 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 components/pagination/__tests__/__snapshots__/pagination.test.tsx.snap create mode 100644 components/pagination/__tests__/pagination.test.tsx diff --git a/components/pagination/__tests__/__snapshots__/pagination.test.tsx.snap b/components/pagination/__tests__/__snapshots__/pagination.test.tsx.snap new file mode 100644 index 000000000..142bc1fd1 --- /dev/null +++ b/components/pagination/__tests__/__snapshots__/pagination.test.tsx.snap @@ -0,0 +1,189 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pagination should render correctly 1`] = ` +"

    " +`; diff --git a/components/pagination/__tests__/pagination.test.tsx b/components/pagination/__tests__/pagination.test.tsx new file mode 100644 index 000000000..8bf9dfb3c --- /dev/null +++ b/components/pagination/__tests__/pagination.test.tsx @@ -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() + expect(wrapper.html()).toMatchSnapshot() + expect(() => wrapper.unmount()).not.toThrow() + }) + + it('the specified page should be activated', async () => { + const wrapper = mount() + 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() + + 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() + 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() + 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() + 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() + 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() + 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( + + custom-prev + custom-next + , + ) + const btns = wrapper.find('button') + expect(btns.at(0).text()).toEqual('custom-prev') + expect(btns.at(btns.length - 1).text()).toEqual('custom-next') + }) +}) diff --git a/components/pagination/pagination-item.tsx b/components/pagination/pagination-item.tsx index e4b728349..74824d76d 100644 --- a/components/pagination/pagination-item.tsx +++ b/components/pagination/pagination-item.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' import useTheme from '../styles/use-theme' -import { addColorAlpha } from 'components/utils/color' +import { addColorAlpha } from '../utils/color' interface Props { active?: boolean diff --git a/components/pagination/pagination-pages.tsx b/components/pagination/pagination-pages.tsx index 50a24a182..a90f58fdc 100644 --- a/components/pagination/pagination-pages.tsx +++ b/components/pagination/pagination-pages.tsx @@ -1,6 +1,6 @@ import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react' import PaginationItem from './pagination-item' -import PaginationEllipsis from 'components/pagination/pagination-ellipsis' +import PaginationEllipsis from './pagination-ellipsis' interface Props { limit: number From 90acd59697c5a538a8162aa8b5984e3b1d71020f Mon Sep 17 00:00:00 2001 From: unix Date: Tue, 9 Jun 2020 10:44:15 +0800 Subject: [PATCH 15/28] chore: release v1.7.0-canary.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b78e61954..bee170d80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zeit-ui/react", - "version": "1.7.0-canary.1", + "version": "1.7.0-canary.2", "main": "dist/index.js", "module": "esm/index.js", "types": "dist/index.d.ts", From e798497b91ff5965564dc95d1d634a436f714d64 Mon Sep 17 00:00:00 2001 From: unix Date: Wed, 10 Jun 2020 05:34:08 +0800 Subject: [PATCH 16/28] chore: release v1.7.0-canary.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bee170d80..2754f10c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zeit-ui/react", - "version": "1.7.0-canary.2", + "version": "1.7.0-canary.3", "main": "dist/index.js", "module": "esm/index.js", "types": "dist/index.d.ts", From 94cce07e6e20e7776876d1c0bef20a3577f99904 Mon Sep 17 00:00:00 2001 From: unix Date: Fri, 12 Jun 2020 05:38:13 +0800 Subject: [PATCH 17/28] feat(radio): support for number type --- components/radio/radio-context.ts | 4 ++-- components/radio/radio-group.tsx | 10 +++++----- components/radio/radio.tsx | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/radio/radio-context.ts b/components/radio/radio-context.ts index b29271bce..41fcce416 100644 --- a/components/radio/radio-context.ts +++ b/components/radio/radio-context.ts @@ -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 } diff --git a/components/radio/radio-group.tsx b/components/radio/radio-group.tsx index 802d3ce6d..21954f117 100644 --- a/components/radio/radio-group.tsx +++ b/components/radio/radio-group.tsx @@ -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> = ({ useRow, ...props }) => { - const [selfVal, setSelfVal] = useState(initialValue) - const updateState = (nextValue: string) => { + const [selfVal, setSelfVal] = useState(initialValue) + const updateState = (nextValue: string | number) => { setSelfVal(nextValue) onChange && onChange(nextValue) } diff --git a/components/radio/radio.tsx b/components/radio/radio.tsx index b449ad7e8..15109e4e8 100644 --- a/components/radio/radio.tsx +++ b/components/radio/radio.tsx @@ -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> = ({ } setSelfChecked(!selfChecked) if (inGroup) { - updateState && updateState(radioValue as string) + updateState && updateState(radioValue as string | number) } onChange && onChange(selfEvent) } From 1703046caf0990b6088531d3d786accb8a730796 Mon Sep 17 00:00:00 2001 From: unix Date: Fri, 12 Jun 2020 05:38:33 +0800 Subject: [PATCH 18/28] test(radio): add testcase for number type --- components/radio/__tests__/group.test.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/components/radio/__tests__/group.test.tsx b/components/radio/__tests__/group.test.tsx index 9cb15c5e7..22bd1e1ab 100644 --- a/components/radio/__tests__/group.test.tsx +++ b/components/radio/__tests__/group.test.tsx @@ -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( + + Option 1 + Option 2 + , + ) + + 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( From 8deb370d41940414d1fbc7bb41431805f617ab19 Mon Sep 17 00:00:00 2001 From: unix Date: Fri, 12 Jun 2020 05:45:05 +0800 Subject: [PATCH 19/28] chore: release v1.7.0-canary.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2754f10c7..a5102a155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zeit-ui/react", - "version": "1.7.0-canary.3", + "version": "1.7.0-canary.4", "main": "dist/index.js", "module": "esm/index.js", "types": "dist/index.d.ts", From fc0cdec223bb358368589463adf9b28a6d5ac047 Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 13 Jun 2020 06:31:12 +0800 Subject: [PATCH 20/28] feat(image): pass through all props of anchor element --- components/image/__tests__/browser.test.tsx | 11 +++++++++++ components/image/image-browser.tsx | 20 +++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/components/image/__tests__/browser.test.tsx b/components/image/__tests__/browser.test.tsx index 7533daa57..db6c82882 100644 --- a/components/image/__tests__/browser.test.tsx +++ b/components/image/__tests__/browser.test.tsx @@ -59,4 +59,15 @@ describe('Image Browser', () => { const wrapper = mount() expect(() => wrapper.unmount()).not.toThrow() }) + + it('anchor props should be passed through', () => { + const anchorRel = 'noreferrer' + const wrapper = mount( + + + , + ) + const rel = wrapper.find('a').getDOMNode().getAttribute('rel') + expect(anchorRel).toEqual(anchorRel) + }) }) diff --git a/components/image/image-browser.tsx b/components/image/image-browser.tsx index 14f5e1a51..ce420f93c 100644 --- a/components/image/image-browser.tsx +++ b/components/image/image-browser.tsx @@ -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, 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) => (
    ) -const getAddressInput = (url: string, showFullLink: boolean, colors: BrowserColors) => ( +const getAddressInput = ( + url: string, + showFullLink: boolean, + colors: BrowserColors, + anchorProps: AnchorProps, +) => (
    - + {showFullLink ? url : getHostFromUrl(url)} From 9ae21bac11248be3047d8a0878172316decdd2dc Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 20 Jun 2020 13:05:43 +0800 Subject: [PATCH 26/28] test(grid): add testcase for grid hidden --- .../__snapshots__/index.test.tsx.snap | 120 ++++++++++++++++++ components/grid/__tests__/index.test.tsx | 6 + 2 files changed, 126 insertions(+) diff --git a/components/grid/__tests__/__snapshots__/index.test.tsx.snap b/components/grid/__tests__/__snapshots__/index.test.tsx.snap index 1f8e91768..c0253ff72 100644 --- a/components/grid/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/grid/__tests__/__snapshots__/index.test.tsx.snap @@ -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; } }
    " @@ -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; } } " @@ -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; } } " @@ -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; } }
    test
    test
    test
    ,
    ,
    ," @@ -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; } }
    test
    " @@ -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; } }
    test
    " diff --git a/components/grid/__tests__/index.test.tsx b/components/grid/__tests__/index.test.tsx index 72250d0bf..f40b00a89 100644 --- a/components/grid/__tests__/index.test.tsx +++ b/components/grid/__tests__/index.test.tsx @@ -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() + expect(wrapper.find('.item').hasClass('xs')).toBeTruthy() + expect(wrapper.find('.item').html()).toContain('display: none') + }) }) From fb4e08c834522ac7754b13d095c44b966e3277ad Mon Sep 17 00:00:00 2001 From: unix Date: Sat, 20 Jun 2020 13:10:19 +0800 Subject: [PATCH 27/28] docs(grid): add examples for grid hidden --- pages/en-us/components/grid.mdx | 20 ++++++++++++++++++++ pages/zh-cn/components/grid.mdx | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pages/en-us/components/grid.mdx b/pages/en-us/components/grid.mdx index b4ed13ceb..242bfc61d 100644 --- a/pages/en-us/components/grid.mdx +++ b/pages/en-us/components/grid.mdx @@ -79,6 +79,26 @@ and very small size. Of course, it still supports dynamic props and custom break } `} /> + { + const MockItem = () => { + return + } + return ( + + + + + + + ) +} +`} /> + + { + const MockItem = () => { + return + } + return ( + + + + + + + ) +} +`} /> + Date: Sat, 20 Jun 2020 13:18:24 +0800 Subject: [PATCH 28/28] test: update grid snapshots --- .../select/__tests__/__snapshots__/multiple.test.tsx.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/select/__tests__/__snapshots__/multiple.test.tsx.snap b/components/select/__tests__/__snapshots__/multiple.test.tsx.snap index fa8a6270c..f850dbe87 100644 --- a/components/select/__tests__/__snapshots__/multiple.test.tsx.snap +++ b/components/select/__tests__/__snapshots__/multiple.test.tsx.snap @@ -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; } }