@astryxdesign/core 0.1.4-canary.39ffd21 → 0.1.4-canary.3d64ef2
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/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +8 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +31 -11
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +15 -7
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +1 -0
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +8 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +26 -3
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +23 -5
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +101 -13
- package/dist/Collapsible/CollapsibleGroup.d.ts +39 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +70 -9
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +31 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +33 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +2 -0
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +2 -0
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +26 -9
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/Item/Item.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +26 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +32 -16
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +87 -36
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +39 -21
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +2 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +10 -2
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +6 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +34 -7
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +14 -3
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +5 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +11 -3
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +12 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -2
- package/dist/astryx.css +7 -0
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +1 -1
- package/src/Avatar/Avatar.doc.mjs +11 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +112 -4
- package/src/Calendar/Calendar.tsx +26 -5
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +1 -0
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
- package/src/CheckboxInput/CheckboxInput.tsx +2 -0
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +87 -3
- package/src/CodeBlock/CodeBlock.tsx +43 -3
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.tsx +86 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +288 -0
- package/src/Collapsible/CollapsibleGroup.tsx +107 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +42 -2
- package/src/Collapsible/index.ts +4 -0
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +15 -0
- package/src/ContextMenu/ContextMenu.tsx +2 -0
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +70 -0
- package/src/DateTimeInput/DateTimeInput.tsx +2 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/Item/Item.tsx +1 -1
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Pagination/Pagination.test.tsx +143 -0
- package/src/Pagination/Pagination.tsx +75 -23
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.test.tsx +10 -0
- package/src/ProgressBar/ProgressBar.tsx +2 -1
- package/src/Resizable/ResizeHandle.test.tsx +180 -0
- package/src/Resizable/ResizeHandle.tsx +10 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +20 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +13 -2
- package/src/Table/Table.test.tsx +51 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +20 -0
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
|
@@ -214,6 +214,21 @@ describe('DateTimeInput', () => {
|
|
|
214
214
|
expect(screen.getByRole('combobox')).not.toHaveAttribute('aria-busy');
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
+
it('sets aria-busy on the time input when isLoading is true', () => {
|
|
218
|
+
render(<DateTimeInput label="Meeting" isLoading onChange={() => {}} />);
|
|
219
|
+
expect(screen.getByLabelText('Meeting time')).toHaveAttribute(
|
|
220
|
+
'aria-busy',
|
|
221
|
+
'true',
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('does not set aria-busy on the time input when not loading', () => {
|
|
226
|
+
render(<DateTimeInput label="Meeting" onChange={() => {}} />);
|
|
227
|
+
expect(screen.getByLabelText('Meeting time')).not.toHaveAttribute(
|
|
228
|
+
'aria-busy',
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
217
232
|
it('renders status icon for error status', () => {
|
|
218
233
|
render(
|
|
219
234
|
<DateTimeInput
|
|
@@ -270,6 +285,61 @@ describe('DateTimeInput', () => {
|
|
|
270
285
|
expect(found).toBe(true);
|
|
271
286
|
});
|
|
272
287
|
|
|
288
|
+
it('links the description to the time input via aria-describedby', () => {
|
|
289
|
+
render(
|
|
290
|
+
<DateTimeInput
|
|
291
|
+
label="Meeting"
|
|
292
|
+
description="Pick the meeting datetime"
|
|
293
|
+
onChange={() => {}}
|
|
294
|
+
/>,
|
|
295
|
+
);
|
|
296
|
+
// The description covers both halves of the field, so the time input must
|
|
297
|
+
// carry it too — a screen-reader user tabbing into the time half should
|
|
298
|
+
// not lose the field's description.
|
|
299
|
+
const timeInput = screen.getByLabelText('Meeting time');
|
|
300
|
+
const describedBy = timeInput.getAttribute('aria-describedby')!;
|
|
301
|
+
const ids = describedBy.split(' ');
|
|
302
|
+
const found = ids.some(id =>
|
|
303
|
+
document
|
|
304
|
+
.getElementById(id)
|
|
305
|
+
?.textContent?.includes('Pick the meeting datetime'),
|
|
306
|
+
);
|
|
307
|
+
expect(found).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('links the status message to the time input via aria-describedby', () => {
|
|
311
|
+
render(
|
|
312
|
+
<DateTimeInput
|
|
313
|
+
label="Meeting"
|
|
314
|
+
onChange={() => {}}
|
|
315
|
+
status={{type: 'error', message: 'Invalid datetime'}}
|
|
316
|
+
/>,
|
|
317
|
+
);
|
|
318
|
+
const timeInput = screen.getByLabelText('Meeting time');
|
|
319
|
+
const describedBy = timeInput.getAttribute('aria-describedby')!;
|
|
320
|
+
const ids = describedBy.split(' ');
|
|
321
|
+
const found = ids.some(id =>
|
|
322
|
+
document.getElementById(id)?.textContent?.includes('Invalid datetime'),
|
|
323
|
+
);
|
|
324
|
+
expect(found).toBe(true);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('links the disabled reason to the time input via aria-describedby', () => {
|
|
328
|
+
HTMLElement.prototype.showPopover = vi.fn();
|
|
329
|
+
HTMLElement.prototype.hidePopover = vi.fn();
|
|
330
|
+
render(
|
|
331
|
+
<DateTimeInput
|
|
332
|
+
label="When"
|
|
333
|
+
onChange={() => {}}
|
|
334
|
+
isDisabled
|
|
335
|
+
disabledMessage="You need the Editor role"
|
|
336
|
+
/>,
|
|
337
|
+
);
|
|
338
|
+
const timeInput = screen.getByLabelText('When time');
|
|
339
|
+
const tooltip = screen.getByRole('tooltip', {hidden: true});
|
|
340
|
+
expect(timeInput.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
341
|
+
});
|
|
342
|
+
|
|
273
343
|
it('handles Escape keydown on date input without error', () => {
|
|
274
344
|
render(<DateTimeInput label="Meeting" onChange={() => {}} />);
|
|
275
345
|
const input = screen.getByRole('combobox');
|
|
@@ -987,10 +987,12 @@ export function DateTimeInput({
|
|
|
987
987
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
988
988
|
readOnly={showsDisabledMessage || undefined}
|
|
989
989
|
aria-label={timeLabel ?? `${label} time`}
|
|
990
|
+
aria-describedby={ariaDescribedBy}
|
|
990
991
|
aria-required={isRequired === true ? 'true' : undefined}
|
|
991
992
|
aria-invalid={
|
|
992
993
|
status?.type === 'error' || !isTimeInputValid ? 'true' : undefined
|
|
993
994
|
}
|
|
995
|
+
aria-busy={isBusy || undefined}
|
|
994
996
|
{...stylex.props(
|
|
995
997
|
styles.input,
|
|
996
998
|
isEffectivelyDisabled && styles.inputDisabled,
|
|
@@ -65,6 +65,51 @@ export const docs = {
|
|
|
65
65
|
default: 'true',
|
|
66
66
|
},
|
|
67
67
|
],
|
|
68
|
+
playground: {
|
|
69
|
+
defaults: {
|
|
70
|
+
title: 'Delete file?',
|
|
71
|
+
subtitle: 'This action cannot be undone.',
|
|
72
|
+
hasDivider: true,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
examples: [
|
|
76
|
+
{
|
|
77
|
+
label: 'Basic',
|
|
78
|
+
code: `
|
|
79
|
+
import {DialogHeader} from '@astryxdesign/core/Dialog';
|
|
80
|
+
|
|
81
|
+
<DialogHeader title="Delete file?" subtitle="This action cannot be undone." />;
|
|
82
|
+
`,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'With close button',
|
|
86
|
+
code: `
|
|
87
|
+
import {useState} from 'react';
|
|
88
|
+
import {DialogHeader} from '@astryxdesign/core/Dialog';
|
|
89
|
+
|
|
90
|
+
function Header() {
|
|
91
|
+
const [, setIsOpen] = useState(true);
|
|
92
|
+
|
|
93
|
+
// Passing onOpenChange renders a close button that calls it with false.
|
|
94
|
+
return <DialogHeader title="Settings" onOpenChange={setIsOpen} />;
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'With start and end content',
|
|
100
|
+
code: `
|
|
101
|
+
import {DialogHeader} from '@astryxdesign/core/Dialog';
|
|
102
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
103
|
+
import {Badge} from '@astryxdesign/core/Badge';
|
|
104
|
+
|
|
105
|
+
<DialogHeader
|
|
106
|
+
title="Notifications"
|
|
107
|
+
startContent={<Icon icon="chevronLeft" size="sm" />}
|
|
108
|
+
endContent={<Badge label="3" />}
|
|
109
|
+
/>;
|
|
110
|
+
`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
68
113
|
};
|
|
69
114
|
|
|
70
115
|
export const docsZh = {
|
|
@@ -11,7 +11,7 @@ export const docs = {
|
|
|
11
11
|
theming: {
|
|
12
12
|
targets: [
|
|
13
13
|
{className: 'astryx-dropdown-menu'},
|
|
14
|
-
{className: 'astryx-dropdown-menu-item'},
|
|
14
|
+
{className: 'astryx-dropdown-menu-item', visualProps: ['size']},
|
|
15
15
|
],
|
|
16
16
|
vars: [
|
|
17
17
|
{name: '--_dropdown-menu-radius', description: 'Border radius of the menu popup', default: 'var(--radius-element)', private: true},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen} from '@testing-library/react';
|
|
14
|
-
import {
|
|
14
|
+
import {TestIcon} from '../__tests__/TestIcon';
|
|
15
15
|
import {FieldLabel} from './FieldLabel';
|
|
16
16
|
|
|
17
17
|
describe('FieldLabel', () => {
|
|
@@ -49,7 +49,7 @@ describe('FieldLabel', () => {
|
|
|
49
49
|
<FieldLabel
|
|
50
50
|
label="Starred"
|
|
51
51
|
inputID="starred-input"
|
|
52
|
-
labelIcon={
|
|
52
|
+
labelIcon={TestIcon}
|
|
53
53
|
/>,
|
|
54
54
|
);
|
|
55
55
|
const svg = document.querySelector('svg');
|
|
@@ -4,7 +4,6 @@ exports[`FormLayout > matches snapshot for horizontal direction 1`] = `
|
|
|
4
4
|
<div
|
|
5
5
|
class="astryx-form-layout horizontal FormLayout__styles.base xdt5ytf x18g69wz FormLayout__styles.horizontal xrvj5dj x1mt1orb xu6a5m6"
|
|
6
6
|
data-direction="horizontal"
|
|
7
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40; @astryxdesign/core:src/FormLayout/FormLayout.tsx:45"
|
|
8
7
|
data-testid="layout"
|
|
9
8
|
>
|
|
10
9
|
<input
|
|
@@ -20,7 +19,6 @@ exports[`FormLayout > matches snapshot for horizontal-labels direction 1`] = `
|
|
|
20
19
|
<div
|
|
21
20
|
class="astryx-form-layout horizontal-labels FormLayout__styles.base xdt5ytf FormLayout__styles.horizontalLabels xrvj5dj x1pmbctz xlaq8a2 x7a106z xedohl4 x1rpgqan x1a1jff"
|
|
22
21
|
data-direction="horizontal-labels"
|
|
23
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40; @astryxdesign/core:src/FormLayout/FormLayout.tsx:50"
|
|
24
22
|
data-testid="layout"
|
|
25
23
|
>
|
|
26
24
|
<label>
|
|
@@ -36,7 +34,6 @@ exports[`FormLayout > matches snapshot for vertical direction 1`] = `
|
|
|
36
34
|
<div
|
|
37
35
|
class="astryx-form-layout vertical FormLayout__styles.base x78zum5 xdt5ytf x18g69wz"
|
|
38
36
|
data-direction="vertical"
|
|
39
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40"
|
|
40
37
|
data-testid="layout"
|
|
41
38
|
>
|
|
42
39
|
<input
|
package/src/Grid/Grid.test.tsx
CHANGED
|
@@ -65,7 +65,7 @@ describe('Grid', () => {
|
|
|
65
65
|
expect(templateColumns(grid)).toBe('repeat(auto-fill, minmax(250px, 1fr))');
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('renders with columns object max (capped
|
|
68
|
+
it('renders with columns object max (count capped, tracks still fill)', () => {
|
|
69
69
|
render(
|
|
70
70
|
<Grid columns={{minWidth: 250, max: 3}} gap={4} data-testid="grid">
|
|
71
71
|
<div>Item 1</div>
|
|
@@ -74,13 +74,37 @@ describe('Grid', () => {
|
|
|
74
74
|
</Grid>,
|
|
75
75
|
);
|
|
76
76
|
const grid = screen.getByTestId('grid');
|
|
77
|
-
//
|
|
77
|
+
// Cap lives on the track MIN (min(100%, max(minWidth, perColumn))); the
|
|
78
|
+
// track MAX stays 1fr so present columns fill the row (a lone column on
|
|
79
|
+
// mobile stretches to 100% instead of leaving dead space).
|
|
78
80
|
expect(templateColumns(grid)).toBe(
|
|
79
|
-
'repeat(auto-fill, minmax(250px, calc((100% - 2 * var(--spacing-4)) / 3)))',
|
|
81
|
+
'repeat(auto-fill, minmax(min(100%, max(250px, calc((100% - 2 * var(--spacing-4)) / 3))), 1fr))',
|
|
80
82
|
);
|
|
81
83
|
expect(grid.style.maxWidth).toBe('');
|
|
82
84
|
});
|
|
83
85
|
|
|
86
|
+
it('keeps track max at 1fr with a max cap so a lone column can fill (#3391)', () => {
|
|
87
|
+
// Regression: previously the cap was applied to the track MAX
|
|
88
|
+
// (minmax(minWidth, 100%/max)), so when fewer than `max` columns fit —
|
|
89
|
+
// e.g. a single column on mobile — the lone column was pinned to ~100%/max
|
|
90
|
+
// and left dead space on the right. The cap now lives on the track MIN and
|
|
91
|
+
// the track MAX stays 1fr, so present columns always stretch to fill.
|
|
92
|
+
render(
|
|
93
|
+
<Grid columns={{minWidth: 360, max: 2}} gap={4} data-testid="grid">
|
|
94
|
+
<div>Left</div>
|
|
95
|
+
<div>Right</div>
|
|
96
|
+
</Grid>,
|
|
97
|
+
);
|
|
98
|
+
const grid = screen.getByTestId('grid');
|
|
99
|
+
const template = templateColumns(grid);
|
|
100
|
+
// Track max must be 1fr (fills), not a fraction-of-container cap.
|
|
101
|
+
expect(template).toMatch(/, 1fr\)\)$/);
|
|
102
|
+
expect(template).not.toMatch(/, calc\([^)]*\/ 2\)\)\)$/);
|
|
103
|
+
expect(template).toBe(
|
|
104
|
+
'repeat(auto-fill, minmax(min(100%, max(360px, calc((100% - 1 * var(--spacing-4)) / 2))), 1fr))',
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
84
108
|
it('renders with columns object max using columnGap', () => {
|
|
85
109
|
render(
|
|
86
110
|
<Grid columns={{minWidth: 200, max: 4}} columnGap={6} data-testid="grid">
|
|
@@ -89,9 +113,9 @@ describe('Grid', () => {
|
|
|
89
113
|
</Grid>,
|
|
90
114
|
);
|
|
91
115
|
const grid = screen.getByTestId('grid');
|
|
92
|
-
// columnGap takes precedence
|
|
116
|
+
// columnGap takes precedence in the perColumn floor calculation
|
|
93
117
|
expect(templateColumns(grid)).toBe(
|
|
94
|
-
'repeat(auto-fill, minmax(200px, calc((100% - 3 * var(--spacing-6)) / 4)))',
|
|
118
|
+
'repeat(auto-fill, minmax(min(100%, max(200px, calc((100% - 3 * var(--spacing-6)) / 4))), 1fr))',
|
|
95
119
|
);
|
|
96
120
|
expect(grid.style.maxWidth).toBe('');
|
|
97
121
|
});
|
|
@@ -165,7 +189,7 @@ describe('Grid', () => {
|
|
|
165
189
|
expect(templateColumns(grid)).toBe('1fr');
|
|
166
190
|
});
|
|
167
191
|
|
|
168
|
-
it('uses auto-fill
|
|
192
|
+
it('uses auto-fill with a plain 1fr track when no max specified', () => {
|
|
169
193
|
render(
|
|
170
194
|
<Grid columns={{minWidth: 200}} data-testid="grid">
|
|
171
195
|
<div>Item</div>
|
|
@@ -220,7 +244,7 @@ describe('Grid', () => {
|
|
|
220
244
|
|
|
221
245
|
// --- P2: columns object + columnGap interaction (hardening #719) ---
|
|
222
246
|
|
|
223
|
-
it('uses columnGap var in
|
|
247
|
+
it('uses columnGap var in the count-cap floor when both columnGap and gap are set', () => {
|
|
224
248
|
render(
|
|
225
249
|
<Grid
|
|
226
250
|
columns={{minWidth: 200, max: 3}}
|
|
@@ -231,14 +255,14 @@ describe('Grid', () => {
|
|
|
231
255
|
</Grid>,
|
|
232
256
|
);
|
|
233
257
|
const grid = screen.getByTestId('grid');
|
|
234
|
-
// columnGap takes precedence over gap in
|
|
258
|
+
// columnGap takes precedence over gap in the perColumn floor
|
|
235
259
|
expect(templateColumns(grid)).toBe(
|
|
236
|
-
'repeat(auto-fill, minmax(200px, calc((100% - 2 * var(--spacing-6)) / 3)))',
|
|
260
|
+
'repeat(auto-fill, minmax(min(100%, max(200px, calc((100% - 2 * var(--spacing-6)) / 3))), 1fr))',
|
|
237
261
|
);
|
|
238
262
|
expect(grid.style.maxWidth).toBe('');
|
|
239
263
|
});
|
|
240
264
|
|
|
241
|
-
it('uses gap var in
|
|
265
|
+
it('uses gap var in the count-cap floor when columnGap is not set', () => {
|
|
242
266
|
render(
|
|
243
267
|
<Grid columns={{minWidth: 150, max: 2}} gap={3} data-testid="grid">
|
|
244
268
|
<div>Item</div>
|
|
@@ -246,12 +270,12 @@ describe('Grid', () => {
|
|
|
246
270
|
);
|
|
247
271
|
const grid = screen.getByTestId('grid');
|
|
248
272
|
expect(templateColumns(grid)).toBe(
|
|
249
|
-
'repeat(auto-fill, minmax(150px, calc((100% - 1 * var(--spacing-3)) / 2)))',
|
|
273
|
+
'repeat(auto-fill, minmax(min(100%, max(150px, calc((100% - 1 * var(--spacing-3)) / 2))), 1fr))',
|
|
250
274
|
);
|
|
251
275
|
expect(grid.style.maxWidth).toBe('');
|
|
252
276
|
});
|
|
253
277
|
|
|
254
|
-
it('uses simple fraction in
|
|
278
|
+
it('uses simple fraction in the count-cap floor when no gap is set', () => {
|
|
255
279
|
render(
|
|
256
280
|
<Grid columns={{minWidth: 100, max: 3}} data-testid="grid">
|
|
257
281
|
<div>Item</div>
|
|
@@ -259,7 +283,7 @@ describe('Grid', () => {
|
|
|
259
283
|
);
|
|
260
284
|
const grid = screen.getByTestId('grid');
|
|
261
285
|
expect(templateColumns(grid)).toBe(
|
|
262
|
-
'repeat(auto-fill, minmax(100px, calc(100% / 3)))',
|
|
286
|
+
'repeat(auto-fill, minmax(min(100%, max(100px, calc(100% / 3))), 1fr))',
|
|
263
287
|
);
|
|
264
288
|
expect(grid.style.maxWidth).toBe('');
|
|
265
289
|
});
|
|
@@ -332,7 +356,7 @@ describe('Grid', () => {
|
|
|
332
356
|
expect(templateColumns(grid)).toBe('repeat(auto-fill, minmax(280px, 1fr))');
|
|
333
357
|
});
|
|
334
358
|
|
|
335
|
-
it('renders with columns={{minWidth, max}} capping
|
|
359
|
+
it('renders with columns={{minWidth, max}} capping the count while filling', () => {
|
|
336
360
|
render(
|
|
337
361
|
<Grid columns={{minWidth: 280, max: 3}} gap={4} data-testid="grid">
|
|
338
362
|
<div>Item 1</div>
|
|
@@ -340,14 +364,15 @@ describe('Grid', () => {
|
|
|
340
364
|
</Grid>,
|
|
341
365
|
);
|
|
342
366
|
const grid = screen.getByTestId('grid');
|
|
343
|
-
//
|
|
367
|
+
// Count is capped via the track MIN; track MAX stays 1fr so present
|
|
368
|
+
// columns fill the row (grid stays full width).
|
|
344
369
|
expect(templateColumns(grid)).toBe(
|
|
345
|
-
'repeat(auto-fill, minmax(280px, calc((100% - 2 * var(--spacing-4)) / 3)))',
|
|
370
|
+
'repeat(auto-fill, minmax(min(100%, max(280px, calc((100% - 2 * var(--spacing-4)) / 3))), 1fr))',
|
|
346
371
|
);
|
|
347
372
|
expect(grid.style.maxWidth).toBe('');
|
|
348
373
|
});
|
|
349
374
|
|
|
350
|
-
it('renders with columns={{minWidth, max, repeat: "fit"}} using auto-fit +
|
|
375
|
+
it('renders with columns={{minWidth, max, repeat: "fit"}} using auto-fit + count cap', () => {
|
|
351
376
|
render(
|
|
352
377
|
<Grid
|
|
353
378
|
columns={{minWidth: 280, max: 3, repeat: 'fit'}}
|
|
@@ -359,7 +384,7 @@ describe('Grid', () => {
|
|
|
359
384
|
);
|
|
360
385
|
const grid = screen.getByTestId('grid');
|
|
361
386
|
expect(templateColumns(grid)).toBe(
|
|
362
|
-
'repeat(auto-fit, minmax(280px, calc((100% - 2 * var(--spacing-4)) / 3)))',
|
|
387
|
+
'repeat(auto-fit, minmax(min(100%, max(280px, calc((100% - 2 * var(--spacing-4)) / 3))), 1fr))',
|
|
363
388
|
);
|
|
364
389
|
expect(grid.style.maxWidth).toBe('');
|
|
365
390
|
});
|
package/src/Grid/Grid.tsx
CHANGED
|
@@ -38,9 +38,10 @@ export type GridAlignment = 'start' | 'center' | 'end' | 'stretch';
|
|
|
38
38
|
* - `minWidth` — minimum width (px) for each column track
|
|
39
39
|
* - `repeat` — `'fill'` (default) preserves empty tracks for consistent widths;
|
|
40
40
|
* `'fit'` collapses empty tracks so items stretch to fill
|
|
41
|
-
* - `max` — caps the maximum number of columns
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* - `max` — caps the maximum number of columns. The grid stretches to 100%
|
|
42
|
+
* of its parent and the columns that are present always fill the row, so a
|
|
43
|
+
* layout collapsing to a single column on mobile stretches to full width
|
|
44
|
+
* (no dead space on the right).
|
|
44
45
|
*/
|
|
45
46
|
export type GridColumns =
|
|
46
47
|
| number
|
|
@@ -321,9 +322,20 @@ const spacingVarNames: Record<SpacingStep, string> = {
|
|
|
321
322
|
};
|
|
322
323
|
|
|
323
324
|
/**
|
|
324
|
-
* Build a grid-template-columns value that caps
|
|
325
|
-
*
|
|
326
|
-
*
|
|
325
|
+
* Build a grid-template-columns value that caps the column *count* at `max`
|
|
326
|
+
* while still letting the columns that are actually present stretch to fill
|
|
327
|
+
* the row.
|
|
328
|
+
*
|
|
329
|
+
* The cap lives on the track's **min** size, not its max: each track is at
|
|
330
|
+
* least `perColumn = (100% - (max-1) * gap) / max`, so more than `max` columns
|
|
331
|
+
* can never fit. The track **max** stays `1fr`, so whenever fewer than `max`
|
|
332
|
+
* columns fit (most importantly a lone column on mobile) they still grow to
|
|
333
|
+
* fill the whole row — no dead space on the right.
|
|
334
|
+
*
|
|
335
|
+
* The track min is `max(minWidth, perColumn)` so an explicit `minWidth` is
|
|
336
|
+
* still honored, wrapped in `min(100%, …)` so a single column on a viewport
|
|
337
|
+
* narrower than `minWidth`/`perColumn` shrinks to the container instead of
|
|
338
|
+
* overflowing it.
|
|
327
339
|
*/
|
|
328
340
|
function buildCappedTemplate(
|
|
329
341
|
minWidth: number,
|
|
@@ -339,12 +351,17 @@ function buildCappedTemplate(
|
|
|
339
351
|
? spacingVarNames[gap]
|
|
340
352
|
: null;
|
|
341
353
|
|
|
342
|
-
//
|
|
343
|
-
|
|
354
|
+
// perColumn = (100% - (maxCols - 1) * gap) / maxCols — the width a track
|
|
355
|
+
// would have if exactly `maxCols` columns were present. Used as a floor so
|
|
356
|
+
// the grid never fits more than `maxCols` columns.
|
|
357
|
+
const perColumn = gapVar
|
|
344
358
|
? `calc((100% - ${maxCols - 1} * var(${gapVar})) / ${maxCols})`
|
|
345
359
|
: `calc(100% / ${maxCols})`;
|
|
346
360
|
|
|
347
|
-
|
|
361
|
+
// Track min caps the count; track max stays 1fr so present columns fill.
|
|
362
|
+
const trackMin = `min(100%, max(${minWidth}px, ${perColumn}))`;
|
|
363
|
+
|
|
364
|
+
return `repeat(${repeatMode}, minmax(${trackMin}, 1fr))`;
|
|
348
365
|
}
|
|
349
366
|
|
|
350
367
|
/**
|
package/src/Icon/Icon.test.tsx
CHANGED
|
@@ -11,48 +11,48 @@
|
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen} from '@testing-library/react';
|
|
14
|
-
import {
|
|
14
|
+
import {TestIcon} from '../__tests__/TestIcon';
|
|
15
15
|
import {Icon} from './Icon';
|
|
16
16
|
|
|
17
17
|
describe('Icon', () => {
|
|
18
18
|
it('renders the icon component', () => {
|
|
19
|
-
render(<Icon icon={
|
|
19
|
+
render(<Icon icon={TestIcon} data-testid="icon" />);
|
|
20
20
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('renders as an SVG element', () => {
|
|
24
|
-
render(<Icon icon={
|
|
24
|
+
render(<Icon icon={TestIcon} data-testid="icon" />);
|
|
25
25
|
const icon = screen.getByTestId('icon');
|
|
26
26
|
expect(icon.tagName.toLowerCase()).toBe('svg');
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('applies aria-hidden by default', () => {
|
|
30
|
-
render(<Icon icon={
|
|
30
|
+
render(<Icon icon={TestIcon} data-testid="icon" />);
|
|
31
31
|
expect(screen.getByTestId('icon')).toHaveAttribute('aria-hidden', 'true');
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('renders with different color variants', () => {
|
|
35
35
|
const {rerender} = render(
|
|
36
|
-
<Icon icon={
|
|
36
|
+
<Icon icon={TestIcon} color="primary" data-testid="icon" />,
|
|
37
37
|
);
|
|
38
38
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
39
39
|
|
|
40
|
-
rerender(<Icon icon={
|
|
40
|
+
rerender(<Icon icon={TestIcon} color="secondary" data-testid="icon" />);
|
|
41
41
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
42
42
|
|
|
43
|
-
rerender(<Icon icon={
|
|
43
|
+
rerender(<Icon icon={TestIcon} color="accent" data-testid="icon" />);
|
|
44
44
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
45
45
|
|
|
46
|
-
rerender(<Icon icon={
|
|
46
|
+
rerender(<Icon icon={TestIcon} color="success" data-testid="icon" />);
|
|
47
47
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
48
48
|
|
|
49
|
-
rerender(<Icon icon={
|
|
49
|
+
rerender(<Icon icon={TestIcon} color="error" data-testid="icon" />);
|
|
50
50
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
51
51
|
|
|
52
|
-
rerender(<Icon icon={
|
|
52
|
+
rerender(<Icon icon={TestIcon} color="warning" data-testid="icon" />);
|
|
53
53
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
54
54
|
|
|
55
|
-
rerender(<Icon icon={
|
|
55
|
+
rerender(<Icon icon={TestIcon} color="inherit" data-testid="icon" />);
|
|
56
56
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -70,50 +70,41 @@ describe('Icon', () => {
|
|
|
70
70
|
'purple',
|
|
71
71
|
] as const;
|
|
72
72
|
const {rerender} = render(
|
|
73
|
-
<Icon
|
|
74
|
-
icon={HomeIcon}
|
|
75
|
-
color={nonSemanticColors[0]}
|
|
76
|
-
data-testid="icon"
|
|
77
|
-
/>,
|
|
73
|
+
<Icon icon={TestIcon} color={nonSemanticColors[0]} data-testid="icon" />,
|
|
78
74
|
);
|
|
79
75
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
80
76
|
|
|
81
77
|
for (const c of nonSemanticColors.slice(1)) {
|
|
82
|
-
rerender(<Icon icon={
|
|
78
|
+
rerender(<Icon icon={TestIcon} color={c} data-testid="icon" />);
|
|
83
79
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
84
80
|
}
|
|
85
81
|
});
|
|
86
82
|
|
|
87
83
|
it('renders with different size variants', () => {
|
|
88
84
|
const {rerender} = render(
|
|
89
|
-
<Icon icon={
|
|
85
|
+
<Icon icon={TestIcon} size="xsm" data-testid="icon" />,
|
|
90
86
|
);
|
|
91
87
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
92
88
|
|
|
93
|
-
rerender(<Icon icon={
|
|
89
|
+
rerender(<Icon icon={TestIcon} size="sm" data-testid="icon" />);
|
|
94
90
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
95
91
|
|
|
96
|
-
rerender(<Icon icon={
|
|
92
|
+
rerender(<Icon icon={TestIcon} size="md" data-testid="icon" />);
|
|
97
93
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
98
94
|
|
|
99
|
-
rerender(<Icon icon={
|
|
95
|
+
rerender(<Icon icon={TestIcon} size="lg" data-testid="icon" />);
|
|
100
96
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
101
97
|
});
|
|
102
98
|
|
|
103
99
|
it('forwards ref correctly', () => {
|
|
104
100
|
const ref = vi.fn();
|
|
105
|
-
render(<Icon icon={
|
|
101
|
+
render(<Icon icon={TestIcon} ref={ref} />);
|
|
106
102
|
expect(ref).toHaveBeenCalledWith(expect.any(SVGSVGElement));
|
|
107
103
|
});
|
|
108
104
|
|
|
109
105
|
it('passes additional SVG props', () => {
|
|
110
106
|
render(
|
|
111
|
-
<Icon
|
|
112
|
-
icon={HomeIcon}
|
|
113
|
-
data-testid="icon"
|
|
114
|
-
role="img"
|
|
115
|
-
aria-label="Home"
|
|
116
|
-
/>,
|
|
107
|
+
<Icon icon={TestIcon} data-testid="icon" role="img" aria-label="Home" />,
|
|
117
108
|
);
|
|
118
109
|
const icon = screen.getByTestId('icon');
|
|
119
110
|
expect(icon).toHaveAttribute('role', 'img');
|
|
@@ -121,8 +112,29 @@ describe('Icon', () => {
|
|
|
121
112
|
});
|
|
122
113
|
|
|
123
114
|
it('uses default color and size when not specified', () => {
|
|
124
|
-
render(<Icon icon={
|
|
115
|
+
render(<Icon icon={TestIcon} data-testid="icon" />);
|
|
125
116
|
// The component should render without errors with defaults
|
|
126
117
|
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
127
118
|
});
|
|
119
|
+
|
|
120
|
+
it('applies aria-hidden by default in string (registry) mode', () => {
|
|
121
|
+
render(<Icon icon="check" data-testid="icon" />);
|
|
122
|
+
expect(screen.getByTestId('icon')).toHaveAttribute('aria-hidden', 'true');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('lets a string-mode icon be made meaningful by overriding aria-hidden', () => {
|
|
126
|
+
render(
|
|
127
|
+
<Icon
|
|
128
|
+
icon="check"
|
|
129
|
+
data-testid="icon"
|
|
130
|
+
role="img"
|
|
131
|
+
aria-label="Done"
|
|
132
|
+
aria-hidden={false}
|
|
133
|
+
/>,
|
|
134
|
+
);
|
|
135
|
+
const icon = screen.getByTestId('icon');
|
|
136
|
+
expect(icon).toHaveAttribute('role', 'img');
|
|
137
|
+
expect(icon).toHaveAttribute('aria-label', 'Done');
|
|
138
|
+
expect(icon).toHaveAttribute('aria-hidden', 'false');
|
|
139
|
+
});
|
|
128
140
|
});
|
package/src/Icon/Icon.tsx
CHANGED
|
@@ -281,12 +281,16 @@ function IconFromRegistry({
|
|
|
281
281
|
|
|
282
282
|
return (
|
|
283
283
|
<span
|
|
284
|
+
// Decorative by default, but placed BEFORE the prop spread so consumers
|
|
285
|
+
// can override it (e.g. `aria-hidden={false}` + `role="img"` +
|
|
286
|
+
// `aria-label`) to expose a meaningful standalone icon. This matches
|
|
287
|
+
// component-mode Icon, which already sets aria-hidden before its spread.
|
|
288
|
+
aria-hidden="true"
|
|
284
289
|
{...(spanProps as React.HTMLAttributes<HTMLSpanElement>)}
|
|
285
290
|
{...mergeProps(
|
|
286
291
|
themeProps('icon', {size, color}),
|
|
287
292
|
stylex.props(styles.span, colorStyles[color], spanSizeStyles[size]),
|
|
288
|
-
)}
|
|
289
|
-
aria-hidden="true">
|
|
293
|
+
)}>
|
|
290
294
|
{resolvedIcon}
|
|
291
295
|
</span>
|
|
292
296
|
);
|
|
@@ -201,11 +201,11 @@ export function InputGroup({
|
|
|
201
201
|
labelTooltip={labelTooltip}>
|
|
202
202
|
<div
|
|
203
203
|
ref={ref}
|
|
204
|
+
data-testid={testId}
|
|
205
|
+
{...rest}
|
|
204
206
|
role="group"
|
|
205
207
|
aria-labelledby={labelID}
|
|
206
208
|
aria-describedby={describedByIDs}
|
|
207
|
-
data-testid={testId}
|
|
208
|
-
{...rest}
|
|
209
209
|
{...mergeProps(
|
|
210
210
|
themeProps('input-group', {
|
|
211
211
|
size,
|
package/src/Item/Item.tsx
CHANGED
|
@@ -470,9 +470,9 @@ export function Item({
|
|
|
470
470
|
return (
|
|
471
471
|
<Component
|
|
472
472
|
ref={ref as React.Ref<never>}
|
|
473
|
+
{...restProps}
|
|
473
474
|
aria-selected={isSelected || undefined}
|
|
474
475
|
aria-disabled={isDisabled || undefined}
|
|
475
|
-
{...restProps}
|
|
476
476
|
{...mergeProps(
|
|
477
477
|
themeProps('item', {density, align}),
|
|
478
478
|
stylex.props(
|
|
@@ -14,6 +14,12 @@ export const docs = {
|
|
|
14
14
|
type: 'ReactNode',
|
|
15
15
|
description: 'Content.',
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
name: 'padding',
|
|
19
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
20
|
+
description:
|
|
21
|
+
'Internal padding using the spacing scale. Overrides the default padding from the layout container.',
|
|
22
|
+
},
|
|
17
23
|
{
|
|
18
24
|
name: 'isScrollable',
|
|
19
25
|
type: 'boolean',
|
|
@@ -44,6 +50,11 @@ export const docsZh = {
|
|
|
44
50
|
type: 'ReactNode',
|
|
45
51
|
description: '内容。',
|
|
46
52
|
},
|
|
53
|
+
{
|
|
54
|
+
name: 'padding',
|
|
55
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
56
|
+
description: '使用间距比例的内边距。覆盖布局容器的默认内边距。',
|
|
57
|
+
},
|
|
47
58
|
{
|
|
48
59
|
name: 'isScrollable',
|
|
49
60
|
type: 'boolean',
|
|
@@ -70,6 +81,7 @@ export const docsDense = {
|
|
|
70
81
|
description: 'Scrollable main content area.',
|
|
71
82
|
propDescriptions: {
|
|
72
83
|
children: 'Content.',
|
|
84
|
+
padding: 'Internal padding on spacing scale. Overrides layout container default.',
|
|
73
85
|
isScrollable: 'Enable scrollable overflow.',
|
|
74
86
|
label: 'Accessible label for landmark element.',
|
|
75
87
|
role: 'ARIA landmark role.',
|