From a214b9a7fe69377c6f07067cd76faa3dce85fdf3 Mon Sep 17 00:00:00 2001 From: xiejay97 Date: Thu, 6 Jan 2022 18:39:00 +0800 Subject: [PATCH] feat(ui): add `switch` component --- packages/site/src/app/configs/icons.json | 22 +++ packages/site/src/app/styles/_app.scss | 2 + .../src/components/_select-box/SelectBox.tsx | 1 + packages/ui/src/components/button/Button.tsx | 6 +- .../src/components/button/demos/5.Loading.md | 2 +- .../ui/src/components/checkbox/Checkbox.tsx | 41 ++--- .../src/components/checkbox/CheckboxGroup.tsx | 25 +-- packages/ui/src/components/checkbox/README.md | 11 +- .../src/components/checkbox/README.zh-Hant.md | 11 +- .../src/components/checkbox/demos/1.Basic.md | 23 +-- .../src/components/checkbox/demos/4.Size.md | 51 ------ packages/ui/src/components/form/FormItem.tsx | 21 ++- .../form/demos/13.SupportComponents.md | 7 +- packages/ui/src/components/index.ts | 3 + packages/ui/src/components/radio/README.md | 8 +- .../ui/src/components/radio/README.zh-Hant.md | 8 +- packages/ui/src/components/radio/Radio.tsx | 44 ++--- .../ui/src/components/radio/RadioGroup.tsx | 12 +- .../ui/src/components/radio/demos/1.Basic.md | 21 +-- .../ui/src/components/radio/demos/4.Size.md | 24 --- packages/ui/src/components/select/Select.tsx | 13 +- packages/ui/src/components/switch/README.md | 29 ++++ .../src/components/switch/README.zh-Hant.md | 28 +++ packages/ui/src/components/switch/Switch.tsx | 163 ++++++++++++++++++ .../ui/src/components/switch/demos/1.Basic.md | 35 ++++ .../switch/demos/2.LabelPlacement.md | 21 +++ .../src/components/switch/demos/3.Content.md | 27 +++ .../src/components/switch/demos/4.Loading.md | 28 +++ packages/ui/src/components/switch/index.ts | 1 + packages/ui/src/hooks/two-way-binding.ts | 2 +- packages/ui/src/styles/_components.scss | 1 + .../ui/src/styles/components/_button.scss | 22 +-- .../ui/src/styles/components/_checkbox.scss | 10 -- .../ui/src/styles/components/_compose.scss | 1 + .../ui/src/styles/components/_dialog.scss | 2 - .../ui/src/styles/components/_drawer.scss | 1 - packages/ui/src/styles/components/_form.scss | 9 +- .../src/styles/components/_notification.scss | 1 - packages/ui/src/styles/components/_radio.scss | 12 +- .../ui/src/styles/components/_select-box.scss | 1 - .../ui/src/styles/components/_switch.scss | 130 ++++++++++++++ packages/ui/src/styles/components/_tabs.scss | 2 + packages/ui/src/styles/components/_tag.scss | 11 +- .../ui/src/styles/components/_textarea.scss | 2 - packages/ui/src/styles/components/_toast.scss | 1 - 45 files changed, 623 insertions(+), 273 deletions(-) delete mode 100644 packages/ui/src/components/checkbox/demos/4.Size.md create mode 100644 packages/ui/src/components/switch/README.md create mode 100644 packages/ui/src/components/switch/README.zh-Hant.md create mode 100644 packages/ui/src/components/switch/Switch.tsx create mode 100644 packages/ui/src/components/switch/demos/1.Basic.md create mode 100644 packages/ui/src/components/switch/demos/2.LabelPlacement.md create mode 100644 packages/ui/src/components/switch/demos/3.Content.md create mode 100644 packages/ui/src/components/switch/demos/4.Loading.md create mode 100644 packages/ui/src/components/switch/index.ts create mode 100644 packages/ui/src/styles/components/_switch.scss diff --git a/packages/site/src/app/configs/icons.json b/packages/site/src/app/configs/icons.json index eb395dda..c5517e10 100644 --- a/packages/site/src/app/configs/icons.json +++ b/packages/site/src/app/configs/icons.json @@ -132,5 +132,27 @@ ] } ] + }, + { + "name": "check", + "list": [ + { + "viewBox": "64 64 896 896", + "paths": [ + "M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" + ] + } + ] + }, + { + "name": "close", + "list": [ + { + "viewBox": "64 64 896 896", + "paths": [ + "M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" + ] + } + ] } ] diff --git a/packages/site/src/app/styles/_app.scss b/packages/site/src/app/styles/_app.scss index c8e83ca8..fed44f28 100644 --- a/packages/site/src/app/styles/_app.scss +++ b/packages/site/src/app/styles/_app.scss @@ -141,6 +141,8 @@ h3 { border-collapse: collapse; border-spacing: 0; + word-break: break-all; + table-layout: fixed; empty-cells: show; diff --git a/packages/ui/src/components/_select-box/SelectBox.tsx b/packages/ui/src/components/_select-box/SelectBox.tsx index 59bd151f..5cb54952 100644 --- a/packages/ui/src/components/_select-box/SelectBox.tsx +++ b/packages/ui/src/components/_select-box/SelectBox.tsx @@ -123,6 +123,7 @@ const SelectBox: React.ForwardRefRenderFunction 'is-expanded': dExpanded, 'is-disabled': disabled, })} + role="button" tabIndex={disabled ? undefined : tabIndex} aria-disabled={disabled} aria-haspopup="listbox" diff --git a/packages/ui/src/components/button/Button.tsx b/packages/ui/src/components/button/Button.tsx index b0e585c2..6d39375a 100644 --- a/packages/ui/src/components/button/Button.tsx +++ b/packages/ui/src/components/button/Button.tsx @@ -60,17 +60,17 @@ const Button: React.ForwardRefRenderFunction = (props, const buttonType = isUndefined(props.dType) ? buttonGroupType ?? dType : dType; const theme = isUndefined(props.dTheme) ? buttonGroupTheme ?? dTheme : dTheme; const size = dSize ?? gSize; - const _disabled = disabled || buttonGroupDisabled || gDisabled; + const _disabled = disabled || dLoading || buttonGroupDisabled || gDisabled; const handleClick = useCallback( (e) => { onClick?.(e); - if (!dLoading && (buttonType === 'primary' || buttonType === 'secondary' || buttonType === 'outline' || buttonType === 'dashed')) { + if (buttonType === 'primary' || buttonType === 'secondary' || buttonType === 'outline' || buttonType === 'dashed') { wave(e.currentTarget, `var(--${dPrefix}color-${theme})`); } }, - [theme, dLoading, dPrefix, onClick, buttonType, wave] + [theme, dPrefix, onClick, buttonType, wave] ); const buttonIcon = (loading: boolean, ref?: React.LegacyRef) => ( diff --git a/packages/ui/src/components/button/demos/5.Loading.md b/packages/ui/src/components/button/demos/5.Loading.md index c2979c71..5b80748a 100644 --- a/packages/ui/src/components/button/demos/5.Loading.md +++ b/packages/ui/src/components/button/demos/5.Loading.md @@ -1,7 +1,7 @@ --- title: en-US: Loading - zh-Hant: 加载中状态 + zh-Hant: 加载中 --- # en-US diff --git a/packages/ui/src/components/checkbox/Checkbox.tsx b/packages/ui/src/components/checkbox/Checkbox.tsx index 34842071..bdcaa498 100644 --- a/packages/ui/src/components/checkbox/Checkbox.tsx +++ b/packages/ui/src/components/checkbox/Checkbox.tsx @@ -6,31 +6,28 @@ import { usePrefixConfig, useComponentConfig, useCustomContext, useTwoWayBinding import { getClassName } from '../../utils'; import { DCheckboxGroupContext } from './CheckboxGroup'; -export type DCheckboxRef = HTMLInputElement; - export interface DCheckboxProps extends React.HTMLAttributes { dModel?: [boolean, Updater?]; dFormControlName?: string; dIndeterminate?: boolean; - dAriaControls?: string; - dSize?: 'smaller' | 'larger'; dDisabled?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any dValue?: any; + dInputProps?: React.InputHTMLAttributes; + dInputRef?: React.LegacyRef; onModelChange?: (checked: boolean) => void; } -const Checkbox: React.ForwardRefRenderFunction = (props, ref) => { +export function DCheckbox(props: DCheckboxProps) { const { dModel, dFormControlName, dIndeterminate = false, - dAriaControls, - dSize, dDisabled = false, dValue, + dInputProps, + dInputRef, onModelChange, - id, className, children, onChange, @@ -39,47 +36,43 @@ const Checkbox: React.ForwardRefRenderFunction = ( //#region Context const dPrefix = usePrefixConfig(); - const { gSize, gDisabled } = useGeneralState(); + const { gDisabled } = useGeneralState(); const [{ updateCheckboxs, removeCheckboxs, checkboxGroupValue, onCheckedChange }, checkboxGroupContext] = useCustomContext(DCheckboxGroupContext); //#endregion const uniqueId = useId(); - const _id = id ?? `${dPrefix}checkbox-${uniqueId}`; + const _id = dInputProps?.id ?? `${dPrefix}checkbox-input-${uniqueId}`; useStateBackflow(updateCheckboxs, removeCheckboxs, _id, dValue); const inGroup = checkboxGroupContext !== null; - const [checked, changeChecked, { validateClassName, ariaAttribute, controlDisabled }] = useTwoWayBinding( + const [checked, changeChecked, { ariaAttribute, controlDisabled }] = useTwoWayBinding( false, dModel ?? (dIndeterminate ? [undefined] : inGroup ? [checkboxGroupValue?.includes(dValue) ?? false] : undefined), onModelChange, dFormControlName ? { formControlName: dFormControlName, id: _id } : undefined ); - const size = dSize ?? gSize; const disabled = dDisabled || gDisabled || controlDisabled; const handleChange = useCallback>( (e) => { onChange?.(e); - if (!disabled) { - changeChecked(dIndeterminate ? true : !checked); - if (inGroup) { - onCheckedChange?.(dValue, dIndeterminate ? true : !checked); - } + changeChecked(dIndeterminate ? true : !checked); + if (inGroup) { + onCheckedChange?.(dValue, dIndeterminate ? true : !checked); } }, - [onChange, disabled, changeChecked, dIndeterminate, checked, inGroup, onCheckedChange, dValue] + [onChange, changeChecked, dIndeterminate, checked, inGroup, onCheckedChange, dValue] ); return (
= ( >
{!dIndeterminate && checked &&
} @@ -106,6 +99,4 @@ const Checkbox: React.ForwardRefRenderFunction = (
); -}; - -export const DCheckbox = React.forwardRef(Checkbox); +} diff --git a/packages/ui/src/components/checkbox/CheckboxGroup.tsx b/packages/ui/src/components/checkbox/CheckboxGroup.tsx index cdc777f2..d0fe28be 100644 --- a/packages/ui/src/components/checkbox/CheckboxGroup.tsx +++ b/packages/ui/src/components/checkbox/CheckboxGroup.tsx @@ -2,7 +2,7 @@ import type { DGeneralStateContextData } from '../../hooks/general-state'; import type { Updater } from '../../hooks/two-way-binding'; -import React, { useCallback, useId, useLayoutEffect, useMemo } from 'react'; +import React, { useCallback, useLayoutEffect, useMemo } from 'react'; import { usePrefixConfig, useComponentConfig, useTwoWayBinding, useGeneralState, DGeneralStateContext, useImmer } from '../../hooks'; import { getClassName } from '../../utils'; @@ -19,7 +19,6 @@ export const DCheckboxGroupContext = React.createContext { dModel?: [any[], Updater?]; dFormControlName?: string; - dSize?: 'smaller' | 'larger'; dDisabled?: boolean; dVertical?: boolean; dIndeterminateLabel?: (checked: boolean | 'mixed') => React.ReactNode; @@ -31,13 +30,11 @@ export function DCheckboxGroup(props: DCheckboxGroupProps) { const { dModel, dFormControlName, - dSize, dDisabled = false, dVertical = false, dIndeterminateLabel, dIndeterminateRef, onModelChange, - id, className, children, ...restProps @@ -45,22 +42,18 @@ export function DCheckboxGroup(props: DCheckboxGroupProps) { //#region Context const dPrefix = usePrefixConfig(); - const { gSize, gDisabled } = useGeneralState(); + const { gDisabled } = useGeneralState(); //#endregion - const uniqueId = useId(); - const _id = id ?? `${dPrefix}checkbox-group-${uniqueId}`; - const [checkboxs, setCheckboxs] = useImmer(new Map()); const [value, changeValue, { ariaAttribute, controlDisabled }] = useTwoWayBinding( [], dModel, onModelChange, - dFormControlName ? { formControlName: dFormControlName, id: _id } : undefined + dFormControlName ? { formControlName: dFormControlName } : undefined ); - const size = dSize ?? gSize; const disabled = dDisabled || gDisabled || controlDisabled; const allLength = React.Children.count(children); @@ -72,9 +65,11 @@ export function DCheckboxGroup(props: DCheckboxGroupProps) { item.id) - .join(' ')} + dInputProps={{ + 'aria-controls': Array.from(checkboxs.values()) + .map((item) => item.id) + .join(' '), + }} onModelChange={() => { checked === true ? changeValue([]) : changeValue(Array.from(checkboxs.values()).map((item) => item.value)); }} @@ -92,10 +87,9 @@ export function DCheckboxGroup(props: DCheckboxGroupProps) { const generalStateContextValue = useMemo( () => ({ - gSize: size, gDisabled: disabled, }), - [disabled, size] + [disabled] ); const stateBackflow = useMemo>( @@ -139,7 +133,6 @@ export function DCheckboxGroup(props: DCheckboxGroupProps) {
`. | --- | --- | --- | --- | | dModel | Manual control is checked | [boolean, Updater\?] | - | | dIndeterminate | Is it partially checked | boolean | false | -| dAriaControls | [aria-controls](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls) | string | - | -| dSize | Set size | 'smaller' \| 'larger' | - | | dDisabled | Whether to disable | boolean | false | | dValue | Pass as an identifier in checkbox group | any | - | +| dInputProps | Attributes applied to the `input` element | React.InputHTMLAttributes\ | - | +| dInputRef | Pass a `ref` to the `input` element | React.LegacyRef\ | - | | onModelChange | Selected change callback | `(checked: boolean) => void` | - | -### DCheckboxRef - -```tsx -type DCheckboxRef = HTMLInputElement; -``` - ### DCheckboxGroupProps Extend `React.HTMLAttributes`. @@ -41,7 +35,6 @@ Extend `React.HTMLAttributes`. | Property | Description | Type | Default | | --- | --- | --- | --- | | dModel | Manual control selection | [any[], Updater\?] | - | -| dSize | Checkbox group size | 'smaller' \| 'larger' | - | | dDisabled | Whether to disable | boolean | false | | dVertical | Vertical arrangement of checkbox group | boolean | false | | dIndeterminateLabel | Partially checked label content | `(checked: boolean \| 'mixed') => React.ReactNode` | - | diff --git a/packages/ui/src/components/checkbox/README.zh-Hant.md b/packages/ui/src/components/checkbox/README.zh-Hant.md index b0f82da5..95c32143 100644 --- a/packages/ui/src/components/checkbox/README.zh-Hant.md +++ b/packages/ui/src/components/checkbox/README.zh-Hant.md @@ -19,19 +19,13 @@ title: 多选组 | --- | --- | --- | --- | | dModel | 手动控制是否选中 | [boolean, Updater\?] | - | | dIndeterminate | 是否为部分选中 | boolean | false | -| dAriaControls | [aria-controls](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls) | string | - | -| dSize | 设置尺寸 | 'smaller' \| 'larger' | - | | dDisabled | 是否禁用 | boolean | false | | dValue | 多选组中作为标识传递 | any | - | +| dInputProps | 应用于 `input` 元素的属性 | React.InputHTMLAttributes\ | - | +| dInputRef | 将 `ref` 传递给 `input` 元素 | React.LegacyRef\ | - | | onModelChange | 选中改变的回调 | `(checked: boolean) => void` | - | -### DCheckboxRef - -```tsx -type DCheckboxRef = HTMLInputElement; -``` - ### DCheckboxGroupProps 继承 `React.HTMLAttributes`。 @@ -40,7 +34,6 @@ type DCheckboxRef = HTMLInputElement; | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | dModel | 手动控制选择 | [any[], Updater\?] | - | -| dSize | 多选组尺寸 | 'smaller' \| 'larger' | - | | dDisabled | 是否禁用 | boolean | false | | dVertical | 多选组垂直排布 | boolean | false | | dIndeterminateLabel | 部分选中的标签内容 | `(checked: boolean \| 'mixed') => React.ReactNode` | - | diff --git a/packages/ui/src/components/checkbox/demos/1.Basic.md b/packages/ui/src/components/checkbox/demos/1.Basic.md index 531f64d2..d35a5f8c 100644 --- a/packages/ui/src/components/checkbox/demos/1.Basic.md +++ b/packages/ui/src/components/checkbox/demos/1.Basic.md @@ -17,21 +17,14 @@ import { DCheckbox } from '@react-devui/ui'; export default function Demo() { return ( - <> -
- Checkbox - Checkbox - Checkbox -
-
-
- Checkbox - Checkbox Disabled - - Checkbox Disabled - -
- +
+ Checkbox + Checkbox + Checkbox Disabled + + Checkbox Disabled + +
); } ``` diff --git a/packages/ui/src/components/checkbox/demos/4.Size.md b/packages/ui/src/components/checkbox/demos/4.Size.md deleted file mode 100644 index a5193b73..00000000 --- a/packages/ui/src/components/checkbox/demos/4.Size.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: - en-US: Size - zh-Hant: 尺寸 ---- - -# en-US - -Adjust size by setting `dSize` to `larger` and `smaller`. - -# zh-Hant - -通过设置 `dSize` 为 `larger` `smaller` 调整尺寸。 - -```tsx -import { useState } from 'react'; - -import { DCheckbox, DCheckboxGroup } from '@react-devui/ui'; - -export default function Demo() { - const [value, setValue] = useState([2]); - - return ( - <> - - {[1, 2, 3].map((n) => ( - - Radio {n} - - ))} - -
- - {[1, 2, 3].map((n) => ( - - Radio {n} - - ))} - -
- - {[1, 2, 3].map((n) => ( - - Radio {n} - - ))} - - - ); -} -``` diff --git a/packages/ui/src/components/form/FormItem.tsx b/packages/ui/src/components/form/FormItem.tsx index 9330ba05..fa8a3c39 100644 --- a/packages/ui/src/components/form/FormItem.tsx +++ b/packages/ui/src/components/form/FormItem.tsx @@ -89,7 +89,7 @@ export function DFormItem(props: DFormItemProps) { return _props; })(); - const [formItems, setFormItems] = useImmer(new Map()); + const [formItems, setFormItems] = useImmer(new Map()); const getControl = useCallback( (formControlName: string) => { @@ -278,10 +278,22 @@ export function DFormItem(props: DFormItemProps) { } }, [dLabelExtra]); + const handleLabelClick = useCallback>((e) => { + const id = e.currentTarget.getAttribute('for'); + if (id) { + const el = document.getElementById(id); + if (el && el.tagName !== 'INPUT') { + e.preventDefault(); + el.focus({ preventScroll: true }); + el.click(); + } + } + }, []); + const stateBackflow = useMemo>( () => ({ updateFormItems: (identity, formControlName, id) => { - if (isString(formControlName) && isString(id)) { + if (isString(formControlName)) { setFormItems((draft) => { draft.set(identity, { formControlName, id }); }); @@ -321,7 +333,8 @@ export function DFormItem(props: DFormItemProps) { } )} style={{ - flex: span === true ? '1 0 auto' : `0 0 ${isNumber(span) ? `calc((100% / 12) * ${span})` : span}`, + flexGrow: span === true ? 1 : undefined, + width: span === true ? undefined : isNumber(span) ? `calc((100% / 12) * ${span})` : span, }} >
@@ -334,7 +347,7 @@ export function DFormItem(props: DFormItemProps) { })} style={{ width: formLayout === 'vertical' ? undefined : labelWidth }} > -