From 3c46949597d49110f6affd019b22066dbef691ba Mon Sep 17 00:00:00 2001 From: unix Date: Thu, 19 Mar 2020 23:09:45 +0800 Subject: [PATCH 1/4] feat(radio): add component --- components/index.ts | 1 + components/radio/index.ts | 9 + components/radio/radio-context.ts | 18 ++ components/radio/radio-description.tsx | 33 ++++ components/radio/radio-group.tsx | 71 ++++++++ components/radio/radio.tsx | 161 ++++++++++++++++++ components/toast/use-toast.tsx | 7 +- components/utils/collections.ts | 6 +- components/utils/use-portal.ts | 5 +- .../attributes/attributes-title.tsx | 26 ++- pages/docs/components/radio.mdx | 118 +++++++++++++ 11 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 components/radio/index.ts create mode 100644 components/radio/radio-context.ts create mode 100644 components/radio/radio-description.tsx create mode 100644 components/radio/radio-group.tsx create mode 100644 components/radio/radio.tsx create mode 100644 pages/docs/components/radio.mdx diff --git a/components/index.ts b/components/index.ts index 698f621..885f98d 100644 --- a/components/index.ts +++ b/components/index.ts @@ -30,3 +30,4 @@ export { default as Spinner } from './spinner' export { default as ButtonDropdown } from './button-dropdown' export { default as Capacity } from './capacity' export { default as Input } from './input' +export { default as Radio } from './radio' diff --git a/components/radio/index.ts b/components/radio/index.ts new file mode 100644 index 0000000..c3ee9bc --- /dev/null +++ b/components/radio/index.ts @@ -0,0 +1,9 @@ +import Radio from './radio' +import RadioGroup from './radio-group' +import RadioDescription from './radio-description' + +Radio.Group = RadioGroup +Radio.Description = RadioDescription +Radio.Desc = RadioDescription + +export default Radio diff --git a/components/radio/radio-context.ts b/components/radio/radio-context.ts new file mode 100644 index 0000000..9a0ecb5 --- /dev/null +++ b/components/radio/radio-context.ts @@ -0,0 +1,18 @@ +import React from 'react' + +export interface RadioConfig { + updateState: (value: string) => void + disabledAll: boolean + value?: string + inGroup: boolean +} + +const defaultContext = { + disabledAll: false, + updateState: () => {}, + inGroup: false, +} + +export const RadioContext = React.createContext(defaultContext) + +export const useRadioContext = (): RadioConfig => React.useContext(RadioContext) diff --git a/components/radio/radio-description.tsx b/components/radio/radio-description.tsx new file mode 100644 index 0000000..5b36127 --- /dev/null +++ b/components/radio/radio-description.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import withDefaults from '../utils/with-defaults' +import useTheme from '../styles/use-theme' + +interface Props { + className?: string +} + +const defaultProps = { + className: '', +} + +export type RadioDescriptionProps = Props & typeof defaultProps & React.HTMLAttributes + +const RadioDescription: React.FC> = React.memo(({ + className, children, ...props +}) => { + const theme = useTheme() + + return ( + + {children} + + + ) +}) + +export default withDefaults(RadioDescription, defaultProps) diff --git a/components/radio/radio-group.tsx b/components/radio/radio-group.tsx new file mode 100644 index 0000000..076a2dd --- /dev/null +++ b/components/radio/radio-group.tsx @@ -0,0 +1,71 @@ +import React, { useEffect, useMemo, useState } from 'react' +import withDefaults from '../utils/with-defaults' +import useTheme from '../styles/use-theme' +import { RadioContext } from './radio-context' + +interface Props { + value: string + initialValue?: string + disabled?: boolean + onChange?: (value: string) => void + className?: string + useRow?: boolean +} + +const defaultProps = { + disabled: false, + className: '', + useRow: false, +} + +export type RadioGroupProps = Props & typeof defaultProps & React.HTMLAttributes + +const RadioGroup: React.FC> = React.memo(({ + disabled, onChange, value, children, className, initialValue, useRow, ...props +}) => { + const theme = useTheme() + const [selfVal, setSelfVal] = useState(initialValue) + + const updateState = (nextValue: string) => { + setSelfVal(nextValue) + onChange && onChange(nextValue) + } + + const providerValue = useMemo(() => { + return { + updateState, + disabledAll: disabled, + inGroup: true, + value: selfVal, + } + },[disabled, selfVal]) + + useEffect(() => { + setSelfVal(value) + }, [value]) + + return ( + +
+ {children} +
+ +
+ ) +}) + +export default withDefaults(RadioGroup, defaultProps) diff --git a/components/radio/radio.tsx b/components/radio/radio.tsx new file mode 100644 index 0000000..a4f8126 --- /dev/null +++ b/components/radio/radio.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useMemo, useState } from 'react' +import useTheme from '../styles/use-theme' +import { useRadioContext } from './radio-context' +import RadioGroup from './radio-group' +import RadioDescription from './radio-description' +import { pickChild } from '../utils/collections' + +interface RadioEventTarget { + checked: boolean +} + +export interface RadioEvent { + target: RadioEventTarget + stopPropagation: () => void + preventDefault: () => void + nativeEvent: React.ChangeEvent +} + +interface Props { + checked?: boolean + value?: string + id?: string + className?: string + disabled?: boolean + onChange: (e: RadioEvent) => void +} + +const defaultProps = { + disabled: false, + className: '', +} + +export type RadioProps = Props & typeof defaultProps & React.InputHTMLAttributes + +const Radio: React.FC> = React.memo(({ + className, id: customId, checked, onChange, disabled, + value: radioValue, children, ...props +}) => { + const theme = useTheme() + const [selfChecked, setSelfChecked] = useState(!!checked) + const { value: groupValue, disabledAll, inGroup, updateState } = useRadioContext() + const [withoutDescChildren, DescChildren] = pickChild(children, RadioDescription) + + if (inGroup) { + if (checked !== undefined) { + console.error('[Radio]: remove props "checked" if in the Radio.Group.') + } + if (radioValue === undefined) { + console.error('[Radio]: props "value" must be deinfed if in the Radio.Group.') + } + useEffect(() => setSelfChecked(groupValue === radioValue), [groupValue, radioValue]) + } + + // const id = useMemo(() => customId || `zeit-ui-radio-${label}`, [customId, label]) + const isDisabled = useMemo(() => disabled || disabledAll, [disabled, disabledAll]) + const changeHandler = (event: React.ChangeEvent) => { + if (isDisabled) return + const selfEvent: RadioEvent = { + target: { + checked: !selfChecked, + }, + stopPropagation: event.stopPropagation, + preventDefault: event.preventDefault, + nativeEvent: event, + } + setSelfChecked(!selfChecked) + if (inGroup) { + updateState(radioValue as string) + } + onChange && onChange(selfEvent) + } + + useEffect(() => setSelfChecked(!!checked), [checked]) + + return ( +
+ + + +
+ ) +}) + + +Radio.defaultProps = defaultProps + +type RadioComponent

