@dsh-plugin/dsh-ui-components 0.0.1 → 0.1.0-dev.43613492370
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/NOTICE +21 -21
- package/README.md +136 -136
- package/README.zh_CN.md +136 -136
- package/docs/tokens.md +67 -67
- package/lib/basic/form.js +49 -49
- package/lib/basic/styles.js +391 -391
- package/lib/composites/styles.js +427 -427
- package/lib/data/styles.js +257 -257
- package/lib/display/styles.js +310 -310
- package/lib/feedback/styles.js +228 -228
- package/lib/foundation/bridge.js +203 -203
- package/lib/navigation/styles.js +417 -417
- package/package.json +104 -104
- package/scripts/check-size.mjs +69 -69
- package/scripts/dump-npm-latest.mjs +59 -59
- package/scripts/gen-host-tokens.mjs +61 -61
- package/scripts/gen-icons.mjs +188 -178
- package/scripts/verify-no-literal-colors.mjs +63 -63
- package/scripts/verify-tokens.mjs +135 -135
- package/src/basic/button.tsx +76 -76
- package/src/basic/form.tsx +187 -187
- package/src/basic/icon-button.tsx +66 -66
- package/src/basic/index.ts +38 -38
- package/src/basic/input.tsx +140 -140
- package/src/basic/select.tsx +120 -120
- package/src/basic/styles.ts +412 -412
- package/src/basic/toggle.tsx +116 -116
- package/src/composites/data-view.tsx +189 -189
- package/src/composites/index.ts +11 -11
- package/src/composites/indicator.tsx +90 -90
- package/src/composites/menu-select.tsx +230 -230
- package/src/composites/panel.tsx +72 -72
- package/src/composites/settings.tsx +80 -80
- package/src/composites/styles.ts +450 -450
- package/src/data/collection.tsx +71 -71
- package/src/data/index.ts +39 -39
- package/src/data/info.tsx +89 -89
- package/src/data/picker.tsx +113 -113
- package/src/data/styles.ts +278 -278
- package/src/display/container.tsx +67 -67
- package/src/display/feedback.tsx +92 -92
- package/src/display/index.ts +44 -44
- package/src/display/label.tsx +165 -165
- package/src/display/overlay.tsx +103 -103
- package/src/display/styles.ts +332 -332
- package/src/feedback/global.tsx +110 -110
- package/src/feedback/index.ts +19 -19
- package/src/feedback/inline.tsx +50 -50
- package/src/feedback/panel.tsx +93 -93
- package/src/feedback/styles.ts +250 -250
- package/src/foundation/bridge.ts +318 -318
- package/src/foundation/icons.tsx +315 -315
- package/src/foundation/index.ts +33 -33
- package/src/foundation/palette.ts +153 -153
- package/src/foundation/provider.tsx +62 -62
- package/src/foundation/style.ts +63 -63
- package/src/foundation/tokens.ts +62 -62
- package/src/index.ts +18 -18
- package/src/navigation/hierarchy.tsx +82 -82
- package/src/navigation/index.ts +21 -21
- package/src/navigation/indicator.tsx +125 -125
- package/src/navigation/menu.tsx +94 -94
- package/src/navigation/segmented.tsx +136 -136
- package/src/navigation/styles.ts +438 -438
package/src/navigation/menu.tsx
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 菜单、下拉与标签页。
|
|
3
|
-
*
|
|
4
|
-
* @module @dsh-plugin/dsh-ui-components/navigation/menu
|
|
5
|
-
*/
|
|
6
|
-
import * as React from 'react'
|
|
7
|
-
import { Arco } from '@dsh-plugin/dsh-loader/client'
|
|
8
|
-
import { cx } from '../foundation/style.js'
|
|
9
|
-
import { injectNavigationStyles } from './styles.js'
|
|
10
|
-
|
|
11
|
-
/** {@link Menu} 的 props。 */
|
|
12
|
-
export type MenuProps = React.ComponentPropsWithoutRef<typeof Arco.Menu>
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 侧边或页面内的菜单。选中项走宿主的选择态填充。
|
|
16
|
-
*
|
|
17
|
-
* @param props - 见 {@link MenuProps}。
|
|
18
|
-
* @returns 菜单元素。
|
|
19
|
-
*/
|
|
20
|
-
export function Menu(props: MenuProps): React.ReactElement {
|
|
21
|
-
injectNavigationStyles()
|
|
22
|
-
return <Arco.Menu {...props} />
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** {@link MenuItem} 的 props。 */
|
|
26
|
-
export type MenuItemProps = React.ComponentPropsWithoutRef<typeof Arco.Menu.Item>
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 菜单中的一项。
|
|
30
|
-
*
|
|
31
|
-
* @param props - 见 {@link MenuItemProps}。
|
|
32
|
-
* @returns 菜单项元素。
|
|
33
|
-
*/
|
|
34
|
-
export function MenuItem(props: MenuItemProps): React.ReactElement {
|
|
35
|
-
injectNavigationStyles()
|
|
36
|
-
return <Arco.Menu.Item {...props} />
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** {@link Dropdown} 的 props。 */
|
|
40
|
-
export type DropdownProps = React.ComponentPropsWithoutRef<typeof Arco.Dropdown>
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 附着在触发元素上的下拉菜单。
|
|
44
|
-
*
|
|
45
|
-
* @param props - 见 {@link DropdownProps}。
|
|
46
|
-
* @returns 下拉元素。
|
|
47
|
-
*/
|
|
48
|
-
export function Dropdown(props: DropdownProps): React.ReactElement {
|
|
49
|
-
injectNavigationStyles()
|
|
50
|
-
return <Arco.Dropdown {...props} />
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** {@link Tabs} 的 props。 */
|
|
54
|
-
export type TabsProps = React.ComponentPropsWithoutRef<typeof Arco.Tabs> & {
|
|
55
|
-
/**
|
|
56
|
-
* 外观。`line` 是默认的下划线标签页;`segment` 渲染为胶囊分段控件,
|
|
57
|
-
* 适合在少数几个模式之间切换。
|
|
58
|
-
*/
|
|
59
|
-
appearance?: 'line' | 'segment'
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 标签页。
|
|
64
|
-
*
|
|
65
|
-
* `Tabs.TabPane` 与 Arco 本身一致,用作子页。
|
|
66
|
-
*
|
|
67
|
-
* @param props - 见 {@link TabsProps}。
|
|
68
|
-
* @returns 标签页元素。
|
|
69
|
-
*/
|
|
70
|
-
function TabsBase({ appearance = 'line', className, ...rest }: TabsProps): React.ReactElement {
|
|
71
|
-
injectNavigationStyles()
|
|
72
|
-
return (
|
|
73
|
-
<Arco.Tabs
|
|
74
|
-
{...rest}
|
|
75
|
-
className={cx(appearance === 'segment' && 'dsh-tabs--segment', Array.isArray(className) ? className.join(' ') : className)}
|
|
76
|
-
/>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** {@link TabPane} 的 props。 */
|
|
81
|
-
export type TabPaneProps = React.ComponentPropsWithoutRef<typeof Arco.Tabs.TabPane>
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 标签页中的一页。
|
|
85
|
-
*
|
|
86
|
-
* 直接取 Arco 的原组件而不是包一层:Arco 通过 `child.type` 识别子页,包一层
|
|
87
|
-
* 会让识别失败并静默渲染出空标签栏。
|
|
88
|
-
*/
|
|
89
|
-
export const TabPane: typeof Arco.Tabs.TabPane = Arco.Tabs.TabPane
|
|
90
|
-
|
|
91
|
-
/** 带静态子组件的标签页导出,`<Tabs><Tabs.TabPane /></Tabs>` 可直接使用。 */
|
|
92
|
-
export const Tabs: typeof TabsBase & { TabPane: typeof Arco.Tabs.TabPane } = Object.assign(TabsBase, {
|
|
93
|
-
TabPane: Arco.Tabs.TabPane,
|
|
94
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* 菜单、下拉与标签页。
|
|
3
|
+
*
|
|
4
|
+
* @module @dsh-plugin/dsh-ui-components/navigation/menu
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
import { Arco } from '@dsh-plugin/dsh-loader/client'
|
|
8
|
+
import { cx } from '../foundation/style.js'
|
|
9
|
+
import { injectNavigationStyles } from './styles.js'
|
|
10
|
+
|
|
11
|
+
/** {@link Menu} 的 props。 */
|
|
12
|
+
export type MenuProps = React.ComponentPropsWithoutRef<typeof Arco.Menu>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 侧边或页面内的菜单。选中项走宿主的选择态填充。
|
|
16
|
+
*
|
|
17
|
+
* @param props - 见 {@link MenuProps}。
|
|
18
|
+
* @returns 菜单元素。
|
|
19
|
+
*/
|
|
20
|
+
export function Menu(props: MenuProps): React.ReactElement {
|
|
21
|
+
injectNavigationStyles()
|
|
22
|
+
return <Arco.Menu {...props} />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** {@link MenuItem} 的 props。 */
|
|
26
|
+
export type MenuItemProps = React.ComponentPropsWithoutRef<typeof Arco.Menu.Item>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 菜单中的一项。
|
|
30
|
+
*
|
|
31
|
+
* @param props - 见 {@link MenuItemProps}。
|
|
32
|
+
* @returns 菜单项元素。
|
|
33
|
+
*/
|
|
34
|
+
export function MenuItem(props: MenuItemProps): React.ReactElement {
|
|
35
|
+
injectNavigationStyles()
|
|
36
|
+
return <Arco.Menu.Item {...props} />
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** {@link Dropdown} 的 props。 */
|
|
40
|
+
export type DropdownProps = React.ComponentPropsWithoutRef<typeof Arco.Dropdown>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 附着在触发元素上的下拉菜单。
|
|
44
|
+
*
|
|
45
|
+
* @param props - 见 {@link DropdownProps}。
|
|
46
|
+
* @returns 下拉元素。
|
|
47
|
+
*/
|
|
48
|
+
export function Dropdown(props: DropdownProps): React.ReactElement {
|
|
49
|
+
injectNavigationStyles()
|
|
50
|
+
return <Arco.Dropdown {...props} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** {@link Tabs} 的 props。 */
|
|
54
|
+
export type TabsProps = React.ComponentPropsWithoutRef<typeof Arco.Tabs> & {
|
|
55
|
+
/**
|
|
56
|
+
* 外观。`line` 是默认的下划线标签页;`segment` 渲染为胶囊分段控件,
|
|
57
|
+
* 适合在少数几个模式之间切换。
|
|
58
|
+
*/
|
|
59
|
+
appearance?: 'line' | 'segment'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 标签页。
|
|
64
|
+
*
|
|
65
|
+
* `Tabs.TabPane` 与 Arco 本身一致,用作子页。
|
|
66
|
+
*
|
|
67
|
+
* @param props - 见 {@link TabsProps}。
|
|
68
|
+
* @returns 标签页元素。
|
|
69
|
+
*/
|
|
70
|
+
function TabsBase({ appearance = 'line', className, ...rest }: TabsProps): React.ReactElement {
|
|
71
|
+
injectNavigationStyles()
|
|
72
|
+
return (
|
|
73
|
+
<Arco.Tabs
|
|
74
|
+
{...rest}
|
|
75
|
+
className={cx(appearance === 'segment' && 'dsh-tabs--segment', Array.isArray(className) ? className.join(' ') : className)}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** {@link TabPane} 的 props。 */
|
|
81
|
+
export type TabPaneProps = React.ComponentPropsWithoutRef<typeof Arco.Tabs.TabPane>
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 标签页中的一页。
|
|
85
|
+
*
|
|
86
|
+
* 直接取 Arco 的原组件而不是包一层:Arco 通过 `child.type` 识别子页,包一层
|
|
87
|
+
* 会让识别失败并静默渲染出空标签栏。
|
|
88
|
+
*/
|
|
89
|
+
export const TabPane: typeof Arco.Tabs.TabPane = Arco.Tabs.TabPane
|
|
90
|
+
|
|
91
|
+
/** 带静态子组件的标签页导出,`<Tabs><Tabs.TabPane /></Tabs>` 可直接使用。 */
|
|
92
|
+
export const Tabs: typeof TabsBase & { TabPane: typeof Arco.Tabs.TabPane } = Object.assign(TabsBase, {
|
|
93
|
+
TabPane: Arco.Tabs.TabPane,
|
|
94
|
+
})
|
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 分段控件。
|
|
3
|
-
*
|
|
4
|
-
* 与 `Pill` 的差别同宿主一致:`Pill` 是一组各自独立的开关,`SegmentedControl`
|
|
5
|
-
* 是少数互斥模式中的一个选择,带 tablist 键盘语义(方向键在段之间移动,只有
|
|
6
|
-
* 选中段在 Tab 序列里)。
|
|
7
|
-
*
|
|
8
|
-
* 指示条的位置由段数与选中下标通过 CSS 变量推导,不测量 DOM。
|
|
9
|
-
*
|
|
10
|
-
* @module @dsh-plugin/dsh-ui-components/navigation/segmented
|
|
11
|
-
*/
|
|
12
|
-
import * as React from 'react'
|
|
13
|
-
import { cx } from '../foundation/style.js'
|
|
14
|
-
import { injectNavigationStyles } from './styles.js'
|
|
15
|
-
|
|
16
|
-
/** 一个分段。 */
|
|
17
|
-
export interface Segment {
|
|
18
|
-
/** 该段的值。 */
|
|
19
|
-
value: string
|
|
20
|
-
/** 展示文本。 */
|
|
21
|
-
label: React.ReactNode
|
|
22
|
-
/** 不可选。 */
|
|
23
|
-
disabled?: boolean
|
|
24
|
-
/** 禁用原因的悬停提示。 */
|
|
25
|
-
disabledReason?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** {@link SegmentedControl} 的 props。 */
|
|
29
|
-
export interface SegmentedControlProps {
|
|
30
|
-
/** 分段列表,至少两个。 */
|
|
31
|
-
items: readonly Segment[]
|
|
32
|
-
/** 当前选中的值。 */
|
|
33
|
-
value: string
|
|
34
|
-
/** 选中项变化时回调。 */
|
|
35
|
-
onChange: (value: string) => void
|
|
36
|
-
/** 列表的可访问名称。 */
|
|
37
|
-
label: string
|
|
38
|
-
/** id 前缀,用于生成每段的 id 与它控制的面板 id。 */
|
|
39
|
-
id?: string
|
|
40
|
-
/** 锁定全部段,用于有操作在途时。 */
|
|
41
|
-
disabled?: boolean
|
|
42
|
-
/** 撑满父容器宽度,默认按内容收缩。 */
|
|
43
|
-
block?: boolean
|
|
44
|
-
/** 追加类名。 */
|
|
45
|
-
className?: string
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 一组互斥模式的分段切换。
|
|
50
|
-
*
|
|
51
|
-
* 每段的 id 是 `<id>-<value>`,它控制的面板 id 是 `<id>-<value>-panel`;面板
|
|
52
|
-
* 由调用方渲染并用 `aria-labelledby` 指回该段。
|
|
53
|
-
*
|
|
54
|
-
* @param props - 见 {@link SegmentedControlProps}。
|
|
55
|
-
* @returns 带 tablist 语义的分段控件。
|
|
56
|
-
*/
|
|
57
|
-
export function SegmentedControl({
|
|
58
|
-
items,
|
|
59
|
-
value,
|
|
60
|
-
onChange,
|
|
61
|
-
label,
|
|
62
|
-
id,
|
|
63
|
-
disabled,
|
|
64
|
-
block,
|
|
65
|
-
className,
|
|
66
|
-
}: SegmentedControlProps): React.ReactElement {
|
|
67
|
-
injectNavigationStyles()
|
|
68
|
-
|
|
69
|
-
const selectedIndex = Math.max(0, items.findIndex(item => item.value === value))
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 方向键、Home、End 在段之间移动。
|
|
73
|
-
*
|
|
74
|
-
* @param event - 键盘事件。
|
|
75
|
-
*/
|
|
76
|
-
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
77
|
-
if (disabled === true) return
|
|
78
|
-
const delta = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
|
|
79
|
-
const enabled = items.map((item, index) => (item.disabled === true ? -1 : index)).filter(index => index >= 0)
|
|
80
|
-
if (enabled.length === 0) return
|
|
81
|
-
|
|
82
|
-
let nextIndex: number | undefined
|
|
83
|
-
if (delta !== 0) {
|
|
84
|
-
const position = enabled.indexOf(selectedIndex)
|
|
85
|
-
const base = position >= 0 ? position : 0
|
|
86
|
-
nextIndex = enabled[(base + delta + enabled.length) % enabled.length]
|
|
87
|
-
} else if (event.key === 'Home') {
|
|
88
|
-
nextIndex = enabled[0]
|
|
89
|
-
} else if (event.key === 'End') {
|
|
90
|
-
nextIndex = enabled[enabled.length - 1]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (nextIndex === undefined) return
|
|
94
|
-
event.preventDefault()
|
|
95
|
-
const target = items[nextIndex]
|
|
96
|
-
if (target !== undefined) onChange(target.value)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div
|
|
101
|
-
role="tablist"
|
|
102
|
-
aria-label={label}
|
|
103
|
-
aria-disabled={disabled === true ? true : undefined}
|
|
104
|
-
className={cx('dsh-segmented', block === true && 'dsh-segmented--block', className)}
|
|
105
|
-
style={
|
|
106
|
-
{
|
|
107
|
-
'--dsh-segment-count': items.length,
|
|
108
|
-
'--dsh-segment-index': selectedIndex,
|
|
109
|
-
} as React.CSSProperties
|
|
110
|
-
}
|
|
111
|
-
onKeyDown={onKeyDown}
|
|
112
|
-
>
|
|
113
|
-
<span className="dsh-segmented__indicator" aria-hidden="true" />
|
|
114
|
-
{items.map(item => {
|
|
115
|
-
const selected = item.value === value
|
|
116
|
-
return (
|
|
117
|
-
<button
|
|
118
|
-
key={item.value}
|
|
119
|
-
type="button"
|
|
120
|
-
role="tab"
|
|
121
|
-
id={id === undefined ? undefined : `${id}-${item.value}`}
|
|
122
|
-
aria-selected={selected}
|
|
123
|
-
aria-controls={id === undefined ? undefined : `${id}-${item.value}-panel`}
|
|
124
|
-
tabIndex={selected ? 0 : -1}
|
|
125
|
-
disabled={disabled === true || item.disabled === true}
|
|
126
|
-
title={item.disabledReason}
|
|
127
|
-
className="dsh-segmented__tab"
|
|
128
|
-
onClick={() => onChange(item.value)}
|
|
129
|
-
>
|
|
130
|
-
{item.label}
|
|
131
|
-
</button>
|
|
132
|
-
)
|
|
133
|
-
})}
|
|
134
|
-
</div>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 分段控件。
|
|
3
|
+
*
|
|
4
|
+
* 与 `Pill` 的差别同宿主一致:`Pill` 是一组各自独立的开关,`SegmentedControl`
|
|
5
|
+
* 是少数互斥模式中的一个选择,带 tablist 键盘语义(方向键在段之间移动,只有
|
|
6
|
+
* 选中段在 Tab 序列里)。
|
|
7
|
+
*
|
|
8
|
+
* 指示条的位置由段数与选中下标通过 CSS 变量推导,不测量 DOM。
|
|
9
|
+
*
|
|
10
|
+
* @module @dsh-plugin/dsh-ui-components/navigation/segmented
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react'
|
|
13
|
+
import { cx } from '../foundation/style.js'
|
|
14
|
+
import { injectNavigationStyles } from './styles.js'
|
|
15
|
+
|
|
16
|
+
/** 一个分段。 */
|
|
17
|
+
export interface Segment {
|
|
18
|
+
/** 该段的值。 */
|
|
19
|
+
value: string
|
|
20
|
+
/** 展示文本。 */
|
|
21
|
+
label: React.ReactNode
|
|
22
|
+
/** 不可选。 */
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
/** 禁用原因的悬停提示。 */
|
|
25
|
+
disabledReason?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** {@link SegmentedControl} 的 props。 */
|
|
29
|
+
export interface SegmentedControlProps {
|
|
30
|
+
/** 分段列表,至少两个。 */
|
|
31
|
+
items: readonly Segment[]
|
|
32
|
+
/** 当前选中的值。 */
|
|
33
|
+
value: string
|
|
34
|
+
/** 选中项变化时回调。 */
|
|
35
|
+
onChange: (value: string) => void
|
|
36
|
+
/** 列表的可访问名称。 */
|
|
37
|
+
label: string
|
|
38
|
+
/** id 前缀,用于生成每段的 id 与它控制的面板 id。 */
|
|
39
|
+
id?: string
|
|
40
|
+
/** 锁定全部段,用于有操作在途时。 */
|
|
41
|
+
disabled?: boolean
|
|
42
|
+
/** 撑满父容器宽度,默认按内容收缩。 */
|
|
43
|
+
block?: boolean
|
|
44
|
+
/** 追加类名。 */
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 一组互斥模式的分段切换。
|
|
50
|
+
*
|
|
51
|
+
* 每段的 id 是 `<id>-<value>`,它控制的面板 id 是 `<id>-<value>-panel`;面板
|
|
52
|
+
* 由调用方渲染并用 `aria-labelledby` 指回该段。
|
|
53
|
+
*
|
|
54
|
+
* @param props - 见 {@link SegmentedControlProps}。
|
|
55
|
+
* @returns 带 tablist 语义的分段控件。
|
|
56
|
+
*/
|
|
57
|
+
export function SegmentedControl({
|
|
58
|
+
items,
|
|
59
|
+
value,
|
|
60
|
+
onChange,
|
|
61
|
+
label,
|
|
62
|
+
id,
|
|
63
|
+
disabled,
|
|
64
|
+
block,
|
|
65
|
+
className,
|
|
66
|
+
}: SegmentedControlProps): React.ReactElement {
|
|
67
|
+
injectNavigationStyles()
|
|
68
|
+
|
|
69
|
+
const selectedIndex = Math.max(0, items.findIndex(item => item.value === value))
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 方向键、Home、End 在段之间移动。
|
|
73
|
+
*
|
|
74
|
+
* @param event - 键盘事件。
|
|
75
|
+
*/
|
|
76
|
+
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
77
|
+
if (disabled === true) return
|
|
78
|
+
const delta = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
|
|
79
|
+
const enabled = items.map((item, index) => (item.disabled === true ? -1 : index)).filter(index => index >= 0)
|
|
80
|
+
if (enabled.length === 0) return
|
|
81
|
+
|
|
82
|
+
let nextIndex: number | undefined
|
|
83
|
+
if (delta !== 0) {
|
|
84
|
+
const position = enabled.indexOf(selectedIndex)
|
|
85
|
+
const base = position >= 0 ? position : 0
|
|
86
|
+
nextIndex = enabled[(base + delta + enabled.length) % enabled.length]
|
|
87
|
+
} else if (event.key === 'Home') {
|
|
88
|
+
nextIndex = enabled[0]
|
|
89
|
+
} else if (event.key === 'End') {
|
|
90
|
+
nextIndex = enabled[enabled.length - 1]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (nextIndex === undefined) return
|
|
94
|
+
event.preventDefault()
|
|
95
|
+
const target = items[nextIndex]
|
|
96
|
+
if (target !== undefined) onChange(target.value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
role="tablist"
|
|
102
|
+
aria-label={label}
|
|
103
|
+
aria-disabled={disabled === true ? true : undefined}
|
|
104
|
+
className={cx('dsh-segmented', block === true && 'dsh-segmented--block', className)}
|
|
105
|
+
style={
|
|
106
|
+
{
|
|
107
|
+
'--dsh-segment-count': items.length,
|
|
108
|
+
'--dsh-segment-index': selectedIndex,
|
|
109
|
+
} as React.CSSProperties
|
|
110
|
+
}
|
|
111
|
+
onKeyDown={onKeyDown}
|
|
112
|
+
>
|
|
113
|
+
<span className="dsh-segmented__indicator" aria-hidden="true" />
|
|
114
|
+
{items.map(item => {
|
|
115
|
+
const selected = item.value === value
|
|
116
|
+
return (
|
|
117
|
+
<button
|
|
118
|
+
key={item.value}
|
|
119
|
+
type="button"
|
|
120
|
+
role="tab"
|
|
121
|
+
id={id === undefined ? undefined : `${id}-${item.value}`}
|
|
122
|
+
aria-selected={selected}
|
|
123
|
+
aria-controls={id === undefined ? undefined : `${id}-${item.value}-panel`}
|
|
124
|
+
tabIndex={selected ? 0 : -1}
|
|
125
|
+
disabled={disabled === true || item.disabled === true}
|
|
126
|
+
title={item.disabledReason}
|
|
127
|
+
className="dsh-segmented__tab"
|
|
128
|
+
onClick={() => onChange(item.value)}
|
|
129
|
+
>
|
|
130
|
+
{item.label}
|
|
131
|
+
</button>
|
|
132
|
+
)
|
|
133
|
+
})}
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|