@astryxdesign/core 0.4.0 → 0.4.1-canary.f4868b2
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/CHANGELOG.md +39 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +1 -2
- package/dist/AspectRatio/index.d.ts.map +1 -1
- package/dist/AspectRatio/index.js +1 -2
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +1 -2
- package/dist/Badge/index.d.ts.map +1 -1
- package/dist/Badge/index.js +1 -2
- package/dist/Banner/Banner.d.ts +3 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +17 -3
- package/dist/Blockquote/Blockquote.d.ts.map +1 -1
- package/dist/Blockquote/Blockquote.js +1 -2
- package/dist/Blockquote/index.d.ts.map +1 -1
- package/dist/Blockquote/index.js +1 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +1 -2
- package/dist/Card/index.d.ts.map +1 -1
- package/dist/Card/index.js +1 -2
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +1 -2
- package/dist/Center/index.d.ts.map +1 -1
- package/dist/Center/index.js +1 -2
- package/dist/Code/Code.d.ts.map +1 -1
- package/dist/Code/Code.js +1 -2
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -0
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +7 -8
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +23 -10
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +44 -16
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +32 -13
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +10 -3
- package/dist/Field/PanelSearchInput.d.ts +81 -0
- package/dist/Field/PanelSearchInput.d.ts.map +1 -0
- package/dist/Field/PanelSearchInput.js +151 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +21 -10
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +1 -2
- package/dist/Grid/index.d.ts.map +1 -1
- package/dist/Grid/index.js +1 -2
- package/dist/Icon/globalIconRegistry.d.ts +5 -4
- package/dist/Icon/globalIconRegistry.d.ts.map +1 -1
- package/dist/Icon/globalIconRegistry.js +5 -4
- package/dist/Indicator/index.d.ts +1 -1
- package/dist/Indicator/index.d.ts.map +1 -1
- package/dist/Indicator/types.d.ts +10 -0
- package/dist/Indicator/types.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +24 -50
- package/dist/MultiSelector/MultiSelector.d.ts +8 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +134 -102
- package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
- package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
- package/dist/NavItem/navItemStyles.stylex.js +11 -5
- package/dist/Popover/usePopover.d.ts +15 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +14 -2
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +2 -3
- package/dist/Section/Section.d.ts.map +1 -1
- package/dist/Section/Section.js +1 -2
- package/dist/Section/index.d.ts.map +1 -1
- package/dist/Section/index.js +1 -2
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +135 -95
- package/dist/SideNav/SideNav.d.ts +7 -9
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +32 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +39 -15
- package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
- package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseContext.js +16 -2
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +70 -24
- package/dist/SideNav/SideNavItem.d.ts +7 -2
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +114 -75
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +7 -16
- package/dist/SideNav/index.d.ts +1 -1
- package/dist/SideNav/index.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +1 -2
- package/dist/Skeleton/index.d.ts.map +1 -1
- package/dist/Skeleton/index.js +1 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +2 -6
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +3 -3
- package/dist/Table/TableRow.d.ts +1 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +4 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +7 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +14 -3
- package/dist/Timestamp/formatInstant.d.ts +11 -1
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +3 -1
- package/dist/TopNav/TopNavMegaMenu.js +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +2 -2
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden/VisuallyHidden.js +1 -2
- package/dist/VisuallyHidden/index.d.ts.map +1 -1
- package/dist/VisuallyHidden/index.js +1 -2
- package/dist/astryx.css +20 -17
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +17 -4
- package/dist/theme/defineTheme.d.ts +2 -2
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +2 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +23 -0
- package/dist/utils/focusOutline.stylex.d.ts +34 -19
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +46 -33
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +5 -1
- package/dist/utils/interactionModality.d.ts +34 -0
- package/dist/utils/interactionModality.d.ts.map +1 -0
- package/dist/utils/interactionModality.js +70 -0
- package/locales/en.json +44 -0
- package/locales/pseudo.json +33 -0
- package/package.json +3 -3
- package/src/AppShell/AppShell.doc.mjs +3 -0
- package/src/AspectRatio/AspectRatio.tsx +0 -2
- package/src/AspectRatio/index.ts +0 -2
- package/src/Badge/Badge.tsx +0 -2
- package/src/Badge/index.ts +0 -2
- package/src/Banner/Banner.test.tsx +58 -1
- package/src/Banner/Banner.tsx +24 -5
- package/src/Blockquote/Blockquote.tsx +0 -2
- package/src/Blockquote/index.ts +0 -2
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.tsx +5 -4
- package/src/Card/Card.tsx +0 -2
- package/src/Card/index.ts +0 -2
- package/src/Center/Center.tsx +0 -2
- package/src/Center/index.ts +0 -2
- package/src/CheckboxInput/CheckboxInput.test.tsx +8 -3
- package/src/Code/Code.tsx +0 -2
- package/src/CommandPalette/CommandPalette.test.tsx +54 -0
- package/src/CommandPalette/CommandPalette.tsx +5 -0
- package/src/ComplexSelector/ComplexSelector.test.tsx +15 -6
- package/src/ComplexSelector/ComplexSelector.tsx +6 -10
- package/src/DateInput/DateInput.test.tsx +19 -0
- package/src/DateInput/DateInput.tsx +3 -6
- package/src/DateRangeInput/DateRangeInput.tsx +3 -9
- package/src/DateTimeInput/DateTimeInput.test.tsx +79 -1
- package/src/DateTimeInput/DateTimeInput.tsx +23 -9
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +2 -1
- package/src/Field/PanelSearchInput.tsx +271 -0
- package/src/FileInput/FileInput.tsx +21 -8
- package/src/Grid/Grid.tsx +0 -2
- package/src/Grid/index.ts +0 -2
- package/src/Icon/globalIconRegistry.tsx +5 -4
- package/src/Indicator/Indicator.doc.mjs +1 -1
- package/src/Indicator/index.ts +1 -0
- package/src/Indicator/types.ts +11 -0
- package/src/Lightbox/Lightbox.tsx +2 -11
- package/src/MultiSelector/MultiSelector.doc.mjs +9 -0
- package/src/MultiSelector/MultiSelector.test.tsx +135 -5
- package/src/MultiSelector/MultiSelector.tsx +170 -127
- package/src/NavItem/navItemStyles.stylex.ts +31 -5
- package/src/Popover/Popover.doc.mjs +2 -0
- package/src/Popover/usePopover.tsx +37 -4
- package/src/ProgressBar/ProgressBar.tsx +2 -8
- package/src/Section/Section.tsx +0 -2
- package/src/Section/index.ts +0 -2
- package/src/Selector/Selector.doc.mjs +10 -0
- package/src/Selector/Selector.test.tsx +386 -2
- package/src/Selector/Selector.tsx +182 -122
- package/src/SideNav/SideNav.doc.mjs +13 -4
- package/src/SideNav/SideNav.test.tsx +771 -2
- package/src/SideNav/SideNav.tsx +37 -16
- package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
- package/src/SideNav/SideNavCollapseButton.tsx +67 -15
- package/src/SideNav/SideNavCollapseContext.ts +29 -9
- package/src/SideNav/SideNavHeading.tsx +28 -10
- package/src/SideNav/SideNavItem.doc.mjs +13 -0
- package/src/SideNav/SideNavItem.tsx +92 -84
- package/src/SideNav/SideNavSection.tsx +6 -20
- package/src/SideNav/index.ts +2 -0
- package/src/Skeleton/Skeleton.tsx +0 -2
- package/src/Skeleton/index.ts +0 -2
- package/src/Slider/Slider.tsx +2 -11
- package/src/Switch/Switch.tsx +10 -2
- package/src/Table/Table.test.tsx +31 -0
- package/src/Table/TableRow.tsx +6 -0
- package/src/TimeInput/TimeInput.test.tsx +54 -0
- package/src/TimeInput/TimeInput.tsx +17 -2
- package/src/Timestamp/Timestamp.test.tsx +91 -14
- package/src/Timestamp/Timestamp.tsx +14 -2
- package/src/Timestamp/formatInstant.ts +16 -1
- package/src/TopNav/TopNavMegaMenu.doc.mjs +5 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
- package/src/TopNav/TopNavMegaMenu.tsx +4 -1
- package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
- package/src/TopNav/TopNavMenu.test.tsx +41 -0
- package/src/TopNav/TopNavMenu.tsx +8 -2
- package/src/TreeList/TreeList.test.tsx +3 -2
- package/src/VisuallyHidden/VisuallyHidden.tsx +0 -2
- package/src/VisuallyHidden/index.ts +0 -2
- package/src/hooks/useInputStatusIcon.tsx +8 -8
- package/src/serverSafeComponents.test.ts +665 -0
- package/src/theme/defineTheme.ts +3 -0
- package/src/theme/generateThemeRules.test.ts +17 -0
- package/src/theme/index.ts +2 -0
- package/src/theme/tokens.stylex.ts +18 -0
- package/src/theme/tokens.test.ts +37 -0
- package/src/utils/focusOutline.stylex.ts +37 -23
- package/src/utils/index.ts +4 -0
- package/src/utils/interactionModality.ts +74 -0
|
@@ -33,6 +33,7 @@ import {usePopover} from '../Popover/usePopover';
|
|
|
33
33
|
import {useTooltip} from '../Tooltip';
|
|
34
34
|
import {Icon, renderIconSlot, type IconType} from '../Icon';
|
|
35
35
|
import {useIndicator} from '../Indicator';
|
|
36
|
+
import type {IndicatorPosition} from '../Indicator';
|
|
36
37
|
import type {IconName} from '../Icon';
|
|
37
38
|
import {
|
|
38
39
|
Field,
|
|
@@ -46,7 +47,7 @@ import {Divider} from '../Divider';
|
|
|
46
47
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
47
48
|
import type {LayerPlacement} from '../Layer/useLayer';
|
|
48
49
|
import {Spinner} from '../Spinner';
|
|
49
|
-
import {
|
|
50
|
+
import {PanelSearchInput} from '../Field/PanelSearchInput';
|
|
50
51
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
51
52
|
import {
|
|
52
53
|
colorVars,
|
|
@@ -76,6 +77,7 @@ import {useSize} from '../SizeContext/SizeContext';
|
|
|
76
77
|
import type {BaseProps} from '../BaseProps';
|
|
77
78
|
import type {SizeValue} from '../utils/types';
|
|
78
79
|
import {themeProps} from '../utils/themeProps';
|
|
80
|
+
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
79
81
|
import {stableClassName} from '../naming';
|
|
80
82
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
81
83
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
@@ -179,14 +181,6 @@ const styles = stylex.create({
|
|
|
179
181
|
':focus-within': 'none',
|
|
180
182
|
},
|
|
181
183
|
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
182
|
-
outline: {
|
|
183
|
-
default: 'none',
|
|
184
|
-
':has(:focus-visible)': `2px solid ${colorVars['--color-accent']}`,
|
|
185
|
-
},
|
|
186
|
-
outlineOffset: {
|
|
187
|
-
default: '0',
|
|
188
|
-
':has(:focus-visible)': '3px',
|
|
189
|
-
},
|
|
190
184
|
transitionProperty:
|
|
191
185
|
'background-image, background-color, color, opacity, transform',
|
|
192
186
|
transform: {
|
|
@@ -215,11 +209,6 @@ const styles = stylex.create({
|
|
|
215
209
|
color: 'inherit',
|
|
216
210
|
cursor: 'pointer',
|
|
217
211
|
borderRadius: radiusVars['--radius-element'],
|
|
218
|
-
outline: {
|
|
219
|
-
default: 'none',
|
|
220
|
-
':focus-visible': `${borderVars['--border-width']} solid ${colorVars['--color-accent']}`,
|
|
221
|
-
},
|
|
222
|
-
outlineOffset: 1,
|
|
223
212
|
},
|
|
224
213
|
|
|
225
214
|
// Dropdown container
|
|
@@ -237,6 +226,11 @@ const styles = stylex.create({
|
|
|
237
226
|
// pixel in the menu; the borderless ghost variant needs no correction.
|
|
238
227
|
paddingInline: `calc(${spacingVars['--spacing-1']} + ${borderVars['--border-width']})`,
|
|
239
228
|
},
|
|
229
|
+
// Same correction for the search row's gutter, so the search field and the
|
|
230
|
+
// option rows share one left edge.
|
|
231
|
+
searchRowInput: {
|
|
232
|
+
paddingInline: `calc(${spacingVars['--spacing-1']} + ${borderVars['--border-width']})`,
|
|
233
|
+
},
|
|
240
234
|
dropdownHidden: {
|
|
241
235
|
opacity: 0,
|
|
242
236
|
transition: 'none',
|
|
@@ -246,15 +240,6 @@ const styles = stylex.create({
|
|
|
246
240
|
popover: {
|
|
247
241
|
minWidth: 'anchor-size(width)',
|
|
248
242
|
},
|
|
249
|
-
// Search field. The inner TextInput owns the border, focus ring, magnifier
|
|
250
|
-
// (startIcon), and clear button (hasClear); this wrapper only supplies the
|
|
251
|
-
// dropdown's inline/block padding around it.
|
|
252
|
-
searchWrapper: {
|
|
253
|
-
display: 'flex',
|
|
254
|
-
alignItems: 'center',
|
|
255
|
-
paddingInline: spacingVars['--spacing-2'],
|
|
256
|
-
paddingBlock: spacingVars['--spacing-1'],
|
|
257
|
-
},
|
|
258
243
|
|
|
259
244
|
// Empty state
|
|
260
245
|
emptyState: {
|
|
@@ -265,9 +250,19 @@ const styles = stylex.create({
|
|
|
265
250
|
fontSize: typeScaleVars['--text-label-size'],
|
|
266
251
|
},
|
|
267
252
|
|
|
268
|
-
// Section
|
|
269
|
-
|
|
270
|
-
|
|
253
|
+
// Section heading. Plain secondary text, no rules — the same treatment
|
|
254
|
+
// DropdownMenu and CommandPaletteGroup already use for a group heading in a
|
|
255
|
+
// panel list. A labeled Divider (line–text–line) reads as a separator, and
|
|
256
|
+
// next to the search row's own divider it stacked two rules a few pixels
|
|
257
|
+
// apart.
|
|
258
|
+
sectionHeading: {
|
|
259
|
+
paddingBlock: spacingVars['--spacing-1'],
|
|
260
|
+
paddingInline: spacingVars['--spacing-2'],
|
|
261
|
+
fontFamily: typographyVars['--font-family-body'],
|
|
262
|
+
fontSize: typeScaleVars['--text-supporting-size'],
|
|
263
|
+
lineHeight: typeScaleVars['--text-supporting-leading'],
|
|
264
|
+
color: colorVars['--color-text-secondary'],
|
|
265
|
+
userSelect: 'none',
|
|
271
266
|
},
|
|
272
267
|
|
|
273
268
|
// Divider
|
|
@@ -301,6 +296,19 @@ const styles = stylex.create({
|
|
|
301
296
|
flex: 1,
|
|
302
297
|
minWidth: 0,
|
|
303
298
|
},
|
|
299
|
+
// The mark's column, reserved on every row and at either position, so a row
|
|
300
|
+
// occupies the same geometry whether or not it is the chosen one — the
|
|
301
|
+
// default check draws nothing when unchecked, and without the column a list
|
|
302
|
+
// would indent (or truncate) its chosen row differently from the rest.
|
|
303
|
+
// `minWidth` rather than `width`: a theme can replace `check` with a larger
|
|
304
|
+
// indicator (a radio is 20px at `sm`), and the column has to grow with it.
|
|
305
|
+
itemMarkColumn: {
|
|
306
|
+
display: 'inline-flex',
|
|
307
|
+
alignItems: 'center',
|
|
308
|
+
justifyContent: 'center',
|
|
309
|
+
flexShrink: 0,
|
|
310
|
+
minWidth: '1rem',
|
|
311
|
+
},
|
|
304
312
|
itemCheckmark: {
|
|
305
313
|
flexShrink: 0,
|
|
306
314
|
width: 16,
|
|
@@ -518,6 +526,16 @@ interface SelectorPropsBase<
|
|
|
518
526
|
*/
|
|
519
527
|
renderOption?: (option: SelectorOptionData) => ReactNode;
|
|
520
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Which edge of the option row carries the selected mark. `start` reserves a
|
|
531
|
+
* mark column ahead of every label so they stay aligned, the way a native
|
|
532
|
+
* menu does; `end` is the house convention shared with Typeahead and
|
|
533
|
+
* CommandPalette.
|
|
534
|
+
*
|
|
535
|
+
* @default 'end'
|
|
536
|
+
*/
|
|
537
|
+
indicatorPosition?: IndicatorPosition;
|
|
538
|
+
|
|
521
539
|
/**
|
|
522
540
|
* Whether to show a search input for filtering options.
|
|
523
541
|
* @default false
|
|
@@ -668,6 +686,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
668
686
|
startIcon,
|
|
669
687
|
htmlName,
|
|
670
688
|
renderOption,
|
|
689
|
+
indicatorPosition = 'end',
|
|
671
690
|
hasSearch = false,
|
|
672
691
|
searchPlaceholder: searchPlaceholderFromProps,
|
|
673
692
|
placement,
|
|
@@ -706,7 +725,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
706
725
|
const inputGroup = useInputGroup();
|
|
707
726
|
|
|
708
727
|
const [searchQuery, setSearchQuery] = useState('');
|
|
709
|
-
// A typed query shows
|
|
728
|
+
// A typed query shows the search row's clear (✕) button, which becomes
|
|
710
729
|
// the next tab stop after the search input.
|
|
711
730
|
const hasQuery = searchQuery.length > 0;
|
|
712
731
|
|
|
@@ -797,6 +816,9 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
797
816
|
// The popup's own role="listbox" is the exposed semantics; the trigger
|
|
798
817
|
// keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
|
|
799
818
|
role: 'none',
|
|
819
|
+
// The theme target belongs on the SURFACE that paints the popup, which
|
|
820
|
+
// `usePopover` owns — not on the scrolling list inside it.
|
|
821
|
+
surfaceTarget: 'selector-popup',
|
|
800
822
|
});
|
|
801
823
|
|
|
802
824
|
// Open dropdown on mount when isDefaultOpen is true
|
|
@@ -988,11 +1010,40 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
988
1010
|
return null;
|
|
989
1011
|
}
|
|
990
1012
|
return (
|
|
991
|
-
<
|
|
992
|
-
{
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1013
|
+
<PanelSearchInput
|
|
1014
|
+
ref={searchRef}
|
|
1015
|
+
id={searchId}
|
|
1016
|
+
// The search row is the panel's header: a magnifier, a borderless
|
|
1017
|
+
// input, and the shared clear (✕) button. It deliberately does NOT
|
|
1018
|
+
// render a bordered TextInput — the popup is already a bordered
|
|
1019
|
+
// surface, and a field inside it drew a second box within that box.
|
|
1020
|
+
label={t('@astryx.selector.searchOptions')}
|
|
1021
|
+
// Same accessible name the TextInput's built-in clear produced
|
|
1022
|
+
// ("Clear Search options"), so the affordance keeps its name while its
|
|
1023
|
+
// chrome changes.
|
|
1024
|
+
clearLabel={t('@astryx.textInput.clearLabel', {
|
|
1025
|
+
label: t('@astryx.selector.searchOptions'),
|
|
1026
|
+
})}
|
|
1027
|
+
{...themeProps('selector-search')}
|
|
1028
|
+
xstyle={variant !== 'ghost' && styles.searchRowInput}
|
|
1029
|
+
// When hasSearch is set, focus moves into this input on open, so it —
|
|
1030
|
+
// not the trigger — must be the combobox that reports the highlighted
|
|
1031
|
+
// option via aria-activedescendant (comboboxes-4). A bare searchbox
|
|
1032
|
+
// left the highlight silent to screen readers.
|
|
1033
|
+
role="combobox"
|
|
1034
|
+
aria-expanded={popover.isOpen}
|
|
1035
|
+
aria-controls={listboxId}
|
|
1036
|
+
aria-autocomplete="list"
|
|
1037
|
+
aria-activedescendant={
|
|
1038
|
+
popover.isOpen && highlightedIndex >= 0
|
|
1039
|
+
? getItemId(highlightedIndex)
|
|
1040
|
+
: undefined
|
|
1041
|
+
}
|
|
1042
|
+
value={searchQuery}
|
|
1043
|
+
onValueChange={handleSearchChange}
|
|
1044
|
+
onContainerKeyDown={e => {
|
|
1045
|
+
// The clear (✕) button lives inside the row, after the input in DOM
|
|
1046
|
+
// order. When it is focused and the user tabs forward there is
|
|
996
1047
|
// nothing else in the popup, so dismiss it (Shift+Tab returns to the
|
|
997
1048
|
// input natively). Key events originating on the input are handled on
|
|
998
1049
|
// the input below; ignore them here so we don't double-dismiss.
|
|
@@ -1002,65 +1053,32 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1002
1053
|
if (e.key === 'Tab' && !e.shiftKey) {
|
|
1003
1054
|
onKeyDown(e);
|
|
1004
1055
|
}
|
|
1005
|
-
}}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
width="100%"
|
|
1022
|
-
// When hasSearch is set, focus moves into this input on open, so it —
|
|
1023
|
-
// not the trigger — must be the combobox that reports the highlighted
|
|
1024
|
-
// option via aria-activedescendant (comboboxes-4). A bare searchbox
|
|
1025
|
-
// left the highlight silent to screen readers. role + aria-* pass
|
|
1026
|
-
// through to the underlying <input> via BaseProps.
|
|
1027
|
-
role="combobox"
|
|
1028
|
-
aria-expanded={popover.isOpen}
|
|
1029
|
-
aria-controls={listboxId}
|
|
1030
|
-
aria-autocomplete="list"
|
|
1031
|
-
aria-activedescendant={
|
|
1032
|
-
popover.isOpen && highlightedIndex >= 0
|
|
1033
|
-
? getItemId(highlightedIndex)
|
|
1034
|
-
: undefined
|
|
1056
|
+
}}
|
|
1057
|
+
onKeyDown={e => {
|
|
1058
|
+
// Arrow keys navigate options; Enter selects; Escape closes.
|
|
1059
|
+
// Home/End are left to the input for caret movement (APG editable
|
|
1060
|
+
// combobox); PageUp/PageDown are the sanctioned substitute for
|
|
1061
|
+
// jumping to the first/last option.
|
|
1062
|
+
if (
|
|
1063
|
+
e.key === 'ArrowDown' ||
|
|
1064
|
+
e.key === 'ArrowUp' ||
|
|
1065
|
+
e.key === 'PageUp' ||
|
|
1066
|
+
e.key === 'PageDown' ||
|
|
1067
|
+
e.key === 'Enter' ||
|
|
1068
|
+
e.key === 'Escape'
|
|
1069
|
+
) {
|
|
1070
|
+
onKeyDown(e);
|
|
1071
|
+
return;
|
|
1035
1072
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
e.key === 'ArrowUp' ||
|
|
1046
|
-
e.key === 'PageUp' ||
|
|
1047
|
-
e.key === 'PageDown' ||
|
|
1048
|
-
e.key === 'Enter' ||
|
|
1049
|
-
e.key === 'Escape'
|
|
1050
|
-
) {
|
|
1051
|
-
onKeyDown(e);
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
// Tab: when a query is showing the clear (✕) button, forward-tab
|
|
1055
|
-
// moves focus to it (keeping the popup open) so the affordance is
|
|
1056
|
-
// keyboard-reachable. Every other Tab dismisses the popup as usual.
|
|
1057
|
-
if (e.key === 'Tab' && (e.shiftKey || !hasQuery)) {
|
|
1058
|
-
onKeyDown(e);
|
|
1059
|
-
}
|
|
1060
|
-
}}
|
|
1061
|
-
placeholder={searchPlaceholder}
|
|
1062
|
-
/>
|
|
1063
|
-
</div>
|
|
1073
|
+
// Tab: when a query is showing the clear (✕) button, forward-tab
|
|
1074
|
+
// moves focus to it (keeping the popup open) so the affordance is
|
|
1075
|
+
// keyboard-reachable. Every other Tab dismisses the popup as usual.
|
|
1076
|
+
if (e.key === 'Tab' && (e.shiftKey || !hasQuery)) {
|
|
1077
|
+
onKeyDown(e);
|
|
1078
|
+
}
|
|
1079
|
+
}}
|
|
1080
|
+
placeholder={searchPlaceholder}
|
|
1081
|
+
/>
|
|
1064
1082
|
);
|
|
1065
1083
|
}, [
|
|
1066
1084
|
hasSearch,
|
|
@@ -1074,6 +1092,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1074
1092
|
popover.isOpen,
|
|
1075
1093
|
highlightedIndex,
|
|
1076
1094
|
getItemId,
|
|
1095
|
+
variant,
|
|
1077
1096
|
t,
|
|
1078
1097
|
]);
|
|
1079
1098
|
|
|
@@ -1088,6 +1107,45 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1088
1107
|
const isHighlighted = flatIndex === highlightedIndex;
|
|
1089
1108
|
const isSelected = item.value === normalizedValue;
|
|
1090
1109
|
|
|
1110
|
+
/*
|
|
1111
|
+
* Rendered UNCONDITIONALLY, with the state passed down: the default
|
|
1112
|
+
* check draws nothing when unchecked, but a theme that replaces the
|
|
1113
|
+
* `check` indicator with a radio needs the unselected state to draw
|
|
1114
|
+
* its empty circle. `{isSelected && …}` would make that impossible.
|
|
1115
|
+
*
|
|
1116
|
+
* `selector-check` stays the stable target for the mark's position
|
|
1117
|
+
* in the row; the indicator owns what the mark looks like.
|
|
1118
|
+
*/
|
|
1119
|
+
const mark = (
|
|
1120
|
+
<span {...stylex.props(styles.itemMarkColumn)}>
|
|
1121
|
+
<SelectionMark
|
|
1122
|
+
state={isSelected ? 'checked' : 'unchecked'}
|
|
1123
|
+
size="sm"
|
|
1124
|
+
isDisabled={item.disabled ?? false}
|
|
1125
|
+
{...themeProps('selector-check')}
|
|
1126
|
+
/>
|
|
1127
|
+
</span>
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
const optionContent = (
|
|
1131
|
+
<span {...stylex.props(styles.itemContent)}>
|
|
1132
|
+
{renderOption ? renderOption(item) : <DefaultOption option={item} />}
|
|
1133
|
+
</span>
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
const content =
|
|
1137
|
+
indicatorPosition === 'start' ? (
|
|
1138
|
+
<>
|
|
1139
|
+
{mark}
|
|
1140
|
+
{optionContent}
|
|
1141
|
+
</>
|
|
1142
|
+
) : (
|
|
1143
|
+
<>
|
|
1144
|
+
{optionContent}
|
|
1145
|
+
{mark}
|
|
1146
|
+
</>
|
|
1147
|
+
);
|
|
1148
|
+
|
|
1091
1149
|
return (
|
|
1092
1150
|
<div
|
|
1093
1151
|
key={item.value}
|
|
@@ -1104,33 +1162,13 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1104
1162
|
isSelected && styles.itemSelected,
|
|
1105
1163
|
item.disabled && styles.itemDisabled,
|
|
1106
1164
|
)}>
|
|
1107
|
-
|
|
1108
|
-
{renderOption ? (
|
|
1109
|
-
renderOption(item)
|
|
1110
|
-
) : (
|
|
1111
|
-
<DefaultOption option={item} />
|
|
1112
|
-
)}
|
|
1113
|
-
</span>
|
|
1114
|
-
{/*
|
|
1115
|
-
* Rendered UNCONDITIONALLY, with the state passed down: the default
|
|
1116
|
-
* check draws nothing when unchecked, but a theme that replaces the
|
|
1117
|
-
* `check` indicator with a radio needs the unselected state to draw
|
|
1118
|
-
* its empty circle. `{isSelected && …}` would make that impossible.
|
|
1119
|
-
*
|
|
1120
|
-
* `selector-check` stays the stable target for the mark's position
|
|
1121
|
-
* in the row; the indicator owns what the mark looks like.
|
|
1122
|
-
*/}
|
|
1123
|
-
<SelectionMark
|
|
1124
|
-
state={isSelected ? 'checked' : 'unchecked'}
|
|
1125
|
-
size="sm"
|
|
1126
|
-
isDisabled={item.disabled ?? false}
|
|
1127
|
-
{...themeProps('selector-check')}
|
|
1128
|
-
/>
|
|
1165
|
+
{content}
|
|
1129
1166
|
</div>
|
|
1130
1167
|
);
|
|
1131
1168
|
},
|
|
1132
1169
|
[
|
|
1133
1170
|
renderOption,
|
|
1171
|
+
indicatorPosition,
|
|
1134
1172
|
highlightedIndex,
|
|
1135
1173
|
size,
|
|
1136
1174
|
normalizedValue,
|
|
@@ -1192,17 +1230,23 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1192
1230
|
if (sectionItems.length === 0) {
|
|
1193
1231
|
continue;
|
|
1194
1232
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
xstyle={styles.sectionDivider}
|
|
1201
|
-
/>,
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1233
|
+
// The heading lives INSIDE the group and is aria-hidden: the group
|
|
1234
|
+
// already carries the title as its accessible name, so exposing the
|
|
1235
|
+
// text again would announce it twice. This also keeps role="listbox"'s
|
|
1236
|
+
// children to option/group only — the old labeled Divider sat in the
|
|
1237
|
+
// listbox as a stray role="separator".
|
|
1204
1238
|
elements.push(
|
|
1205
1239
|
<div key={`section-${i}`} role="group" aria-label={option.title}>
|
|
1240
|
+
{option.title && (
|
|
1241
|
+
<div
|
|
1242
|
+
aria-hidden="true"
|
|
1243
|
+
{...mergeProps(
|
|
1244
|
+
themeProps('selector-section-heading'),
|
|
1245
|
+
stylex.props(styles.sectionHeading),
|
|
1246
|
+
)}>
|
|
1247
|
+
{option.title}
|
|
1248
|
+
</div>
|
|
1249
|
+
)}
|
|
1206
1250
|
{sectionItems}
|
|
1207
1251
|
</div>,
|
|
1208
1252
|
);
|
|
@@ -1251,6 +1295,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1251
1295
|
styles.triggerContainer,
|
|
1252
1296
|
sizeStyles[size],
|
|
1253
1297
|
variant === 'ghost' && styles.triggerGhost,
|
|
1298
|
+
variant === 'ghost' && focusOutlineStyles.focusWithin,
|
|
1254
1299
|
isDisabled && inputWrapperStyles.disabled,
|
|
1255
1300
|
variant === 'ghost' && isDisabled && styles.triggerGhostDisabled,
|
|
1256
1301
|
!selectedItem && styles.triggerPlaceholder,
|
|
@@ -1338,7 +1383,10 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1338
1383
|
aria-label={t(STATUS_BUTTON_LABEL_KEY[status.type])}
|
|
1339
1384
|
aria-describedby={statusTooltip.describedBy}
|
|
1340
1385
|
onClick={e => e.stopPropagation()}
|
|
1341
|
-
{...stylex.props(
|
|
1386
|
+
{...stylex.props(
|
|
1387
|
+
focusOutlineStyles.focusVisible,
|
|
1388
|
+
styles.statusButton,
|
|
1389
|
+
)}>
|
|
1342
1390
|
<Icon
|
|
1343
1391
|
icon={STATUS_ICON_MAP[status.type]}
|
|
1344
1392
|
size="sm"
|
|
@@ -1383,6 +1431,12 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1383
1431
|
hasSearch ? (
|
|
1384
1432
|
<div>
|
|
1385
1433
|
{renderSearch()}
|
|
1434
|
+
{/*
|
|
1435
|
+
Separates the header from the options and spans the panel: the
|
|
1436
|
+
search row and the listbox each hold their own inline padding,
|
|
1437
|
+
the line does not, so it reads as the panel's own edge.
|
|
1438
|
+
*/}
|
|
1439
|
+
<Divider />
|
|
1386
1440
|
<div
|
|
1387
1441
|
ref={listboxRef}
|
|
1388
1442
|
id={listboxId}
|
|
@@ -1412,6 +1466,12 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1412
1466
|
{
|
|
1413
1467
|
placement: popoverPlacement,
|
|
1414
1468
|
alignment: 'start',
|
|
1469
|
+
// The system's standard menu clearance, except in overlay mode:
|
|
1470
|
+
// there the measured negative margin owns the block geometry and
|
|
1471
|
+
// the menu is meant to sit on the trigger, not clear it.
|
|
1472
|
+
offset: shouldOverlaySelectedItem
|
|
1473
|
+
? undefined
|
|
1474
|
+
: spacingVars['--spacing-1'],
|
|
1415
1475
|
xstyle: [styles.popover, layerAnimations[popoverPlacement]],
|
|
1416
1476
|
style: popoverOffsetStyle,
|
|
1417
1477
|
},
|
|
@@ -21,7 +21,7 @@ export const docs = {
|
|
|
21
21
|
targets: [
|
|
22
22
|
{className: 'astryx-side-nav', visualProps: ['mode']},
|
|
23
23
|
{className: 'astryx-side-nav-heading'},
|
|
24
|
-
{className: 'astryx-side-nav-item', visualProps: ['size'], states: ['selected']},
|
|
24
|
+
{className: 'astryx-side-nav-item', visualProps: ['size'], states: ['selected', 'disabled']},
|
|
25
25
|
{className: 'astryx-side-nav-section'},
|
|
26
26
|
],
|
|
27
27
|
},
|
|
@@ -85,7 +85,7 @@ export const docs = {
|
|
|
85
85
|
{
|
|
86
86
|
name: 'footerIcons',
|
|
87
87
|
type: 'ReactNode',
|
|
88
|
-
description:
|
|
88
|
+
description: "Footer icon bar. The row cascades a 'sm' size to the interactive children it contains, so its icons and the built-in collapse button come out one height; pass an explicit size on a child to opt out.",
|
|
89
89
|
slotElements: [
|
|
90
90
|
{
|
|
91
91
|
__element: 'Icon',
|
|
@@ -99,7 +99,7 @@ export const docs = {
|
|
|
99
99
|
{
|
|
100
100
|
name: 'collapsible',
|
|
101
101
|
type: 'boolean | { defaultIsCollapsed?: boolean; isCollapsed?: boolean; onCollapsedChange?: (isCollapsed: boolean) => void; hasButton?: boolean; buttonLabel?: string }',
|
|
102
|
-
description: 'Enables collapse behavior. true for uncontrolled with default toggle button, or an object for controlled mode and advanced config (defaultIsCollapsed, isCollapsed + onCollapsedChange, hasButton, buttonLabel).',
|
|
102
|
+
description: 'Enables collapse behavior. true for uncontrolled with default toggle button, or an object for controlled mode and advanced config (defaultIsCollapsed, isCollapsed + onCollapsedChange, hasButton, buttonLabel). A controlled config can also be passed to a SideNavCollapseButton rendered outside this SideNav, so both share one state.',
|
|
103
103
|
default: 'false',
|
|
104
104
|
},
|
|
105
105
|
{
|
|
@@ -111,7 +111,7 @@ export const docs = {
|
|
|
111
111
|
{
|
|
112
112
|
name: 'handleRef',
|
|
113
113
|
type: 'Ref<SideNavImperativeCollapseHandle>',
|
|
114
|
-
description: 'Imperative collapse handle for SideNavCollapseButton instances rendered outside this SideNav. Separate from `ref`, which continues to expose the root HTMLElement.',
|
|
114
|
+
description: 'Deprecated. Imperative collapse handle for SideNavCollapseButton instances rendered outside this SideNav; hand both the same controlled collapsible config instead. Separate from `ref`, which continues to expose the root HTMLElement.',
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
name: 'xstyle',
|
|
@@ -131,6 +131,9 @@ export const docs = {
|
|
|
131
131
|
bestPractices: [
|
|
132
132
|
{guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
|
|
133
133
|
{guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
|
|
134
|
+
{guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
|
|
135
|
+
{guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
|
|
136
|
+
{guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
|
|
134
137
|
{guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
|
|
135
138
|
{guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
|
|
136
139
|
],
|
|
@@ -150,6 +153,9 @@ export const docsZh = {
|
|
|
150
153
|
bestPractices: [
|
|
151
154
|
{guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
|
|
152
155
|
{guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
|
|
156
|
+
{guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
|
|
157
|
+
{guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
|
|
158
|
+
{guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
|
|
153
159
|
{guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
|
|
154
160
|
{guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
|
|
155
161
|
],
|
|
@@ -169,6 +175,9 @@ export const docsDense = {
|
|
|
169
175
|
bestPractices: [
|
|
170
176
|
{guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
|
|
171
177
|
{guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
|
|
178
|
+
{guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
|
|
179
|
+
{guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
|
|
180
|
+
{guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
|
|
172
181
|
{guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
|
|
173
182
|
{guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
|
|
174
183
|
],
|