= React.FC

& { + Group: typeof RadioGroup + Desc: typeof RadioDescription + Description: typeof RadioDescription +} + +type ComponentProps = Partial & Omit + +export default Radio as RadioComponent diff --git a/components/toast/use-toast.tsx b/components/toast/use-toast.tsx index eff4471..ee2874c 100644 --- a/components/toast/use-toast.tsx +++ b/components/toast/use-toast.tsx @@ -3,6 +3,7 @@ import { NormalTypes } from '../utils/prop-types' import useCurrentState from '../utils/use-current-state' import { useZEITUIContext } from '../utils/use-zeit-ui-context' import { ToastWithID } from './toast-container' +import { getId } from '../utils/collections' export interface ToastAction { name: string @@ -21,10 +22,6 @@ const defaultToast = { delay: 2000, } -const generateId = () => { - return Math.random().toString(32).slice(2, 10) -} - const useToasts = (): [Array, (t: Toast) => void] => { const { updateToasts, toastHovering, toasts } = useZEITUIContext() const destoryStack = useRef>([]) @@ -55,7 +52,7 @@ const useToasts = (): [Array, (t: Toast) => void] => { const setToast = (toast: Toast): void => { - const id = `toast-${generateId()}` + const id = `toast-${getId()}` const delay = toast.delay || defaultToast.delay const cancel = (id: string, delay: number) => { diff --git a/components/utils/collections.ts b/components/utils/collections.ts index 4b68711..82a4494 100644 --- a/components/utils/collections.ts +++ b/components/utils/collections.ts @@ -1,5 +1,9 @@ import React, { ReactNode } from 'react' +export const getId = () => { + return Math.random().toString(32).slice(2, 10) +} + export const hasChild = ( children: ReactNode | undefined, child: React.ElementType @@ -18,7 +22,7 @@ export const pickChild = ( ): [ReactNode | undefined, ReactNode | undefined] => { let target: ReactNode[] = [] const withoutTargetChildren = React.Children.map(children, item => { - if (!React.isValidElement(item)) return null + if (!React.isValidElement(item)) return item if (item.type === targetChild) { target.push(item) return null diff --git a/components/utils/use-portal.ts b/components/utils/use-portal.ts index fe9605d..f5b10fd 100644 --- a/components/utils/use-portal.ts +++ b/components/utils/use-portal.ts @@ -1,9 +1,6 @@ import { useEffect, useState } from 'react' import useSSR from '../utils/use-ssr' - -const getId = () => { - return Math.random().toString(32).slice(2, 10) -} +import { getId } from './collections' const createElement = (id: string): HTMLElement => { const el = document.createElement('div') diff --git a/lib/components/attributes/attributes-title.tsx b/lib/components/attributes/attributes-title.tsx index 8071ac9..ec3e639 100644 --- a/lib/components/attributes/attributes-title.tsx +++ b/lib/components/attributes/attributes-title.tsx @@ -2,15 +2,37 @@ import React from 'react' import { Spacer, Code } from 'components' export interface AttributesTitleProps { + alias?: string +} + +const getAlias = (alias?: string) => { + if (!alias) return null + return ( + [alias: {alias}] + ) } const AttributesTitle: React.FC> = React.memo(({ - children, + children, alias, }) => { return ( <> -

{children}

+

{children}{getAlias(alias)}

+ + ) }) diff --git a/pages/docs/components/radio.mdx b/pages/docs/components/radio.mdx new file mode 100644 index 0000000..615dc76 --- /dev/null +++ b/pages/docs/components/radio.mdx @@ -0,0 +1,118 @@ +import { Layout, Playground, Attributes } from 'lib/components' +import { Radio, Spacer, Code } from 'components' +import { useState } from 'react' + +export const meta = { + title: 'radio', + description: 'avatar', +} + +## Radio + +Provides single user input from a selection of options. + +Option 1 +`} /> + + { + const [state, setState] = useState('1') + const handler = val => { + setState(val) + console.log(val) + } + return ( + <> + + Option 1 + Option 2 + + + ) +} +`} /> + + console.log(val)}> + + Option 1 + Description for Option1 + + + Option 2 + Description for Option2 + + +`} /> + + + + Option 1 + Option 2 + +`} /> + + + + Option 1 + Description for Option1 + + + Option 2 + Description for Option2 + + +`} /> + + + +Radio.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| **checked** | selected or not (in single) | `boolean` | - | `false` | +| **value** | unique ident value (in group) | `string` | - | - | +| **id** | native attr | `string` | - | - | +| **disabled** | disable current radio | `boolean` | - | `false` | +| **onChange** | change event | `(e: RadioEvent) => void` | - | - | +| ... | native props | `InputHTMLAttributes` | `'name', 'alt', 'className', ...` | - | + +Radio.Group.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| **initialValue** | initial value | `string` | - | - | +| **value** | selected child radio | `string` | - | - | +| **useRow** | horizontal layout | `boolean` | - | `false` | +| **disabled** | disable all radios | `boolean` | - | `false` | +| **onChange** | change event | `(value: string) => void` | - | - | +| ... | native props | `HTMLAttributes` | `'name', 'id', 'className', ...` | - | + +Radio.Description.Props + +| Attribute | Description | Type | Accepted values | Default +| ---------- | ---------- | ---- | -------------- | ------ | +| ... | native props | `HTMLAttributes` | `'name', 'id', 'className', ...` | - | + + + +export default ({ children }) => {children} From b04dbfc0154c1fe47c00755b74c38cd2af8749d6 Mon Sep 17 00:00:00 2001 From: unix Date: Thu, 19 Mar 2020 23:16:41 +0800 Subject: [PATCH 2/4] chore: append edit links --- lib/components/attributes/attributes.tsx | 7 +++++-- pages/docs/components/avatar.mdx | 2 +- pages/docs/components/button-dropdown.mdx | 2 +- pages/docs/components/button.mdx | 2 +- pages/docs/components/capacity.mdx | 2 +- pages/docs/components/card.mdx | 2 +- pages/docs/components/checkbox.mdx | 2 +- pages/docs/components/code.mdx | 2 +- pages/docs/components/description.mdx | 2 +- pages/docs/components/display.mdx | 2 +- pages/docs/components/dot.mdx | 2 +- pages/docs/components/fieldset.mdx | 2 +- pages/docs/components/image.mdx | 2 +- pages/docs/components/input.mdx | 2 +- pages/docs/components/keyboard.mdx | 2 +- pages/docs/components/layout.mdx | 2 +- pages/docs/components/link.mdx | 2 +- pages/docs/components/modal.mdx | 2 +- pages/docs/components/note.mdx | 2 +- pages/docs/components/radio.mdx | 2 +- pages/docs/components/spacer.mdx | 4 ++-- pages/docs/components/spinner.mdx | 2 +- pages/docs/components/tag.mdx | 2 +- pages/docs/components/text.mdx | 2 +- pages/docs/components/toast.mdx | 2 +- 25 files changed, 30 insertions(+), 27 deletions(-) diff --git a/lib/components/attributes/attributes.tsx b/lib/components/attributes/attributes.tsx index 5fef74c..1037907 100644 --- a/lib/components/attributes/attributes.tsx +++ b/lib/components/attributes/attributes.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useMemo } from 'react' import { Card, Link, Spacer, useTheme } from 'components' import AttributesTitle from './attributes-title' @@ -10,6 +10,9 @@ const Attributes: React.FC> = React.mem edit, children, }) => { const theme = useTheme() + const link = useMemo(() => { + return `https://github.com/zeit-ui/react/blob/master${edit || '/pages'}` + }, []) return ( <> @@ -19,7 +22,7 @@ const Attributes: React.FC> = React.mem {children} - Edit this page on GitHub + Edit this page on GitHub