diff --git a/components/badge/__tests__/__snapshots__/anchor.test.tsx.snap b/components/badge/__tests__/__snapshots__/anchor.test.tsx.snap new file mode 100644 index 0000000..35c4750 --- /dev/null +++ b/components/badge/__tests__/__snapshots__/anchor.test.tsx.snap @@ -0,0 +1,124 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BadgeAnchor should be support multiple position 1`] = ` +"
test
" +`; + +exports[`BadgeAnchor should be support multiple position 2`] = ` +"
test
" +`; + +exports[`BadgeAnchor should be support multiple position 3`] = ` +"
test
" +`; diff --git a/components/badge/__tests__/__snapshots__/index.test.tsx.snap b/components/badge/__tests__/__snapshots__/index.test.tsx.snap index 761d646..ec27c54 100644 --- a/components/badge/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/badge/__tests__/__snapshots__/index.test.tsx.snap @@ -11,7 +11,7 @@ initialize { "children": Array [ Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -33,6 +33,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -70,6 +75,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -97,7 +107,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -119,6 +129,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -156,6 +171,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -183,7 +203,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -205,6 +225,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -242,6 +267,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -269,7 +299,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -291,6 +321,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -328,6 +363,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -396,7 +436,7 @@ initialize { }, Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -418,6 +458,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -455,6 +500,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -482,7 +532,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -504,6 +554,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -541,6 +596,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -568,7 +628,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -590,6 +650,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -627,6 +692,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -676,7 +746,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -698,6 +768,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -735,6 +810,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -781,7 +861,7 @@ initialize { }, Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -803,6 +883,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -840,6 +925,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -867,7 +957,7 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -889,6 +979,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -926,6 +1021,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -965,7 +1065,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -987,6 +1087,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1024,6 +1129,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1053,7 +1163,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1075,6 +1185,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1112,6 +1227,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1166,7 +1286,7 @@ initialize { }, Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1188,6 +1308,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1225,6 +1350,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1254,7 +1384,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1276,6 +1406,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1313,6 +1448,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1342,7 +1482,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1364,6 +1504,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1401,6 +1546,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1430,7 +1580,7 @@ initialize { "parent": [Circular], "prev": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1452,6 +1602,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1489,6 +1644,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1588,7 +1748,7 @@ exports[`Badge should supoort text 1`] = ` initialize { "0": Object { "attribs": Object { - "class": "", + "class": " ", }, "children": Array [ Object { @@ -1610,6 +1770,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], @@ -1647,6 +1812,11 @@ initialize { font-size: .875rem; border: 0; } + + .dot { + padding: 4px; + border-radius: 50%; + } ", "next": null, "parent": [Circular], diff --git a/components/badge/__tests__/anchor.test.tsx b/components/badge/__tests__/anchor.test.tsx new file mode 100644 index 0000000..e722141 --- /dev/null +++ b/components/badge/__tests__/anchor.test.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import { mount } from 'enzyme' +import { Badge } from 'components' + +describe('BadgeAnchor', () => { + it('should render correctly', () => { + const wrapper = mount( + + test + link + + ) + expect(() => wrapper.unmount()).not.toThrow() + }) + + it('should be work without Badge', () => { + const wrapper = mount( + + link + + ) + expect(() => wrapper.unmount()).not.toThrow() + }) + + it('should be support multiple position', () => { + const wrapper = mount( + + test + + + ) + expect(wrapper.html()).toMatchSnapshot() + + wrapper.setProps({ placement: 'topLeft' }) + expect(wrapper.html()).toMatchSnapshot() + + wrapper.setProps({ placement: 'bottomRight' }) + expect(wrapper.html()).toMatchSnapshot() + }) +}) diff --git a/components/badge/__tests__/index.test.tsx b/components/badge/__tests__/index.test.tsx index 808491b..45f4d34 100644 --- a/components/badge/__tests__/index.test.tsx +++ b/components/badge/__tests__/index.test.tsx @@ -52,4 +52,10 @@ describe('Badge', () => { expect(span.props().style).not.toBeUndefined() expect((span.props().style as any).background).toBe('white') }) + + it('should hide content when in dot mode', () => { + const wrapper = mount(test-value) + expect(wrapper.html()).not.toContain('test-value') + expect(() => wrapper.unmount()).not.toThrow() + }) }) diff --git a/components/badge/badge-anchor.tsx b/components/badge/badge-anchor.tsx new file mode 100644 index 0000000..7120bd3 --- /dev/null +++ b/components/badge/badge-anchor.tsx @@ -0,0 +1,102 @@ +import React, { useMemo } from 'react' +import withDefaults from '../utils/with-defaults' +import { pickChild } from '../utils/collections' +import { tuple } from '../utils/prop-types' +import Badge from './badge' + +const placement = tuple( + 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', +) + +type BadgeAnchorPlacement = typeof placement[number] + +interface Props { + placement?: BadgeAnchorPlacement + className?: string +} + +const defaultProps = { + placement: 'topRight' as BadgeAnchorPlacement, + className: '', +} + +type NativeAttrs = Omit, keyof Props> +export type BadgeAnchorProps = Props & typeof defaultProps & NativeAttrs + +type TransformStyles = { + top?: string + bottom?: string + left?: string + right?: string + value: string + origin: string +} + +const getTransform = (placement: BadgeAnchorPlacement): TransformStyles => { + const styles: { [key in BadgeAnchorPlacement]: TransformStyles } = { + topLeft: { + top: '0', left: '0', + value: 'translate(-50%, -50%)', + origin: '0% 0%', + }, + topRight: { + top: '0', right: '0', + value: 'translate(50%, -50%)', + origin: '100% 0%', + }, + bottomLeft: { + left: '0', bottom: '0', + value: 'translate(-50%, 50%)', + origin: '0% 100%', + }, + bottomRight: { + right: '0', bottom: '0', + value: 'translate(50%, 50%)', + origin: '100% 100%', + } + } + return styles[placement] +} + +const BadgeAnchor: React.FC> = ({ + children, placement, +}) => { + const [withoutBadgeChildren, badgeChldren] = pickChild(children, Badge) + const { + top, bottom, left, right, value, origin, + } = useMemo(() => getTransform(placement), [placement]) + + return ( +
+ {withoutBadgeChildren} + + {badgeChldren} + + + +
+ ) +} + +const MemoBadgeAnchor = React.memo(BadgeAnchor) + +export default withDefaults(MemoBadgeAnchor, defaultProps) diff --git a/components/badge/badge.tsx b/components/badge/badge.tsx index 047fd96..64dfdec 100644 --- a/components/badge/badge.tsx +++ b/components/badge/badge.tsx @@ -1,18 +1,20 @@ import React, { useMemo } from 'react' -import withDefaults from '../utils/with-defaults' import useTheme from '../styles/use-theme' import { NormalSizes, NormalTypes } from '../utils/prop-types' import { ZeitUIThemesPalette } from 'components/styles/themes' +import BadgeAnchor from './badge-anchor' interface Props { type?: NormalTypes size?: NormalSizes + dot?: boolean className?: string } const defaultProps = { type: 'default' as NormalTypes, size: 'medium' as NormalSizes, + dot: false, className: '', } @@ -41,7 +43,7 @@ const getBgColor = (type: NormalTypes, palette: ZeitUIThemesPalette) => { } const Badge: React.FC> = ({ - type, size, className, children, ...props + type, size, className, children, dot, ...props }) => { const theme = useTheme() const bg = useMemo(() => getBgColor(type, theme.palette), [type, theme.palette]) @@ -52,8 +54,8 @@ const Badge: React.FC> = ({ }, [type, theme.palette.background]) return ( - - {children} + + {!dot && children} ) } -const MemoBadge = React.memo>(Badge) +type MemoBadgeComponent

