@yunlefun/vue 0.2.0 → 0.3.0
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/components/YlfSegmentedControl.vue +180 -0
- package/components/YlfSelect.vue +39 -11
- package/components/YlfTooltip.vue +3 -1
- package/components/segmented-control/demo.vue +21 -0
- package/components/segmented-control/index.md +41 -0
- package/components/segmented-control.ts +8 -0
- package/components/select/index.md +4 -0
- package/components/tooltip/index.md +2 -0
- package/dist/index.d.cts +10 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends string = string">
|
|
2
|
+
import type { YlfSegmentedOption } from './segmented-control'
|
|
3
|
+
import { ToggleGroupItem, ToggleGroupRoot } from 'reka-ui'
|
|
4
|
+
import YlfTooltip from './YlfTooltip.vue'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
options: readonly YlfSegmentedOption<T>[]
|
|
8
|
+
/** Accessible name of the group, including for icon-only controls. */
|
|
9
|
+
label: string
|
|
10
|
+
size?: 'sm' | 'md'
|
|
11
|
+
iconOnly?: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}>(), {
|
|
14
|
+
size: 'md',
|
|
15
|
+
iconOnly: false,
|
|
16
|
+
disabled: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
defineSlots<{
|
|
20
|
+
icon?: (props: { option: YlfSegmentedOption<T>, selected: boolean }) => unknown
|
|
21
|
+
}>()
|
|
22
|
+
const model = defineModel<T>({ required: true })
|
|
23
|
+
function updateValue(value: unknown): void {
|
|
24
|
+
const option = props.options.find(option => option.value === value)
|
|
25
|
+
// A view or filter always has one active choice, even when clicked again.
|
|
26
|
+
if (!props.disabled && option && !option.disabled)
|
|
27
|
+
model.value = option.value
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<ToggleGroupRoot
|
|
33
|
+
class="ylf-segmented"
|
|
34
|
+
:data-size="size"
|
|
35
|
+
:data-icon-only="iconOnly || undefined"
|
|
36
|
+
type="single"
|
|
37
|
+
orientation="horizontal"
|
|
38
|
+
:aria-label="label"
|
|
39
|
+
:disabled="disabled"
|
|
40
|
+
:model-value="model"
|
|
41
|
+
@update:model-value="updateValue"
|
|
42
|
+
>
|
|
43
|
+
<YlfTooltip v-for="option in options" :key="option.value" :content="option.label" :disabled="!iconOnly || disabled || option.disabled">
|
|
44
|
+
<ToggleGroupItem
|
|
45
|
+
class="ylf-segmented__item"
|
|
46
|
+
:value="option.value"
|
|
47
|
+
:disabled="option.disabled"
|
|
48
|
+
:aria-label="option.ariaLabel ?? option.label"
|
|
49
|
+
>
|
|
50
|
+
<span v-if="$slots.icon" class="ylf-segmented__icon" aria-hidden="true">
|
|
51
|
+
<slot name="icon" :option="option" :selected="model === option.value" />
|
|
52
|
+
</span>
|
|
53
|
+
<span v-if="!iconOnly || !$slots.icon" class="ylf-segmented__label">{{ option.label }}</span>
|
|
54
|
+
<span v-if="option.count !== undefined && !iconOnly" class="ylf-segmented__count">{{ option.count }}</span>
|
|
55
|
+
</ToggleGroupItem>
|
|
56
|
+
</YlfTooltip>
|
|
57
|
+
</ToggleGroupRoot>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.ylf-segmented {
|
|
62
|
+
--ylf-segmented-item-size: 36px;
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
flex: none;
|
|
66
|
+
box-sizing: border-box;
|
|
67
|
+
max-width: 100%;
|
|
68
|
+
gap: 2px;
|
|
69
|
+
padding: 3px;
|
|
70
|
+
border: 1px solid var(--ylf-c-border, #e2e8f0);
|
|
71
|
+
border-radius: var(--ylf-radius-sm, 10px);
|
|
72
|
+
background: var(--ylf-c-bg-soft, #f1f5f9);
|
|
73
|
+
font-family: var(--ylf-font-body, inherit);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ylf-segmented[data-size='sm'] {
|
|
77
|
+
--ylf-segmented-item-size: 26px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ylf-segmented__item {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
flex: 0 0 auto;
|
|
85
|
+
box-sizing: border-box;
|
|
86
|
+
min-width: var(--ylf-segmented-item-size);
|
|
87
|
+
height: var(--ylf-segmented-item-size);
|
|
88
|
+
gap: 6px;
|
|
89
|
+
padding: 0 10px;
|
|
90
|
+
border: 1px solid transparent;
|
|
91
|
+
border-radius: calc(var(--ylf-radius-sm, 10px) - 4px);
|
|
92
|
+
background: transparent;
|
|
93
|
+
color: var(--ylf-c-text-2, #475569);
|
|
94
|
+
font: inherit;
|
|
95
|
+
font-size: var(--ylf-text-sm, 14px);
|
|
96
|
+
font-weight: 500;
|
|
97
|
+
line-height: 1;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition:
|
|
101
|
+
background var(--ylf-duration-fast, 160ms),
|
|
102
|
+
color var(--ylf-duration-fast, 160ms),
|
|
103
|
+
box-shadow var(--ylf-duration-fast, 160ms);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ylf-segmented[data-size='sm'] .ylf-segmented__item {
|
|
107
|
+
padding-inline: 8px;
|
|
108
|
+
font-size: 12px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ylf-segmented[data-icon-only] .ylf-segmented__item {
|
|
112
|
+
width: calc(var(--ylf-segmented-item-size) + 4px);
|
|
113
|
+
padding: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ylf-segmented__item:hover:not([data-disabled], [aria-pressed='true']) {
|
|
117
|
+
color: var(--ylf-c-text, #0f172a);
|
|
118
|
+
background: color-mix(in srgb, var(--ylf-c-surface, #fff) 60%, transparent);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.ylf-segmented__item[aria-pressed='true'] {
|
|
122
|
+
color: var(--ylf-c-brand, #2563eb);
|
|
123
|
+
background: var(--ylf-c-surface, #fff);
|
|
124
|
+
border-color: var(--ylf-c-border, #e2e8f0);
|
|
125
|
+
box-shadow: var(--ylf-shadow-sm);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ylf-segmented__item:focus-visible {
|
|
129
|
+
position: relative;
|
|
130
|
+
z-index: 1;
|
|
131
|
+
outline: 2px solid var(--ylf-c-brand, #2563eb);
|
|
132
|
+
outline-offset: 2px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ylf-segmented__item[data-disabled] {
|
|
136
|
+
cursor: not-allowed;
|
|
137
|
+
opacity: 0.45;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ylf-segmented__icon {
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
flex: none;
|
|
145
|
+
width: 16px;
|
|
146
|
+
height: 16px;
|
|
147
|
+
font-size: 16px;
|
|
148
|
+
line-height: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ylf-segmented__icon > :where(svg, i, span) {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 100%;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ylf-segmented__count {
|
|
157
|
+
min-width: 16px;
|
|
158
|
+
color: var(--ylf-c-text-3, #64748b);
|
|
159
|
+
font-size: 10px;
|
|
160
|
+
font-variant-numeric: tabular-nums;
|
|
161
|
+
text-align: center;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ylf-segmented__item[aria-pressed='true'] .ylf-segmented__count {
|
|
165
|
+
color: inherit;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (pointer: coarse) {
|
|
169
|
+
.ylf-segmented .ylf-segmented__item {
|
|
170
|
+
min-width: 44px;
|
|
171
|
+
min-height: 44px;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (prefers-reduced-motion: reduce) {
|
|
176
|
+
.ylf-segmented__item {
|
|
177
|
+
transition: none;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
</style>
|
package/components/YlfSelect.vue
CHANGED
|
@@ -6,10 +6,13 @@ defineOptions({ inheritAttrs: false })
|
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(defineProps<{
|
|
8
8
|
/** color 可使用 CSS 色值或公共 token,例如 var(--ylf-accent-blue) */
|
|
9
|
-
options: { label: string, value: T, disabled?: boolean, color?: string }[]
|
|
9
|
+
options: readonly { label: string, value: T, disabled?: boolean, color?: string }[]
|
|
10
|
+
/** Compact workbench or comfortable form density. */
|
|
11
|
+
size?: 'sm' | 'md'
|
|
10
12
|
placeholder?: string
|
|
11
13
|
disabled?: boolean
|
|
12
14
|
}>(), {
|
|
15
|
+
size: 'md',
|
|
13
16
|
placeholder: '请选择',
|
|
14
17
|
disabled: false,
|
|
15
18
|
})
|
|
@@ -20,19 +23,21 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
20
23
|
|
|
21
24
|
<template>
|
|
22
25
|
<SelectRoot v-model="model" :disabled="disabled">
|
|
23
|
-
<SelectTrigger class="ylf-select__trigger" :aria-label="$attrs.id || $attrs['aria-labelledby'] ? undefined : placeholder" v-bind="$attrs">
|
|
26
|
+
<SelectTrigger class="ylf-select__trigger" :data-size="size" :aria-label="$attrs.id || $attrs['aria-labelledby'] ? undefined : placeholder" v-bind="$attrs">
|
|
24
27
|
<SelectValue :placeholder="placeholder" class="ylf-select__label">
|
|
25
28
|
<span v-if="selectedOption?.color" class="ylf-select__swatch" :style="{ backgroundColor: selectedOption.color }" aria-hidden="true" />
|
|
26
29
|
<span>{{ selectedOption?.label ?? placeholder }}</span>
|
|
27
30
|
</SelectValue>
|
|
28
31
|
<SelectIcon class="ylf-select__icon">
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
+
<slot name="icon">
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
34
|
+
<path d="M6 9l6 6 6-6" />
|
|
35
|
+
</svg>
|
|
36
|
+
</slot>
|
|
32
37
|
</SelectIcon>
|
|
33
38
|
</SelectTrigger>
|
|
34
39
|
<SelectPortal>
|
|
35
|
-
<SelectContent class="ylf-select__content" position="popper" :side-offset="6">
|
|
40
|
+
<SelectContent class="ylf-select__content" :data-size="size" position="popper" :side-offset="6">
|
|
36
41
|
<SelectViewport class="ylf-select__viewport">
|
|
37
42
|
<SelectItem
|
|
38
43
|
v-for="o in options"
|
|
@@ -46,9 +51,11 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
46
51
|
<span>{{ o.label }}</span>
|
|
47
52
|
</SelectItemText>
|
|
48
53
|
<SelectItemIndicator class="ylf-select__check">
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
54
|
+
<slot name="indicator">
|
|
55
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
+
<path d="M5 12.5l4.5 4.5L19 7" />
|
|
57
|
+
</svg>
|
|
58
|
+
</slot>
|
|
52
59
|
</SelectItemIndicator>
|
|
53
60
|
</SelectItem>
|
|
54
61
|
</SelectViewport>
|
|
@@ -119,7 +126,7 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
119
126
|
display: inline-flex;
|
|
120
127
|
color: var(--ylf-c-text-3, #64748b);
|
|
121
128
|
|
|
122
|
-
svg {
|
|
129
|
+
:where(svg, i, span) {
|
|
123
130
|
width: 18px;
|
|
124
131
|
height: 18px;
|
|
125
132
|
}
|
|
@@ -174,12 +181,33 @@ const selectedOption = computed(() => props.options.find(option => option.value
|
|
|
174
181
|
display: inline-flex;
|
|
175
182
|
color: var(--ylf-c-brand, #2563eb);
|
|
176
183
|
|
|
177
|
-
svg {
|
|
184
|
+
:where(svg, i, span) {
|
|
178
185
|
width: 15px;
|
|
179
186
|
height: 15px;
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
.ylf-select__trigger[data-size='sm'] {
|
|
191
|
+
min-width: 0;
|
|
192
|
+
min-height: 34px;
|
|
193
|
+
padding: 5px 10px;
|
|
194
|
+
font-size: 12px;
|
|
195
|
+
border-radius: var(--ylf-radius-sm, 10px);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.ylf-select__content[data-size='sm'] .ylf-select__item {
|
|
199
|
+
min-height: 30px;
|
|
200
|
+
padding: 5px 8px;
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@media (pointer: coarse) {
|
|
205
|
+
.ylf-select__trigger[data-size='sm'],
|
|
206
|
+
.ylf-select__content[data-size='sm'] .ylf-select__item {
|
|
207
|
+
min-height: 44px;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
183
211
|
@keyframes ylf-pop-in {
|
|
184
212
|
from {
|
|
185
213
|
opacity: 0;
|
|
@@ -6,15 +6,17 @@ withDefaults(defineProps<{
|
|
|
6
6
|
content?: string
|
|
7
7
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
8
8
|
delay?: number
|
|
9
|
+
disabled?: boolean
|
|
9
10
|
}>(), {
|
|
10
11
|
side: 'top',
|
|
11
12
|
delay: 300,
|
|
13
|
+
disabled: false,
|
|
12
14
|
})
|
|
13
15
|
</script>
|
|
14
16
|
|
|
15
17
|
<template>
|
|
16
18
|
<TooltipProvider :delay-duration="delay">
|
|
17
|
-
<TooltipRoot>
|
|
19
|
+
<TooltipRoot :disabled="disabled">
|
|
18
20
|
<TooltipTrigger as-child>
|
|
19
21
|
<slot />
|
|
20
22
|
</TooltipTrigger>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import YlfSegmentedControl from '../YlfSegmentedControl.vue'
|
|
4
|
+
|
|
5
|
+
const view = shallowRef<'grid' | 'list'>('grid')
|
|
6
|
+
const kind = shallowRef('all')
|
|
7
|
+
const options = [{ value: 'grid', label: '网格视图' }, { value: 'list', label: '列表视图' }] as const
|
|
8
|
+
const icons = { grid: 'i-ri-grid-line', list: 'i-ri-list-check' }
|
|
9
|
+
const kinds = [{ value: 'all', label: '全部', count: 24 }, { value: 'image', label: '图片', count: 18 }, { value: 'video', label: '视频', count: 6 }]
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="flex flex-wrap items-center gap-4">
|
|
14
|
+
<YlfSegmentedControl v-model="kind" :options="kinds" label="素材类型" size="sm" />
|
|
15
|
+
<YlfSegmentedControl v-model="view" :options="options" label="资源视图" size="sm" icon-only>
|
|
16
|
+
<template #icon="{ option }">
|
|
17
|
+
<i :class="icons[option.value]" />
|
|
18
|
+
</template>
|
|
19
|
+
</YlfSegmentedControl>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Segmented Control
|
|
3
|
+
title_zh: 分段选择器
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
单选视图与筛选控件,基于 [Reka UI ToggleGroup](https://reka-ui.com/docs/components/toggle-group)。保留方向键、Home / End 和禁用项处理,再次点击当前项不会清空选择。切换独立内容面板请使用 Tabs。
|
|
7
|
+
|
|
8
|
+
| 属性 | 说明 | 默认值 |
|
|
9
|
+
| ----------- | ----------------------------------------------------------------- | ------- |
|
|
10
|
+
| `v-model` | 当前选项,支持字符串联合类型 | 必填 |
|
|
11
|
+
| `options` | `{ value, label, disabled?, count?, ariaLabel? }[]`,支持只读数组 | 必填 |
|
|
12
|
+
| `label` | 组合控件的无障碍名称 | 必填 |
|
|
13
|
+
| `size` | `sm`:34px 工作台;`md`:44px 常规界面 | `md` |
|
|
14
|
+
| `icon-only` | 仅显示图标,并提供文字 Tooltip | `false` |
|
|
15
|
+
| `disabled` | 禁用全部选项 | `false` |
|
|
16
|
+
|
|
17
|
+
`#icon="{ option, selected }"` 插槽接收当前项及选中状态。应用通过完整的 UnoCSS / Iconify 类名映射渲染图标,公共组件不依赖应用的图标构建方式。没有图标插槽时仍显示标签。
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import YlfSegmentedControl from '@yunlefun/vue/components/YlfSegmentedControl.vue'
|
|
22
|
+
import { shallowRef } from 'vue'
|
|
23
|
+
|
|
24
|
+
const view = shallowRef<'grid' | 'list'>('grid')
|
|
25
|
+
const options = [
|
|
26
|
+
{ value: 'grid', label: '网格视图' },
|
|
27
|
+
{ value: 'list', label: '列表视图' },
|
|
28
|
+
] as const
|
|
29
|
+
const icons = { grid: 'i-lucide-grid-2x2', list: 'i-lucide-list' }
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<YlfSegmentedControl v-model="view" :options="options" label="资源视图" size="sm" icon-only>
|
|
34
|
+
<template #icon="{ option }">
|
|
35
|
+
<i :class="icons[option.value]" />
|
|
36
|
+
</template>
|
|
37
|
+
</YlfSegmentedControl>
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
样式统一来自 `--ylf-*`:轻灰底座、表面色选中项、品牌色文字、细边框与焦点环。`sm` 与 `YlfSelect size="sm"` 对齐。触摸设备中选项至少 44px;减少动态效果时禁用过渡。不需要在业务页面覆盖选中态或圆角。
|
package/dist/index.d.cts
CHANGED
|
@@ -19,6 +19,15 @@ interface BottomMenuItem {
|
|
|
19
19
|
onClick?: (...args: any[]) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** One choice in a single-selection segmented control. */
|
|
23
|
+
interface YlfSegmentedOption<T extends string = string> {
|
|
24
|
+
value: T;
|
|
25
|
+
label: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
count?: number;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/** Shared accents, defaulting to sky blue. Status colors use their own semantic variants. */
|
|
23
32
|
type YlfAccentTone = 'blue' | 'sun' | 'cyan' | 'coral' | 'pink' | 'green';
|
|
24
33
|
/** Visual emphasis for accent and status buttons/badges. */
|
|
@@ -28,4 +37,4 @@ declare const _dirname: string;
|
|
|
28
37
|
declare const componentsDir: string;
|
|
29
38
|
|
|
30
39
|
export { _dirname, componentsDir };
|
|
31
|
-
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance };
|
|
40
|
+
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance, YlfSegmentedOption };
|
package/dist/index.d.mts
CHANGED
|
@@ -19,6 +19,15 @@ interface BottomMenuItem {
|
|
|
19
19
|
onClick?: (...args: any[]) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** One choice in a single-selection segmented control. */
|
|
23
|
+
interface YlfSegmentedOption<T extends string = string> {
|
|
24
|
+
value: T;
|
|
25
|
+
label: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
count?: number;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/** Shared accents, defaulting to sky blue. Status colors use their own semantic variants. */
|
|
23
32
|
type YlfAccentTone = 'blue' | 'sun' | 'cyan' | 'coral' | 'pink' | 'green';
|
|
24
33
|
/** Visual emphasis for accent and status buttons/badges. */
|
|
@@ -28,4 +37,4 @@ declare const _dirname: string;
|
|
|
28
37
|
declare const componentsDir: string;
|
|
29
38
|
|
|
30
39
|
export { _dirname, componentsDir };
|
|
31
|
-
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance };
|
|
40
|
+
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance, YlfSegmentedOption };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,15 @@ interface BottomMenuItem {
|
|
|
19
19
|
onClick?: (...args: any[]) => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** One choice in a single-selection segmented control. */
|
|
23
|
+
interface YlfSegmentedOption<T extends string = string> {
|
|
24
|
+
value: T;
|
|
25
|
+
label: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
count?: number;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/** Shared accents, defaulting to sky blue. Status colors use their own semantic variants. */
|
|
23
32
|
type YlfAccentTone = 'blue' | 'sun' | 'cyan' | 'coral' | 'pink' | 'green';
|
|
24
33
|
/** Visual emphasis for accent and status buttons/badges. */
|
|
@@ -28,4 +37,4 @@ declare const _dirname: string;
|
|
|
28
37
|
declare const componentsDir: string;
|
|
29
38
|
|
|
30
39
|
export { _dirname, componentsDir };
|
|
31
|
-
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance };
|
|
40
|
+
export type { BottomMenuItem, YlfAccentTone, YlfColorAppearance, YlfSegmentedOption };
|