走鹃 преди 3 години
родител
ревизия
a17828cf4c

+ 2 - 0
content/input/checkbox/index-en-US.md

@@ -399,11 +399,13 @@ import { CheckboxGroup, Checkbox, Row, Col } from '@douyinfe/semi-ui';
 
 | PROPERTIES     | Instructions                                                 | type               | Default |
 | -------------- | ------------------------------------------------------------ | ------------------ | ------- |
+| addonId | id of addon node, aria-labelledby refers to this id, if not set, it will generate an id randomly  **provided after v2.11.0**                                 | string            |       |
 | aria-label     | Define label of the Checkbox  | string | - |
 | checked        | Specify whether the current Checkbox is selected (it is invalid when used in Group)                     | boolean            | false   |
 | defaultChecked | Whether Checked by default (it is invalid when used in Group)                                           | boolean            | false   |
 | disabled       | Disabled state                                               | boolean            | false   |
 | extra          | Provide extra information <br/>**>= v0.25.0**                | ReactNode          | -       |
+| extraId        | id of extra node. aria-describedby refers to this id, if not set, it will randomly generate an id <br/>**provided after v2.11.0**                     | ReactNode         | -      |
 | value          | The value that the checkbox represents in the CheckboxGroup  | any | - |
 | indeterminate  | Set to indeterminate state, style control only               | boolean            | false   |
 | onChange       | Callback function when change                                | function(e: Event) | -       |

+ 2 - 0
content/input/checkbox/index.md

@@ -381,11 +381,13 @@ import { Checkbox, CheckboxGroup, Row, Col } from '@douyinfe/semi-ui';
 
 | 参数 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
+| addonId | addon 节点 id,aria-labelledby 指向这个 id,若无设置会随机生成一个 id  **v2.11.0 后提供**                                 | string            |       |
 | aria-label | 定义 Checkbox 的作用 | string | - |
 | checked | 指定当前Checkbox是否选中(在Group中使用时无效) | boolean | false |
 | defaultChecked | 初始是否选中(在Group中使用时无效) | boolean | false |
 | disabled | 失效状态 | boolean | false |
 | extra | 副文本<br/>__v0.25.0后提供__ | ReactNode | - |
+| extraId        | 副文本的 id,aria-describedby 指向这个 id,若无设置会随机生成一个 id <br/>**v2.11.0 后提供**                     | ReactNode         | -      |
 | value | 该checkbox在CheckboxGroup中代表的value | any | - |
 | indeterminate | 设置 indeterminate 状态,只负责样式控制 | boolean | false |
 | onChange | 变化时回调函数 | function(e:Event) | - |

+ 2 - 0
content/input/radio/index-en-US.md