= React.NamedExoticComponent

& { + Anchor: typeof BadgeAnchor +} +type ComponentProps = Partial & Omit & NativeAttrs -export default withDefaults(MemoBadge, defaultProps) +Badge.defaultProps = defaultProps + +export default React.memo(Badge) as MemoBadgeComponent diff --git a/components/badge/index.ts b/components/badge/index.ts index a712806..191d733 100644 --- a/components/badge/index.ts +++ b/components/badge/index.ts @@ -1,3 +1,6 @@ import Badge from './badge' +import BadgeAnchor from './badge-anchor' + +Badge.Anchor = BadgeAnchor export default Badge diff --git a/pages/en-us/components/badge.mdx b/pages/en-us/components/badge.mdx index 953380e..38a0fbc 100644 --- a/pages/en-us/components/badge.mdx +++ b/pages/en-us/components/badge.mdx @@ -1,5 +1,5 @@ import { Layout, Playground, Attributes } from 'lib/components' -import { Badge, Spacer } from 'components' +import { Badge, Spacer, Avatar, Button, Link } from 'components' export const meta = { title: 'Badge', @@ -48,16 +48,78 @@ Display an indicator that requires attention. `} /> + + + 10 + + + + + 10 + + + + + 99+ + + + + + + ZEIT UI + + + + + Share Link + + +`} /> Badge.Props | Attribute | Description | Type | Accepted values | Default | ---------- | ---------- | ---- | -------------- | ------ | -| **type** | badge type | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` | -| **size** | badge size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` | +| **type** | badge type | `NormalTypes` | [NormalTypes](#normaltypes) | `default` | +| **size** | badge size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` | +| **dot** | show dot and ignore content | `boolean` | - | `false` | | ... | native props | `HTMLAttributes` | `'alt', 'id', 'className', ...` | - | +Badge.Anchor.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| **placement** | fixe position of Badge | `AnchorPlacement` | [AnchorPlacement](#anchorplacement) | `topRight` | +| ... | native props | `HTMLAttributes` | `'alt', 'id', 'className', ...` | - | + +NormalTypes + +```ts +type NormalTypes = 'default' + | 'secondary' + | 'success' + | 'warning' + | 'error' +``` + +NormalSizes + +```ts +type NormalSizes = 'medium' | 'mini' | 'small' | 'large' +``` + +AnchorPlacement + +```ts +type AnchorPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' +``` + export default ({ children }) => {children} diff --git a/pages/zh-cn/components/badge.mdx b/pages/zh-cn/components/badge.mdx index 4ceabbd..b7409f7 100644 --- a/pages/zh-cn/components/badge.mdx +++ b/pages/zh-cn/components/badge.mdx @@ -1,5 +1,5 @@ import { Layout, Playground, Attributes } from 'lib/components' -import { Badge, Spacer } from 'components' +import { Badge, Spacer, Button, Link, Avatar } from 'components' export const meta = { title: '徽章 Badge', @@ -48,16 +48,78 @@ export const meta = { `} /> + + + 10 + + + + + 10 + + + + + 99+ + + + + + + 组件库 + + + + + 分享链接 + + +`} /> Badge.Props | 属性 | 描述 | 类型 | 推荐值 | 默认 | ---------- | ---------- | ---- | -------------- | ------ | -| **type** | 徽章的类型 | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` | -| **size** | 徽章的大小 | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` | +| **type** | 徽章的类型 | `NormalTypes` | [NormalTypes](#normaltypes) | `default` | +| **size** | 徽章的大小 | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` | +| **dot** | 忽略内容并显示圆点 | `boolean` | - | `false` | | ... | 原生属性 | `HTMLAttributes` | `'alt', 'id', 'className', ...` | - | +Badge.Anchor.Props + +| 属性 | 描述 | 类型 | 推荐值 | 默认 +| ---------- | ---------- | ---- | -------------- | ------ | +| **placement** | 固定徽章的位置 | `AnchorPlacement` | [AnchorPlacement](#anchorplacement) | `topRight` | +| ... | 原生属性 | `HTMLAttributes` | `'alt', 'id', 'className', ...` | - | + +NormalTypes + +```ts +type NormalTypes = 'default' + | 'secondary' + | 'success' + | 'warning' + | 'error' +``` + +NormalSizes + +```ts +type NormalSizes = 'medium' | 'mini' | 'small' | 'large' +``` + +AnchorPlacement + +```ts +type AnchorPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' +``` + export default ({ children }) => {children}