@fragments-sdk/ui 0.7.2 → 0.7.4
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/fragments.json +1 -1
- package/package.json +6 -2
- package/src/components/Accordion/Accordion.module.scss +11 -7
- package/src/components/AppShell/AppShell.module.scss +5 -5
- package/src/components/Avatar/index.tsx +11 -1
- package/src/components/Button/Button.module.scss +3 -3
- package/src/components/Chart/Chart.module.scss +5 -0
- package/src/components/Chart/index.tsx +34 -0
- package/src/components/Checkbox/index.tsx +29 -4
- package/src/components/Chip/Chip.module.scss +20 -7
- package/src/components/Chip/index.tsx +36 -26
- package/src/components/CodeBlock/CodeBlock.module.scss +6 -6
- package/src/components/CodeBlock/index.tsx +4 -1
- package/src/components/Collapsible/Collapsible.module.scss +1 -1
- package/src/components/Combobox/Combobox.module.scss +4 -4
- package/src/components/ConversationList/ConversationList.module.scss +4 -4
- package/src/components/Dialog/Dialog.module.scss +4 -4
- package/src/components/Dialog/index.tsx +14 -6
- package/src/components/EmptyState/EmptyState.module.scss +3 -3
- package/src/components/Grid/Grid.module.scss +3 -3
- package/src/components/Header/Header.module.scss +4 -4
- package/src/components/Input/index.tsx +15 -1
- package/src/components/Listbox/Listbox.module.scss +18 -8
- package/src/components/Listbox/index.tsx +163 -13
- package/src/components/Markdown/Markdown.module.scss +3 -3
- package/src/components/Menu/Menu.module.scss +3 -3
- package/src/components/Message/Message.module.scss +9 -3
- package/src/components/Popover/index.tsx +4 -1
- package/src/components/Prompt/Prompt.module.scss +6 -5
- package/src/components/RadioGroup/index.tsx +37 -4
- package/src/components/Select/Select.module.scss +3 -3
- package/src/components/Select/index.tsx +3 -2
- package/src/components/Sidebar/Sidebar.module.scss +32 -22
- package/src/components/Sidebar/index.tsx +31 -9
- package/src/components/Slider/index.tsx +13 -3
- package/src/components/Table/Table.module.scss +27 -17
- package/src/components/Table/index.tsx +52 -30
- package/src/components/Tabs/Tabs.module.scss +2 -4
- package/src/components/Textarea/index.tsx +22 -2
- package/src/components/Toast/index.tsx +114 -20
- package/src/components/ToggleGroup/index.tsx +55 -1
- package/src/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/tokens/_computed.scss +2 -0
- package/src/tokens/_density.scss +4 -0
- package/src/tokens/_derive.scss +16 -16
- package/src/tokens/_variables.scss +8 -2
|
@@ -41,10 +41,21 @@ export interface RadioItemProps {
|
|
|
41
41
|
description?: string;
|
|
42
42
|
/** Whether this item is disabled */
|
|
43
43
|
disabled?: boolean;
|
|
44
|
+
/** Accessible name for icon-only mode */
|
|
45
|
+
'aria-label'?: string;
|
|
46
|
+
/** Accessible labelled-by relationship */
|
|
47
|
+
'aria-labelledby'?: string;
|
|
48
|
+
/** Accessible described-by relationship */
|
|
49
|
+
'aria-describedby'?: string;
|
|
44
50
|
/** Additional class name */
|
|
45
51
|
className?: string;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
function mergeAriaIds(...ids: Array<string | undefined>): string | undefined {
|
|
55
|
+
const merged = ids.filter(Boolean).join(' ').trim();
|
|
56
|
+
return merged.length > 0 ? merged : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
// ============================================
|
|
49
60
|
// Context for size
|
|
50
61
|
// ============================================
|
|
@@ -60,9 +71,15 @@ function RadioItem({
|
|
|
60
71
|
label,
|
|
61
72
|
description,
|
|
62
73
|
disabled = false,
|
|
74
|
+
'aria-label': ariaLabel,
|
|
75
|
+
'aria-labelledby': ariaLabelledBy,
|
|
76
|
+
'aria-describedby': ariaDescribedBy,
|
|
63
77
|
className,
|
|
64
78
|
}: RadioItemProps) {
|
|
65
79
|
const size = React.useContext(RadioSizeContext);
|
|
80
|
+
const id = React.useId();
|
|
81
|
+
const labelId = label ? `radio-label-${id}` : undefined;
|
|
82
|
+
const descriptionId = description ? `radio-desc-${id}` : undefined;
|
|
66
83
|
|
|
67
84
|
const radioClasses = [
|
|
68
85
|
styles.radio,
|
|
@@ -84,6 +101,9 @@ function RadioItem({
|
|
|
84
101
|
<BaseRadio.Root
|
|
85
102
|
value={value}
|
|
86
103
|
disabled={disabled}
|
|
104
|
+
aria-label={ariaLabel}
|
|
105
|
+
aria-labelledby={ariaLabelledBy}
|
|
106
|
+
aria-describedby={ariaDescribedBy}
|
|
87
107
|
className={[radioClasses, className].filter(Boolean).join(' ')}
|
|
88
108
|
>
|
|
89
109
|
<BaseRadio.Indicator className={styles.indicator} />
|
|
@@ -96,14 +116,17 @@ function RadioItem({
|
|
|
96
116
|
<BaseRadio.Root
|
|
97
117
|
value={value}
|
|
98
118
|
disabled={disabled}
|
|
119
|
+
aria-label={ariaLabel}
|
|
120
|
+
aria-labelledby={mergeAriaIds(ariaLabelledBy, labelId)}
|
|
121
|
+
aria-describedby={mergeAriaIds(ariaDescribedBy, descriptionId)}
|
|
99
122
|
className={radioClasses}
|
|
100
123
|
>
|
|
101
124
|
<BaseRadio.Indicator className={styles.indicator} />
|
|
102
125
|
</BaseRadio.Root>
|
|
103
126
|
<div className={styles.content}>
|
|
104
|
-
<span className={labelClasses}>{label}</span>
|
|
127
|
+
<span id={labelId} className={labelClasses}>{label}</span>
|
|
105
128
|
{description && (
|
|
106
|
-
<span className={styles.description}>{description}</span>
|
|
129
|
+
<span id={descriptionId} className={styles.description}>{description}</span>
|
|
107
130
|
)}
|
|
108
131
|
</div>
|
|
109
132
|
</label>
|
|
@@ -126,8 +149,15 @@ function RadioGroupRoot({
|
|
|
126
149
|
size = 'md',
|
|
127
150
|
children,
|
|
128
151
|
className,
|
|
152
|
+
'aria-label': ariaLabel,
|
|
153
|
+
'aria-labelledby': ariaLabelledBy,
|
|
154
|
+
'aria-describedby': ariaDescribedBy,
|
|
129
155
|
...htmlProps
|
|
130
156
|
}: RadioGroupProps) {
|
|
157
|
+
const groupId = React.useId();
|
|
158
|
+
const labelId = label ? `radio-group-label-${groupId}` : undefined;
|
|
159
|
+
const errorId = error ? `radio-group-error-${groupId}` : undefined;
|
|
160
|
+
|
|
131
161
|
const groupClasses = [
|
|
132
162
|
styles.group,
|
|
133
163
|
styles[orientation],
|
|
@@ -137,18 +167,21 @@ function RadioGroupRoot({
|
|
|
137
167
|
return (
|
|
138
168
|
<RadioSizeContext.Provider value={size}>
|
|
139
169
|
<div {...htmlProps} className={styles.wrapper}>
|
|
140
|
-
{label && <span className={styles.groupLabel}>{label}</span>}
|
|
170
|
+
{label && <span id={labelId} className={styles.groupLabel}>{label}</span>}
|
|
141
171
|
<BaseRadioGroup
|
|
142
172
|
value={value}
|
|
143
173
|
defaultValue={defaultValue}
|
|
144
174
|
onValueChange={onValueChange}
|
|
145
175
|
disabled={disabled}
|
|
146
176
|
name={name}
|
|
177
|
+
aria-label={ariaLabel}
|
|
178
|
+
aria-labelledby={mergeAriaIds(ariaLabelledBy, labelId)}
|
|
179
|
+
aria-describedby={mergeAriaIds(ariaDescribedBy, errorId)}
|
|
147
180
|
className={groupClasses}
|
|
148
181
|
>
|
|
149
182
|
{children}
|
|
150
183
|
</BaseRadioGroup>
|
|
151
|
-
{error && <span className={styles.error}>{error}</span>}
|
|
184
|
+
{error && <span id={errorId} className={styles.error}>{error}</span>}
|
|
152
185
|
</div>
|
|
153
186
|
</RadioSizeContext.Provider>
|
|
154
187
|
);
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
var(--fui-space-1, #{$fui-space-1}) * 2
|
|
97
97
|
) !important;
|
|
98
98
|
overflow-y: auto !important;
|
|
99
|
-
padding: var(--fui-
|
|
99
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs);
|
|
100
100
|
box-shadow: var(--fui-shadow-md, $fui-shadow-md);
|
|
101
101
|
|
|
102
102
|
// Animation
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
align-items: center;
|
|
129
129
|
gap: var(--fui-space-2, $fui-space-2);
|
|
130
130
|
width: 100%;
|
|
131
|
-
padding: var(--fui-
|
|
131
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
132
132
|
border-radius: var(--fui-radius-sm, $fui-radius-sm);
|
|
133
133
|
cursor: pointer;
|
|
134
134
|
outline: none;
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
|
|
179
179
|
// Group label
|
|
180
180
|
.groupLabel {
|
|
181
|
-
padding: var(--fui-
|
|
181
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
182
182
|
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
183
183
|
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
184
184
|
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
@@ -268,11 +268,12 @@ function SelectItem({ children, value, disabled, className }: SelectItemProps) {
|
|
|
268
268
|
|
|
269
269
|
// Register this item's children in the registry so the trigger can display them
|
|
270
270
|
React.useEffect(() => {
|
|
271
|
-
itemsRef.current
|
|
271
|
+
const items = itemsRef.current;
|
|
272
|
+
items.set(value, children);
|
|
272
273
|
// Trigger re-render of trigger to show the registered content
|
|
273
274
|
incrementItemsVersion();
|
|
274
275
|
return () => {
|
|
275
|
-
|
|
276
|
+
items.delete(value);
|
|
276
277
|
};
|
|
277
278
|
}, [itemsRef, incrementItemsVersion, value, children]);
|
|
278
279
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
.header {
|
|
33
33
|
justify-content: center;
|
|
34
|
-
padding: var(--fui-
|
|
34
|
+
padding: var(--fui-padding-container-sm, $fui-padding-container-sm);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.sectionLabel {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
.item {
|
|
48
48
|
justify-content: center;
|
|
49
|
-
padding: var(--fui-
|
|
49
|
+
padding: var(--fui-padding-container-sm, $fui-padding-container-sm);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.itemIcon {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
|
|
56
56
|
.footer {
|
|
57
57
|
justify-content: center;
|
|
58
|
-
padding: var(--fui-
|
|
58
|
+
padding: var(--fui-padding-container-sm, $fui-padding-container-sm);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.collapseToggle {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
display: flex;
|
|
93
93
|
align-items: center;
|
|
94
94
|
gap: var(--fui-space-3, $fui-space-3);
|
|
95
|
-
padding: var(--fui-
|
|
95
|
+
padding: var(--fui-padding-item-sm, $fui-padding-item-sm) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
96
96
|
flex-shrink: 0;
|
|
97
97
|
height: var(--appshell-header-height, 56px);
|
|
98
98
|
}
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
flex: 1;
|
|
106
106
|
overflow-y: auto;
|
|
107
107
|
overflow-x: hidden;
|
|
108
|
-
padding: var(--fui-
|
|
108
|
+
padding: var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// ============================================
|
|
@@ -121,13 +121,19 @@
|
|
|
121
121
|
.sectionHeader {
|
|
122
122
|
display: flex;
|
|
123
123
|
align-items: center;
|
|
124
|
-
|
|
125
|
-
padding: var(--fui-
|
|
124
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
125
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.sectionTrigger {
|
|
129
|
+
flex: 1;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
.sectionLabel {
|
|
129
133
|
@include helper-text;
|
|
130
134
|
|
|
135
|
+
flex: 1;
|
|
136
|
+
min-width: 0;
|
|
131
137
|
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
132
138
|
text-transform: uppercase;
|
|
133
139
|
letter-spacing: 0.05em;
|
|
@@ -170,11 +176,14 @@
|
|
|
170
176
|
// Collapsible section styles (using Collapsible component)
|
|
171
177
|
.sectionCollapsible {
|
|
172
178
|
// Override Collapsible trigger styles for sidebar context
|
|
173
|
-
|
|
174
|
-
button[aria-expanded] {
|
|
179
|
+
.sectionTrigger {
|
|
175
180
|
background: transparent;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
176
183
|
justify-content: space-between;
|
|
177
|
-
|
|
184
|
+
width: 100%;
|
|
185
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
186
|
+
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
178
187
|
|
|
179
188
|
&:hover {
|
|
180
189
|
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
@@ -199,11 +208,10 @@
|
|
|
199
208
|
@include interactive-base;
|
|
200
209
|
@include text-base;
|
|
201
210
|
|
|
202
|
-
display: flex;
|
|
211
|
+
display: inline-flex;
|
|
203
212
|
align-items: center;
|
|
204
213
|
gap: var(--fui-space-3, $fui-space-3);
|
|
205
|
-
|
|
206
|
-
padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
|
|
214
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
207
215
|
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
208
216
|
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
209
217
|
text-decoration: none;
|
|
@@ -212,6 +220,7 @@
|
|
|
212
220
|
&:hover:not(.itemDisabled) {
|
|
213
221
|
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
214
222
|
color: var(--fui-text-primary, $fui-text-primary);
|
|
223
|
+
text-decoration: none;
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
&:active:not(.itemDisabled) {
|
|
@@ -225,8 +234,9 @@
|
|
|
225
234
|
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
226
235
|
|
|
227
236
|
&:hover {
|
|
228
|
-
background-color: var(--fui-color-accent-
|
|
229
|
-
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
237
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover) !important;
|
|
238
|
+
color: var(--fui-text-inverse, $fui-text-inverse) !important;
|
|
239
|
+
text-decoration: none;
|
|
230
240
|
}
|
|
231
241
|
|
|
232
242
|
.itemIcon {
|
|
@@ -270,7 +280,7 @@
|
|
|
270
280
|
display: flex;
|
|
271
281
|
align-items: center;
|
|
272
282
|
justify-content: center;
|
|
273
|
-
padding: var(--fui-
|
|
283
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
274
284
|
font-size: var(--fui-font-size-2xs, $fui-font-size-2xs);
|
|
275
285
|
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
276
286
|
// Use accent-hover for WCAG AA contrast (4.5:1 minimum)
|
|
@@ -341,10 +351,9 @@
|
|
|
341
351
|
@include interactive-base;
|
|
342
352
|
@include text-base;
|
|
343
353
|
|
|
344
|
-
display: flex;
|
|
354
|
+
display: inline-flex;
|
|
345
355
|
align-items: center;
|
|
346
|
-
|
|
347
|
-
padding: var(--fui-space-1, $fui-space-1) var(--fui-space-2, $fui-space-2);
|
|
356
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
348
357
|
border-radius: var(--fui-radius-md, $fui-radius-md);
|
|
349
358
|
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
350
359
|
text-decoration: none;
|
|
@@ -369,6 +378,7 @@
|
|
|
369
378
|
&:hover:not(.subItemDisabled) {
|
|
370
379
|
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
371
380
|
color: var(--fui-text-primary, $fui-text-primary);
|
|
381
|
+
text-decoration: none;
|
|
372
382
|
}
|
|
373
383
|
|
|
374
384
|
&:active:not(.subItemDisabled) {
|
|
@@ -399,7 +409,7 @@
|
|
|
399
409
|
display: flex;
|
|
400
410
|
flex-direction: column;
|
|
401
411
|
gap: var(--fui-space-2, $fui-space-2);
|
|
402
|
-
padding: var(--fui-
|
|
412
|
+
padding: var(--fui-padding-container-md, $fui-padding-container-md);
|
|
403
413
|
margin-top: auto;
|
|
404
414
|
flex-shrink: 0;
|
|
405
415
|
}
|
|
@@ -563,14 +573,14 @@
|
|
|
563
573
|
display: flex;
|
|
564
574
|
flex-direction: column;
|
|
565
575
|
gap: var(--fui-space-2, $fui-space-2);
|
|
566
|
-
padding: var(--fui-
|
|
576
|
+
padding: var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
567
577
|
}
|
|
568
578
|
|
|
569
579
|
.skeletonItem {
|
|
570
580
|
display: flex;
|
|
571
581
|
align-items: center;
|
|
572
582
|
gap: var(--fui-space-3, $fui-space-3);
|
|
573
|
-
padding: var(--fui-
|
|
583
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-sm, $fui-padding-item-sm);
|
|
574
584
|
min-height: $fui-sidebar-item-height;
|
|
575
585
|
}
|
|
576
586
|
|
|
@@ -3,6 +3,7 @@ import styles from './Sidebar.module.scss';
|
|
|
3
3
|
import { Tooltip } from '../Tooltip';
|
|
4
4
|
import { Skeleton } from '../Skeleton';
|
|
5
5
|
import { Collapsible } from '../Collapsible';
|
|
6
|
+
import { useFocusTrap } from '../../utils/a11y';
|
|
6
7
|
// Import globals to ensure CSS variables are defined
|
|
7
8
|
import '../../styles/globals.scss';
|
|
8
9
|
|
|
@@ -271,6 +272,7 @@ interface SidebarContextValue {
|
|
|
271
272
|
collapsedWidth: string;
|
|
272
273
|
collapsible: SidebarCollapsible;
|
|
273
274
|
toggleSidebar: () => void;
|
|
275
|
+
sidebarId: string;
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
const SidebarContext = React.createContext<SidebarContextValue | null>(null);
|
|
@@ -294,6 +296,7 @@ function useSidebar() {
|
|
|
294
296
|
collapsedWidth: '64px',
|
|
295
297
|
collapsible: 'icon' as SidebarCollapsible,
|
|
296
298
|
toggleSidebar: () => {},
|
|
299
|
+
sidebarId: 'sidebar',
|
|
297
300
|
state: 'expanded' as 'expanded' | 'collapsed' | 'open' | 'closed',
|
|
298
301
|
};
|
|
299
302
|
}
|
|
@@ -379,6 +382,7 @@ function SidebarProvider({
|
|
|
379
382
|
enableKeyboardShortcut = true,
|
|
380
383
|
}: SidebarProviderProps) {
|
|
381
384
|
const isMobile = useIsMobile();
|
|
385
|
+
const sidebarId = React.useId();
|
|
382
386
|
|
|
383
387
|
const [collapsed, setCollapsed] = useControllableState(
|
|
384
388
|
controlledCollapsed,
|
|
@@ -456,6 +460,7 @@ function SidebarProvider({
|
|
|
456
460
|
collapsedWidth,
|
|
457
461
|
collapsible,
|
|
458
462
|
toggleSidebar,
|
|
463
|
+
sidebarId,
|
|
459
464
|
};
|
|
460
465
|
|
|
461
466
|
return (
|
|
@@ -479,6 +484,7 @@ function SidebarRoot({
|
|
|
479
484
|
collapsible = 'icon',
|
|
480
485
|
className,
|
|
481
486
|
style: styleProp,
|
|
487
|
+
'aria-label': ariaLabel,
|
|
482
488
|
...htmlProps
|
|
483
489
|
}: SidebarProps) {
|
|
484
490
|
// Check if we're inside a SidebarProvider
|
|
@@ -506,6 +512,11 @@ function SidebarRoot({
|
|
|
506
512
|
const resolvedWidth = existingContext ? existingContext.width : width;
|
|
507
513
|
const resolvedCollapsedWidth = existingContext ? existingContext.collapsedWidth : collapsedWidth;
|
|
508
514
|
const resolvedCollapsible = existingContext ? existingContext.collapsible : collapsible;
|
|
515
|
+
const sidebarId = React.useId();
|
|
516
|
+
const resolvedSidebarId = existingContext ? existingContext.sidebarId : sidebarId;
|
|
517
|
+
const sidebarRef = React.useRef<HTMLElement>(null);
|
|
518
|
+
|
|
519
|
+
useFocusTrap(sidebarRef, isMobile && open);
|
|
509
520
|
|
|
510
521
|
const toggleSidebar = React.useCallback(() => {
|
|
511
522
|
if (resolvedCollapsible === 'none') return;
|
|
@@ -558,6 +569,7 @@ function SidebarRoot({
|
|
|
558
569
|
collapsedWidth: resolvedCollapsedWidth,
|
|
559
570
|
collapsible: resolvedCollapsible,
|
|
560
571
|
toggleSidebar,
|
|
572
|
+
sidebarId: resolvedSidebarId,
|
|
561
573
|
};
|
|
562
574
|
|
|
563
575
|
const isCollapsedForStyle = resolvedCollapsible === 'icon' && collapsed;
|
|
@@ -580,9 +592,15 @@ function SidebarRoot({
|
|
|
580
592
|
|
|
581
593
|
const content = (
|
|
582
594
|
<aside
|
|
595
|
+
ref={sidebarRef}
|
|
596
|
+
id={resolvedSidebarId}
|
|
583
597
|
{...htmlProps}
|
|
584
598
|
className={classes}
|
|
585
599
|
style={style}
|
|
600
|
+
role={isMobile ? 'dialog' : undefined}
|
|
601
|
+
aria-modal={isMobile && open ? true : undefined}
|
|
602
|
+
aria-hidden={isMobile && !open ? true : undefined}
|
|
603
|
+
aria-label={isMobile ? (ariaLabel || 'Sidebar navigation') : ariaLabel}
|
|
586
604
|
data-state={isMobile ? (open ? 'open' : 'closed') : (collapsed ? 'collapsed' : 'expanded')}
|
|
587
605
|
data-position={resolvedPosition}
|
|
588
606
|
data-collapsible={resolvedCollapsible}
|
|
@@ -663,13 +681,15 @@ function SidebarSection({
|
|
|
663
681
|
return (
|
|
664
682
|
<div className={classes} role="group" aria-label={label}>
|
|
665
683
|
<Collapsible defaultOpen={defaultOpen} className={styles.sectionCollapsible}>
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
684
|
+
<div className={styles.sectionHeader}>
|
|
685
|
+
<Collapsible.Trigger
|
|
686
|
+
className={styles.sectionTrigger}
|
|
687
|
+
chevronPosition="end"
|
|
688
|
+
>
|
|
689
|
+
<span className={styles.sectionLabel}>{label}</span>
|
|
690
|
+
</Collapsible.Trigger>
|
|
691
|
+
{showAction && <div className={styles.sectionActionWrapper}>{action}</div>}
|
|
692
|
+
</div>
|
|
673
693
|
<Collapsible.Content className={styles.sectionContent}>
|
|
674
694
|
<ul className={styles.sectionList}>
|
|
675
695
|
{children}
|
|
@@ -887,7 +907,7 @@ function SidebarFooter({ children, className }: SidebarFooterProps) {
|
|
|
887
907
|
}
|
|
888
908
|
|
|
889
909
|
function SidebarTrigger({ children, 'aria-label': ariaLabel = 'Toggle navigation', className }: SidebarTriggerProps) {
|
|
890
|
-
const { open, setOpen, isMobile } = useSidebarContext();
|
|
910
|
+
const { open, setOpen, isMobile, sidebarId } = useSidebarContext();
|
|
891
911
|
|
|
892
912
|
// Only render trigger on mobile
|
|
893
913
|
if (!isMobile) {
|
|
@@ -903,6 +923,7 @@ function SidebarTrigger({ children, 'aria-label': ariaLabel = 'Toggle navigation
|
|
|
903
923
|
onClick={() => setOpen(!open)}
|
|
904
924
|
aria-label={ariaLabel}
|
|
905
925
|
aria-expanded={open}
|
|
926
|
+
aria-controls={sidebarId}
|
|
906
927
|
>
|
|
907
928
|
{children || (open ? <CloseIcon /> : <MenuIcon />)}
|
|
908
929
|
</button>
|
|
@@ -989,6 +1010,7 @@ function SidebarMenuSkeleton({
|
|
|
989
1010
|
const isCollapsed = collapsed && !isMobile;
|
|
990
1011
|
|
|
991
1012
|
const classes = [styles.menuSkeleton, className].filter(Boolean).join(' ');
|
|
1013
|
+
const labelWidths = ['64%', '72%', '68%', '79%', '74%', '66%', '83%', '70%'];
|
|
992
1014
|
|
|
993
1015
|
return (
|
|
994
1016
|
<div className={classes} aria-hidden="true">
|
|
@@ -999,7 +1021,7 @@ function SidebarMenuSkeleton({
|
|
|
999
1021
|
<Skeleton
|
|
1000
1022
|
variant="text"
|
|
1001
1023
|
className={styles.skeletonLabel}
|
|
1002
|
-
width={
|
|
1024
|
+
width={labelWidths[i % labelWidths.length]}
|
|
1003
1025
|
/>
|
|
1004
1026
|
)}
|
|
1005
1027
|
</div>
|
|
@@ -16,6 +16,12 @@ export interface SliderProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
16
16
|
valueSuffix?: string;
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
name?: string;
|
|
19
|
+
/** Accessible label when visible label is omitted */
|
|
20
|
+
'aria-label'?: string;
|
|
21
|
+
/** Accessible labelled-by relationship */
|
|
22
|
+
'aria-labelledby'?: string;
|
|
23
|
+
/** Accessible described-by relationship */
|
|
24
|
+
'aria-describedby'?: string;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
@@ -34,6 +40,9 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
34
40
|
className,
|
|
35
41
|
name,
|
|
36
42
|
id,
|
|
43
|
+
'aria-label': ariaLabel,
|
|
44
|
+
'aria-labelledby': ariaLabelledBy,
|
|
45
|
+
'aria-describedby': ariaDescribedBy,
|
|
37
46
|
...htmlProps
|
|
38
47
|
},
|
|
39
48
|
ref
|
|
@@ -48,8 +57,6 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
48
57
|
onChange?.(val);
|
|
49
58
|
};
|
|
50
59
|
|
|
51
|
-
const sliderValue = value !== undefined ? [value] : (defaultValue !== undefined ? [defaultValue] : undefined);
|
|
52
|
-
|
|
53
60
|
return (
|
|
54
61
|
<Field.Root {...htmlProps} disabled={disabled} className={[styles.wrapper, className].filter(Boolean).join(' ')}>
|
|
55
62
|
{(label || showValue) && (
|
|
@@ -64,7 +71,7 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
64
71
|
)}
|
|
65
72
|
<BaseSlider.Root
|
|
66
73
|
ref={ref}
|
|
67
|
-
value={
|
|
74
|
+
value={value !== undefined ? [value] : undefined}
|
|
68
75
|
defaultValue={defaultValue !== undefined ? [defaultValue] : undefined}
|
|
69
76
|
onValueChange={handleChange}
|
|
70
77
|
min={min}
|
|
@@ -73,6 +80,9 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
73
80
|
disabled={disabled}
|
|
74
81
|
name={name}
|
|
75
82
|
id={id}
|
|
83
|
+
aria-label={ariaLabel || (label ? undefined : 'Slider')}
|
|
84
|
+
aria-labelledby={ariaLabelledBy}
|
|
85
|
+
aria-describedby={ariaDescribedBy}
|
|
76
86
|
className={styles.root}
|
|
77
87
|
>
|
|
78
88
|
<BaseSlider.Control className={styles.control}>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
.sm {
|
|
34
34
|
.th,
|
|
35
35
|
.td {
|
|
36
|
-
padding: var(--fui-
|
|
36
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
37
37
|
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
.md {
|
|
42
42
|
.th,
|
|
43
43
|
.td {
|
|
44
|
-
padding: var(--fui-
|
|
44
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
45
45
|
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
position: sticky;
|
|
52
52
|
top: 0;
|
|
53
53
|
z-index: 1;
|
|
54
|
-
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
.headerRow {
|
|
@@ -83,16 +82,21 @@
|
|
|
83
82
|
|
|
84
83
|
// Sortable header cell (for focus styles)
|
|
85
84
|
.thSortable {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
&:focus-visible {
|
|
89
|
-
@include focus-ring;
|
|
90
|
-
outline-offset: -2px;
|
|
91
|
-
}
|
|
85
|
+
padding: 0;
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
.
|
|
95
|
-
|
|
88
|
+
.sortButton {
|
|
89
|
+
@include button-reset;
|
|
90
|
+
@include interactive-base;
|
|
91
|
+
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
96
|
+
width: 100%;
|
|
97
|
+
padding: var(--fui-padding-item-xs, $fui-padding-item-xs) var(--fui-padding-item-md, $fui-padding-item-md);
|
|
98
|
+
color: inherit;
|
|
99
|
+
text-align: left;
|
|
96
100
|
transition: color var(--fui-transition-fast, $fui-transition-fast);
|
|
97
101
|
|
|
98
102
|
&:hover {
|
|
@@ -106,15 +110,13 @@
|
|
|
106
110
|
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
107
111
|
flex-shrink: 0;
|
|
108
112
|
|
|
109
|
-
.
|
|
113
|
+
.sortButton:hover & {
|
|
110
114
|
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
// Body
|
|
115
|
-
.tbody {
|
|
116
|
-
background-color: var(--fui-bg-primary, $fui-bg-primary);
|
|
117
|
-
}
|
|
119
|
+
.tbody {}
|
|
118
120
|
|
|
119
121
|
.row {
|
|
120
122
|
border-bottom: 1px solid var(--fui-border, $fui-border);
|
|
@@ -123,6 +125,10 @@
|
|
|
123
125
|
&:last-child {
|
|
124
126
|
border-bottom: none;
|
|
125
127
|
}
|
|
128
|
+
|
|
129
|
+
&:hover {
|
|
130
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
.clickable {
|
|
@@ -132,6 +138,10 @@
|
|
|
132
138
|
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
133
139
|
}
|
|
134
140
|
|
|
141
|
+
&:focus-visible {
|
|
142
|
+
@include focus-ring;
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
&:active {
|
|
136
146
|
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
137
147
|
}
|
|
@@ -219,7 +229,7 @@
|
|
|
219
229
|
border-bottom-width: 2px;
|
|
220
230
|
}
|
|
221
231
|
|
|
222
|
-
.
|
|
232
|
+
.sortButton:focus-visible {
|
|
223
233
|
outline-width: 3px;
|
|
224
234
|
}
|
|
225
235
|
}
|
|
@@ -233,7 +243,7 @@
|
|
|
233
243
|
transition: none;
|
|
234
244
|
}
|
|
235
245
|
|
|
236
|
-
.
|
|
246
|
+
.sortButton {
|
|
237
247
|
transition: none;
|
|
238
248
|
}
|
|
239
249
|
}
|