@@ -358,6 +358,7 @@ class App extends React.Component {
 | PROPERTIES | Instructions | Type | Default |
 | --- | --- | --- | --- |
 | addonClassName | classname of content wrapper<br/>**provided after v1.16.0** | string |  |
+| addonId | id of addon node, aria-labelledby refers to this id, if not set, it will generate an id randomly  **provided after v2.11.0**                                 | string            |       |
 | addonStyle | inline style of content wrapper<br/>**provided after v1.16.0** | object |  |
 | aria-label      | Label of Radio                                                            | string           | -  |
 | autoFocus | Automatically focus the form control when the page is loaded | boolean | false |
@@ -366,6 +367,7 @@ class App extends React.Component {
 | defaultChecked | Checked by default | boolean | false |
 | disabled | Disable the radio | boolean | false |
 | extra | Extra information displayed <br/>**provided after v0.25.0** | ReactNode | - |
+| extraId        | id of extra node. aria-describedby refers to this id, if not set, it will randomly generate an id <br/>**provided after v2.11.0**                     | ReactNode         | -      |
 | mode | In advanced mode, options can be clicked to uncheck, one of `advanced` | string | - |
 | style | Inline style | CSSProperties |  |
 | value | Compared based on value to determine whether the option is selected | string \| number | - |

+ 2 - 0
content/input/radio/index.md

@@ -318,6 +318,7 @@ class App extends React.Component {
 | 属性           | 说明                                                                   | 类型              | 默认值  |
 |----------------|-----------------------------------------------------------------------|------------------|--------|
 | addonClassName | 包裹内容容器的样式类名  **v1.16.0 后提供**                                 | string            |       |
+| addonId | addon 节点 id,aria-labelledby 指向这个 id,若无设置会随机生成一个 id  **v2.11.0 后提供**                                 | string            |       |
 | addonStyle     | 包裹内容容器的内联样式  **v1.16.0 后提供**                                 | CSSProperties     |       |
 | aria-label      | Radio 的 label                                                            | string           | -  |
 | autoFocus      | 自动获取焦点                                                            | boolean           | false  |
@@ -326,6 +327,7 @@ class App extends React.Component {
 | defaultChecked | 初始是否选中                                                             | boolean           | false  |
 | disabled       | 禁选单选框                                                              |boolean            | false    |
 | extra          | 副文本,只对type='default'生效<br/>**v0.25.0 后提供**                     | ReactNode         | -      |
+| extraId        | 副文本的 id,aria-describedby 指向这个 id,若无设置会随机生成一个 id <br/>**v2.11.0 后提供**                     | ReactNode         | -      |
 | mode           | 高级和普通模式,高级模式可以在 checked 时点击变成 unchecked,可选值 advanced   | string            | -      |
 | style          | 内联样式                                                                 | CSSProperties    |        |
 | value          | 根据 value 进行比较,判断是否选中                                          | string \| number               | -      |

+ 1 - 0
content/show/tooltip/index-en-US.md

@@ -421,6 +421,7 @@ import { Popconfirm, Tooltip, Button } from '@douyinfe/semi-ui';
 | trigger | Timing of triggering display, optional value: `hover`/`focus`/`click`/`custom` | string | 'hover' |  |
 | visible | Whether to show the pop-up layer | boolean |  |  |
 | wrapperClassName | When children are disabled or children are multiple elements, the outer layer will wrap a layer of span elements, and the api is used to set the style class name of this span | string |  | 1.32.0 |
+| wrapperId | The id of the wrapper node of the popup layer. The aria attribute of the trigger points to this id. | string |  | 2.11.0  |
 | zIndex              | Bullet levels.                                                                                                                                                                                                                               | number                      | 1060                |            |
 | onVisibleChange     | A callback triggered when the pop-up layer is displayed/hidden                                                                                                                                                                               | (isVisible: boolean) => void |                     |            |
 | onClickOutSide      | Callback when the pop-up layer is in the display state and the non-Children, non-floating layer inner area is clicked (only valid when trigger is custom, click)                                                                             | (e:event) => void           |                     | **2.1.0** |

+ 1 - 0
content/show/tooltip/index.md

@@ -454,6 +454,7 @@ function Demo() {
 | trigger | 触发展示的时机,可选值:`hover` / `focus` / `click` / `custom` | string | 'hover' |  |
 | visible | 是否展示弹出层 | boolean |  |  |
 | wrapperClassName | 当 children 为 disabled ,或者 children 为多个元素时,外层将会包裹一层 span 元素,该 api 用于设置此 span 的样式类名 | string |  | **1.32.0** |
+| wrapperId | 弹出层 wrapper 节点的 id,trigger 的 aria 属性指向此 id,若不设置组件会随机生成一个 id | string |  | 2.11.0  |
 | zIndex | 弹层层级 | number | 1060 |  |
 | onVisibleChange | 弹出层展示/隐藏时触发的回调 | function(isVisible:boolean) |  |  |
 | onClickOutSide | 当弹出层处于展示状态,点击非Children、非浮层内部区域时的回调(仅trigger为custom、click时有效)| function(e:event) |  | **2.1.0** |

+ 11 - 2
packages/semi-foundation/checkbox/checkboxFoundation.ts

@@ -21,6 +21,8 @@ export interface CheckboxAdapter<P = Record<string, any>, S = Record<string, any
     setNativeControlChecked: (checked: boolean) => void;
     getState: noopFunction;
     notifyChange: (event: BasicCheckboxEvent) => void;
+    setAddonId: () => void;
+    setExtraId: () => void;
 }
 
 class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CheckboxAdapter<P, S>, P, S> {
@@ -29,8 +31,15 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         super({ ...adapter });
     }
 
-    // eslint-disable-next-line @typescript-eslint/no-empty-function
-    init() {}
+    init() {
+        const { children, extra, extraId, addonId } = this.getProps();
+        if (children && !addonId) {
+            this._adapter.setAddonId();
+        }
+        if (extra && !extraId) {
+            this._adapter.setExtraId();
+        }
+    }
 
     getEvent(checked: boolean, e: any) {
         const props = this.getProps();

+ 11 - 0
packages/semi-foundation/radio/radioFoundation.ts

@@ -2,8 +2,19 @@ import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 
 export interface RadioAdapter extends DefaultAdapter {
     setHover: (hover: boolean) => void;
+    setAddonId: () => void;
+    setExtraId: () => void;
 }
 export default class RadioFoundation extends BaseFoundation<RadioAdapter> {
+    init() {
+        const { children, extra, extraId, addonId } = this._adapter.getProps();
+        if (children && !addonId) {
+            this._adapter.setAddonId();
+        }
+        if (extra && !extraId) {
+            this._adapter.setExtraId();
+        }
+    }
     setHover(hover: boolean) {
         this._adapter.setHover(hover);
     }

+ 5 - 0
packages/semi-foundation/tooltip/foundation.ts

@@ -68,6 +68,7 @@ export interface TooltipAdapter<P = Record<string, any>, S = Record<string, any>
     setInitialFocus(): void;
     notifyEscKeydown(event: any): void;
     getTriggerNode(): any;
+    setId(): void;
 }
 
 export type Position = ArrayElement<typeof strings.POSITION_SET>;
@@ -87,10 +88,14 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     }
 
     init() {
+        const { wrapperId } = this.getProps();
         this._mounted = true;
         this._bindEvent();
         this._shouldShow();
         this._initContainerPosition();
+        if (!wrapperId) {
+            this._adapter.setId();
+        }
     }
 
     destroy() {

+ 18 - 8
packages/semi-ui/checkbox/checkbox.tsx

@@ -29,9 +29,13 @@ export interface CheckboxProps extends BaseCheckboxProps {
     'aria-label'?: React.AriaAttributes['aria-label'];
     role?: React.HTMLAttributes<HTMLSpanElement>['role']; // a11y: wrapper role
     tabIndex?: number; // a11y: wrapper tabIndex
+    addonId?: string;
+    extraId?: string;
 }
 interface CheckboxState {
     checked: boolean;
+    addonId?: string;
+    extraId?: string;
 }
 class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
     static contextType = Context;
@@ -89,7 +93,13 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             notifyGroupChange: cbContent => {
                 this.context.checkboxGroup.onChange(cbContent);
             },
-            getGroupDisabled: () => (this.context && this.context.checkboxGroup.disabled)
+            getGroupDisabled: () => (this.context && this.context.checkboxGroup.disabled),
+            setAddonId: () => {
+                this.setState({ addonId: getUuidShort({ prefix: 'addon' }) });
+            },
+            setExtraId: () => {
+                this.setState({ extraId: getUuidShort({ prefix: 'extra' }) });
+            }
         };
     }
 
@@ -103,11 +113,11 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
         this.state = {
             checked: props.checked || props.defaultChecked || checked,
+            addonId: props.addonId,
+            extraId: props.extraId,
         };
 
         this.checkboxEntity = null;
-        this.addonId = getUuidShort({ prefix: 'addon' });
-        this.extraId = getUuidShort({ prefix: 'extra' });
         this.foundation = new CheckboxFoundation(this.adapter);
     }
 
@@ -153,7 +163,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             tabIndex,
             id
         } = this.props;
-        const { checked } = this.state;
+        const { checked, addonId, extraId } = this.state;
         const props: Record<string, any> = {
             checked,
             disabled,
@@ -196,8 +206,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
         const renderContent = () => (
             <>
-                {children ? <span id={this.addonId} className={`${prefix}-addon`}>{children}</span> : null}
-                {extra ? <div id={this.extraId} className={extraCls}>{extra}</div> : null}
+                {children ? <span id={addonId} className={`${prefix}-addon`}>{children}</span> : null}
+                {extra ? <div id={extraId} className={extraCls}>{extra}</div> : null}
             </>
         );
         return (
@@ -218,8 +228,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
                 <CheckboxInner
                     {...this.props}
                     {...props}
-                    addonId={children && this.addonId}
-                    extraId={extra && this.extraId}
+                    addonId={children && addonId}
+                    extraId={extra && extraId}
                     name={name}
                     isPureCardType={props.isPureCardType}
                     ref={ref => {

+ 17 - 7
packages/semi-ui/radio/radio.tsx

@@ -41,10 +41,14 @@ export type RadioProps = {
     addonClassName?: string;
     type?: RadioType;
     'aria-label'?: React.AriaAttributes['aria-label'];
+    addonId?: string;
+    extraId?: string;
 };
 
 export interface RadioState {
     hover?: boolean;
+    addonId?: string;
+    extraId?: string;
 }
 
 export { RadioChangeEvent };
@@ -94,11 +98,11 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
         super(props);
         this.state = {
             hover: false,
+            addonId: props.addonId,
+            extraId: props.extraId,
         };
         this.foundation = new RadioFoundation(this.adapter);
         this.radioEntity = null;
-        this.addonId = getUuidShort({ prefix: 'addon' });
-        this.extraId = getUuidShort({ prefix: 'extra' });
     }
 
     get adapter(): RadioAdapter {
@@ -106,6 +110,12 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
             ...super.adapter,
             setHover: (hover: boolean) => {
                 this.setState({ hover });
+            },
+            setAddonId: () => {
+                this.setState({ addonId: getUuidShort({ prefix: 'addon' }) });
+            },
+            setExtraId: () => {
+                this.setState({ extraId: getUuidShort({ prefix: 'extra' }) });
             }
         };
     }
@@ -168,7 +178,7 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
             isButtonRadioComponent,
             buttonSize,
             realPrefixCls;
-        const isHover = this.state.hover;
+        const { hover: isHover, addonId, extraId } = this.state;
         let props = {};
 
         if (this.isInGroup()) {
@@ -218,8 +228,8 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
         }, addonClassName);
         const renderContent = () => (
             <>
-                {children ? <span className={addonCls} style={addonStyle} id={this.addonId}>{children}</span> : null}
-                {extra && !isButtonRadio ? <div className={`${prefix}-extra`} id={this.extraId}>{extra}</div> : null}
+                {children ? <span className={addonCls} style={addonStyle} id={addonId}>{children}</span> : null}
+                {extra && !isButtonRadio ? <div className={`${prefix}-extra`} id={extraId}>{extra}</div> : null}
             </>
         );
         return (
@@ -240,8 +250,8 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
                     ref={(ref: RadioInner) => {
                         this.radioEntity = ref;
                     }}
-                    addonId={children && this.addonId}
-                    extraId={extra && this.extraId}
+                    addonId={children && addonId}
+                    extraId={extra && extraId}
                 />
                 {
                     isCardRadioGroup ?

+ 5 - 1
packages/semi-ui/tooltip/index.tsx

@@ -75,6 +75,7 @@ export interface TooltipProps extends BaseProps {
     guardFocus?: boolean;
     returnFocusOnClose?: boolean;
     onEscKeyDown?: (e: React.KeyboardEvent) => void;
+    wrapperId?: string;
 }
 interface TooltipState {
     visible: boolean;
@@ -193,7 +194,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             placement: props.position || 'top',
             transitionStyle: {},
             isPositionUpdated: false,
-            id: getUuidShort(), // auto generate id, will be used by children.aria-describedby & content.id, improve a11y
+            id: props.wrapperId, // auto generate id, will be used by children.aria-describedby & content.id, improve a11y
         };
         this.foundation = new TooltipFoundation(this.adapter);
         this.eventManager = new Event();
@@ -428,6 +429,9 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             },
             notifyEscKeydown: (event: React.KeyboardEvent) => {
                 this.props.onEscKeyDown(event);
+            },
+            setId: () => {
+                this.setState({ id: getUuidShort() });
             }
         };
     }