@astryxdesign/core 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21

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.
Files changed (226) hide show
  1. package/dist/Banner/Banner.d.ts.map +1 -1
  2. package/dist/Banner/Banner.js +1 -8
  3. package/dist/Calendar/Calendar.d.ts.map +1 -1
  4. package/dist/Calendar/Calendar.js +11 -31
  5. package/dist/Calendar/styles.d.ts +2 -2
  6. package/dist/Calendar/styles.d.ts.map +1 -1
  7. package/dist/Calendar/styles.js +2 -2
  8. package/dist/Carousel/Carousel.d.ts.map +1 -1
  9. package/dist/Carousel/Carousel.js +0 -1
  10. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  11. package/dist/Chat/ChatToolCalls.js +1 -8
  12. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +1 -3
  15. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  16. package/dist/ClickableCard/ClickableCard.js +3 -26
  17. package/dist/CodeBlock/CodeBlock.d.ts +1 -10
  18. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  19. package/dist/CodeBlock/CodeBlock.js +5 -23
  20. package/dist/Collapsible/Collapsible.d.ts +3 -9
  21. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  22. package/dist/Collapsible/Collapsible.js +13 -101
  23. package/dist/Collapsible/CollapsibleGroup.d.ts +7 -39
  24. package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
  25. package/dist/Collapsible/CollapsibleGroup.js +9 -70
  26. package/dist/Collapsible/CollapsibleGroupContext.d.ts +0 -31
  27. package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
  28. package/dist/Collapsible/CollapsibleGroupContext.js +3 -33
  29. package/dist/Collapsible/index.d.ts +0 -1
  30. package/dist/Collapsible/index.d.ts.map +1 -1
  31. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  32. package/dist/ContextMenu/ContextMenu.js +0 -2
  33. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  34. package/dist/DateTimeInput/DateTimeInput.js +0 -2
  35. package/dist/Grid/Grid.d.ts +3 -4
  36. package/dist/Grid/Grid.d.ts.map +1 -1
  37. package/dist/Grid/Grid.js +9 -26
  38. package/dist/Icon/Icon.d.ts.map +1 -1
  39. package/dist/Icon/Icon.js +1 -5
  40. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  41. package/dist/Lightbox/Lightbox.js +2 -26
  42. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  43. package/dist/NumberInput/NumberInput.js +16 -32
  44. package/dist/Pagination/Pagination.d.ts.map +1 -1
  45. package/dist/Pagination/Pagination.js +36 -87
  46. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  47. package/dist/PowerSearch/PowerSearch.js +21 -39
  48. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  49. package/dist/ProgressBar/ProgressBar.js +1 -2
  50. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  51. package/dist/Resizable/ResizeHandle.js +2 -10
  52. package/dist/SideNav/SideNav.d.ts.map +1 -1
  53. package/dist/SideNav/SideNav.js +2 -6
  54. package/dist/TabList/TabMenu.d.ts.map +1 -1
  55. package/dist/TabList/TabMenu.js +7 -34
  56. package/dist/Table/BaseTable.d.ts.map +1 -1
  57. package/dist/Table/BaseTable.js +2 -12
  58. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  59. package/dist/Table/plugins/columnResize/useTableColumnResize.js +2 -5
  60. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  61. package/dist/Table/plugins/pagination/paginateData.js +1 -5
  62. package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
  63. package/dist/Table/plugins/selection/useTableSelectionState.js +1 -11
  64. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  65. package/dist/Table/plugins/sortable/useTableSortableState.js +4 -8
  66. package/dist/Thumbnail/Thumbnail.d.ts +2 -3
  67. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  68. package/dist/Thumbnail/Thumbnail.js +0 -1
  69. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  70. package/dist/TimeInput/TimeInput.js +1 -6
  71. package/dist/Toast/Toast.d.ts.map +1 -1
  72. package/dist/Toast/Toast.js +3 -11
  73. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  74. package/dist/Toast/ToastViewport.js +0 -12
  75. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  76. package/dist/Typeahead/BaseTypeahead.js +2 -5
  77. package/dist/astryx.css +0 -7
  78. package/dist/astryx.umd.js +48 -48
  79. package/dist/astryx.umd.js.map +4 -4
  80. package/dist/hooks/focusableSelector.d.ts +1 -1
  81. package/dist/hooks/focusableSelector.d.ts.map +1 -1
  82. package/dist/hooks/focusableSelector.js +1 -1
  83. package/dist/hooks/useFocusTrap.d.ts +0 -3
  84. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  85. package/dist/hooks/useFocusTrap.js +1 -38
  86. package/dist/theme/defineTheme.d.ts +1 -1
  87. package/dist/theme/defineTheme.d.ts.map +1 -1
  88. package/dist/theme/expandColorScale.d.ts.map +1 -1
  89. package/dist/theme/expandColorScale.js +3 -10
  90. package/dist/theme/hct.d.ts +15 -0
  91. package/dist/theme/hct.d.ts.map +1 -1
  92. package/dist/theme/hct.js +9 -9
  93. package/dist/theme/tokens.d.ts +2 -10
  94. package/dist/theme/tokens.d.ts.map +1 -1
  95. package/dist/theme/tokens.js +4 -220
  96. package/dist/utils/index.d.ts +0 -2
  97. package/dist/utils/index.d.ts.map +1 -1
  98. package/dist/utils/index.js +1 -2
  99. package/dist/utils/timeParser.d.ts.map +1 -1
  100. package/dist/utils/timeParser.js +5 -8
  101. package/package.json +1 -1
  102. package/src/Avatar/Avatar.doc.mjs +0 -11
  103. package/src/Banner/Banner.doc.mjs +0 -8
  104. package/src/Banner/Banner.test.tsx +0 -38
  105. package/src/Banner/Banner.tsx +1 -8
  106. package/src/Calendar/Calendar.doc.mjs +1 -1
  107. package/src/Calendar/Calendar.test.tsx +4 -112
  108. package/src/Calendar/Calendar.tsx +5 -26
  109. package/src/Calendar/styles.ts +2 -12
  110. package/src/Carousel/Carousel.doc.mjs +12 -12
  111. package/src/Carousel/Carousel.test.tsx +0 -13
  112. package/src/Carousel/Carousel.tsx +0 -1
  113. package/src/Chat/Chat.doc.mjs +1 -1
  114. package/src/Chat/ChatToolCalls.test.tsx +0 -55
  115. package/src/Chat/ChatToolCalls.tsx +12 -10
  116. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
  117. package/src/CheckboxInput/CheckboxInput.test.tsx +4 -71
  118. package/src/CheckboxInput/CheckboxInput.tsx +0 -2
  119. package/src/Citation/Citation.doc.mjs +0 -5
  120. package/src/ClickableCard/ClickableCard.tsx +2 -45
  121. package/src/CodeBlock/CodeBlock.doc.mjs +2 -7
  122. package/src/CodeBlock/CodeBlock.test.tsx +3 -87
  123. package/src/CodeBlock/CodeBlock.tsx +3 -43
  124. package/src/Collapsible/Collapsible.doc.mjs +4 -8
  125. package/src/Collapsible/Collapsible.tsx +10 -86
  126. package/src/Collapsible/CollapsibleGroup.doc.mjs +3 -27
  127. package/src/Collapsible/CollapsibleGroup.test.tsx +0 -288
  128. package/src/Collapsible/CollapsibleGroup.tsx +9 -107
  129. package/src/Collapsible/CollapsibleGroupContext.tsx +2 -42
  130. package/src/Collapsible/index.ts +0 -4
  131. package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +0 -5
  132. package/src/CommandPalette/CommandPaletteGroup.doc.mjs +0 -10
  133. package/src/CommandPalette/CommandPaletteInput.doc.mjs +0 -6
  134. package/src/CommandPalette/CommandPaletteItem.doc.mjs +0 -6
  135. package/src/CommandPalette/CommandPaletteList.doc.mjs +0 -9
  136. package/src/ContextMenu/ContextMenu.doc.mjs +0 -15
  137. package/src/ContextMenu/ContextMenu.test.tsx +0 -15
  138. package/src/ContextMenu/ContextMenu.tsx +0 -2
  139. package/src/ContextMenu/ContextMenuItem.doc.mjs +0 -6
  140. package/src/DateTimeInput/DateTimeInput.test.tsx +0 -70
  141. package/src/DateTimeInput/DateTimeInput.tsx +0 -2
  142. package/src/Dialog/DialogHeader.doc.mjs +0 -45
  143. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
  144. package/src/Field/FieldLabel.test.tsx +2 -2
  145. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -0
  146. package/src/Grid/Grid.test.tsx +18 -43
  147. package/src/Grid/Grid.tsx +9 -26
  148. package/src/Icon/Icon.test.tsx +29 -41
  149. package/src/Icon/Icon.tsx +2 -6
  150. package/src/Layout/LayoutContent.doc.mjs +0 -12
  151. package/src/Layout/LayoutPanel.doc.mjs +0 -40
  152. package/src/Lightbox/Lightbox.doc.mjs +0 -5
  153. package/src/Lightbox/Lightbox.test.tsx +2 -123
  154. package/src/Lightbox/Lightbox.tsx +1 -29
  155. package/src/NavMenu/NavMenu.doc.mjs +0 -15
  156. package/src/NumberInput/NumberInput.test.tsx +3 -48
  157. package/src/NumberInput/NumberInput.tsx +15 -32
  158. package/src/Overlay/Overlay.doc.mjs +0 -6
  159. package/src/Pagination/Pagination.test.tsx +0 -143
  160. package/src/Pagination/Pagination.tsx +23 -75
  161. package/src/Popover/Popover.test.tsx +0 -65
  162. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  163. package/src/PowerSearch/PowerSearch.test.tsx +1 -109
  164. package/src/PowerSearch/PowerSearch.tsx +16 -37
  165. package/src/ProgressBar/ProgressBar.test.tsx +0 -10
  166. package/src/ProgressBar/ProgressBar.tsx +1 -2
  167. package/src/Resizable/ResizeHandle.tsx +2 -10
  168. package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
  169. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  170. package/src/SideNav/SideNav.doc.mjs +1 -7
  171. package/src/SideNav/SideNav.test.tsx +0 -20
  172. package/src/SideNav/SideNav.tsx +2 -6
  173. package/src/TabList/TabList.test.tsx +1 -185
  174. package/src/TabList/TabMenu.tsx +9 -45
  175. package/src/Table/BaseTable.tsx +1 -11
  176. package/src/Table/Table.test.tsx +0 -51
  177. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -13
  178. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -3
  179. package/src/Table/plugins/pagination/paginateData.ts +1 -5
  180. package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -13
  181. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -23
  182. package/src/Table/plugins/selection/useTableSelectionState.tsx +6 -11
  183. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +1 -35
  184. package/src/Table/plugins/sortable/useTableSortableState.tsx +4 -8
  185. package/src/TextArea/TextArea.test.tsx +3 -3
  186. package/src/TextInput/TextInput.test.tsx +3 -3
  187. package/src/Thumbnail/Thumbnail.test.tsx +2 -36
  188. package/src/Thumbnail/Thumbnail.tsx +2 -4
  189. package/src/TimeInput/TimeInput.test.tsx +0 -37
  190. package/src/TimeInput/TimeInput.tsx +1 -11
  191. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  192. package/src/Toast/Toast.tsx +3 -11
  193. package/src/Toast/ToastViewport.test.tsx +0 -64
  194. package/src/Toast/ToastViewport.tsx +0 -12
  195. package/src/ToggleButton/ToggleButton.doc.mjs +0 -1
  196. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  197. package/src/TopNav/TopNav.doc.mjs +1 -1
  198. package/src/TreeList/TreeList.doc.mjs +2 -2
  199. package/src/Typeahead/BaseTypeahead.doc.mjs +0 -19
  200. package/src/Typeahead/BaseTypeahead.tsx +2 -5
  201. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  202. package/src/Typeahead/Typeahead.test.tsx +1 -45
  203. package/src/hooks/focusableSelector.ts +1 -1
  204. package/src/hooks/useFocusTrap.doc.mjs +2 -4
  205. package/src/hooks/useFocusTrap.test.tsx +1 -149
  206. package/src/hooks/useFocusTrap.ts +1 -50
  207. package/src/theme/SyntaxTheme.doc.mjs +4 -4
  208. package/src/theme/defineTheme.ts +13 -4
  209. package/src/theme/derivedVarRegistry.test.ts +2 -2
  210. package/src/theme/expandColorScale.test.ts +0 -29
  211. package/src/theme/expandColorScale.ts +6 -11
  212. package/src/theme/hct.ts +17 -9
  213. package/src/theme/tokens.test.ts +9 -182
  214. package/src/theme/tokens.ts +4 -239
  215. package/src/theme/useTheme.test.tsx +0 -20
  216. package/src/utils/index.ts +0 -3
  217. package/src/utils/timeParser.test.ts +0 -13
  218. package/src/utils/timeParser.ts +5 -8
  219. package/dist/utils/color.d.ts +0 -42
  220. package/dist/utils/color.d.ts.map +0 -1
  221. package/dist/utils/color.js +0 -150
  222. package/src/Resizable/ResizeHandle.test.tsx +0 -180
  223. package/src/__tests__/TestIcon.tsx +0 -26
  224. package/src/docPropReferences.test.ts +0 -178
  225. package/src/utils/color.test.ts +0 -133
  226. package/src/utils/color.ts +0 -148
@@ -270,44 +270,6 @@ describe('Banner', () => {
270
270
  expect(expandIndex).toBeLessThan(dismissIndex);
271
271
  });
272
272
 
273
- it('links the expand toggle to its content region via aria-controls', () => {
274
- render(
275
- <Banner status="info" title="Controls Test" defaultIsExpanded>
276
- <div data-testid="region-content">Region content</div>
277
- </Banner>,
278
- );
279
-
280
- const toggle = screen.getByRole('button', {name: 'Collapse'});
281
- const controlsId = toggle.getAttribute('aria-controls');
282
- // aria-controls must be present and point at the real content region.
283
- expect(controlsId).toBeTruthy();
284
- const region = document.getElementById(controlsId as string);
285
- expect(region).not.toBeNull();
286
- expect(region).toContainElement(screen.getByTestId('region-content'));
287
- });
288
-
289
- it('sets aria-controls only while the content region is mounted', async () => {
290
- const user = userEvent.setup();
291
- render(
292
- <Banner status="info" title="Controls Toggle">
293
- <div data-testid="region-content">Region content</div>
294
- </Banner>,
295
- );
296
-
297
- // Collapsed: the region is unmounted, so no dangling aria-controls target.
298
- const collapsedToggle = screen.getByRole('button', {name: 'Expand'});
299
- expect(collapsedToggle).not.toHaveAttribute('aria-controls');
300
-
301
- // Expanded: aria-controls resolves to the mounted region with the children.
302
- await user.click(collapsedToggle);
303
- const expandedToggle = screen.getByRole('button', {name: 'Collapse'});
304
- const controlsId = expandedToggle.getAttribute('aria-controls');
305
- expect(controlsId).toBeTruthy();
306
- const region = document.getElementById(controlsId as string);
307
- expect(region).not.toBeNull();
308
- expect(region).toContainElement(screen.getByTestId('region-content'));
309
- });
310
-
311
273
  it('does not render content area when no children', () => {
312
274
  const {container} = render(<Banner status="info" title="No Children" />);
313
275
  const root = container.firstElementChild;
@@ -31,7 +31,7 @@
31
31
  * - /packages/cli/templates/blocks/components/Banner/ (showcase blocks)
32
32
  */
33
33
 
34
- import {useId, useState, type ReactNode} from 'react';
34
+ import {useState, type ReactNode} from 'react';
35
35
  import * as stylex from '@stylexjs/stylex';
36
36
  import type {BaseProps} from '../BaseProps';
37
37
  import {Button} from '../Button';
@@ -387,11 +387,6 @@ export function Banner({
387
387
  }: BannerProps) {
388
388
  const [isDismissed, setIsDismissed] = useState(false);
389
389
  const [isExpanded, setIsExpanded] = useState(defaultIsExpanded);
390
- // Links the expand/collapse toggle to the content region it shows/hides so
391
- // assistive tech can move from the button to its controlled content
392
- // (disclosure pattern). The region is conditionally rendered, so aria-controls
393
- // below is set only while it's mounted to avoid a dangling reference.
394
- const contentId = useId();
395
390
  const defaultIconName = defaultIconNames[status];
396
391
  const role = statusRole[status];
397
392
  const iconColor = statusIconColor[status];
@@ -482,7 +477,6 @@ export function Banner({
482
477
  }
483
478
  onClick={handleToggleExpand}
484
479
  aria-expanded={isExpanded}
485
- aria-controls={showContent ? contentId : undefined}
486
480
  isIconOnly
487
481
  />
488
482
  )}
@@ -503,7 +497,6 @@ export function Banner({
503
497
  {/* Content area: collapsible card background — theme target ('banner-content') */}
504
498
  {showContent && (
505
499
  <div
506
- id={contentId}
507
500
  {...mergeProps(
508
501
  themeProps('banner-content', {container, status}),
509
502
  stylex.props(styles.contentArea, isCard && styles.contentAreaCard),
@@ -19,7 +19,7 @@ export const docs = {
19
19
  {guidance: false, description: 'Disable large blocks of dates without context. The user should understand why dates are unavailable.'},
20
20
  ],
21
21
  anatomy: [
22
- {name: 'Month header', required: true, description: 'The month name and year with navigation arrows to move between months. The arrows mirror automatically under dir="rtl".'},
22
+ {name: 'Month header', required: true, description: 'The month name and year with navigation arrows to move between months.'},
23
23
  {name: 'Day grid', required: true, description: 'A 7-column grid of days with column headers for the day names.'},
24
24
  {name: 'Selected day', required: false, description: 'The currently selected date, highlighted. In range mode, the start and end dates plus the days between them.'},
25
25
  {name: 'Today marker', required: false, description: 'A subtle indicator on the current date for orientation.'},
@@ -12,10 +12,8 @@
12
12
  import {describe, it, expect, vi} from 'vitest';
13
13
  import {act, render, screen, within} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
- import * as stylex from '@stylexjs/stylex';
16
15
  import {Calendar} from './Calendar';
17
16
  import type {CalendarHandle} from './Calendar';
18
- import {calendarStyles} from './styles';
19
17
 
20
18
  /**
21
19
  * Helper to find a day button by its day number.
@@ -73,24 +71,6 @@ describe('Calendar', () => {
73
71
  expect(screen.getByText(expectedLabel)).toBeInTheDocument();
74
72
  });
75
73
 
76
- it("marks today's cell with aria-current='date'", () => {
77
- render(<Calendar />);
78
-
79
- const now = new Date();
80
- const iso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
81
-
82
- const todayCell = document.querySelector(`button[data-date="${iso}"]`);
83
- expect(todayCell).not.toBeNull();
84
- expect(todayCell).toHaveAttribute('aria-current', 'date');
85
-
86
- // Only today's cell is marked current.
87
- const others = Array.from(
88
- document.querySelectorAll('button[data-date]'),
89
- ).filter(el => el.getAttribute('data-date') !== iso);
90
- expect(others.length).toBeGreaterThan(0);
91
- others.forEach(el => expect(el).not.toHaveAttribute('aria-current'));
92
- });
93
-
94
74
  it('displays day names', () => {
95
75
  render(<Calendar />);
96
76
 
@@ -117,11 +97,7 @@ describe('Calendar', () => {
117
97
  render(<Calendar value="2026-01-15" focusDate="2026-01-01" />);
118
98
 
119
99
  const day15 = getDayButton(15);
120
- // In an ARIA grid, selection state lives on the gridcell, not the button
121
- // (a plain button role does not permit aria-selected).
122
- const gridcell15 = day15.closest('[role="gridcell"]');
123
- expect(gridcell15).toHaveAttribute('aria-selected', 'true');
124
- expect(day15).not.toHaveAttribute('aria-selected');
100
+ expect(day15).toHaveAttribute('aria-selected', 'true');
125
101
  });
126
102
 
127
103
  it('calls onChange when date is selected', async () => {
@@ -239,33 +215,6 @@ describe('Calendar', () => {
239
215
  expect(screen.getByText(/February 2026.*March 2026/)).toBeInTheDocument();
240
216
  });
241
217
 
242
- it('clamps out-of-range numberOfMonths to a single month', () => {
243
- // 1000 would otherwise try to render 1000 month grids and lock the page up.
244
- render(
245
- <Calendar
246
- numberOfMonths={1000 as unknown as 1 | 2}
247
- focusDate="2026-01-01"
248
- />,
249
- );
250
-
251
- // Only the focused month renders — no second month, no range separator.
252
- expect(screen.getByText('January 2026')).toBeInTheDocument();
253
- expect(screen.queryByText(/February 2026/)).not.toBeInTheDocument();
254
- });
255
-
256
- it('clamps numberOfMonths={0} to a single month', () => {
257
- render(
258
- <Calendar
259
- numberOfMonths={0 as unknown as 1 | 2}
260
- focusDate="2026-01-01"
261
- />,
262
- );
263
-
264
- // 0 previously rendered no months at all; now falls back to one.
265
- expect(screen.getByText('January 2026')).toBeInTheDocument();
266
- expect(screen.getAllByRole('gridcell').length).toBeGreaterThan(0);
267
- });
268
-
269
218
  // ─── Display Options ─────────────────────────────────────────
270
219
 
271
220
  it('shows week numbers when hasWeekNumbers is true', () => {
@@ -361,22 +310,9 @@ describe('Calendar', () => {
361
310
  const day12 = getDayButton(12);
362
311
  const day15 = getDayButton(15);
363
312
 
364
- // Selection state lives on the gridcell wrapper, not the day button.
365
- expect(day10.closest('[role="gridcell"]')).toHaveAttribute(
366
- 'aria-selected',
367
- 'true',
368
- );
369
- expect(day12.closest('[role="gridcell"]')).toHaveAttribute(
370
- 'aria-selected',
371
- 'true',
372
- );
373
- expect(day15.closest('[role="gridcell"]')).toHaveAttribute(
374
- 'aria-selected',
375
- 'true',
376
- );
377
- expect(day10).not.toHaveAttribute('aria-selected');
378
- expect(day12).not.toHaveAttribute('aria-selected');
379
- expect(day15).not.toHaveAttribute('aria-selected');
313
+ expect(day10).toHaveAttribute('aria-selected', 'true');
314
+ expect(day12).toHaveAttribute('aria-selected', 'true');
315
+ expect(day15).toHaveAttribute('aria-selected', 'true');
380
316
  });
381
317
 
382
318
  it('caps the range highlight next to a disabled mid-range day (#2715)', () => {
@@ -739,48 +675,4 @@ describe('Calendar', () => {
739
675
  }
740
676
  }
741
677
  });
742
-
743
- // ─── RTL (#3388) ─────────────────────────────────────────────
744
-
745
- describe('RTL month navigation', () => {
746
- // jsdom does not apply compiled StyleX CSS, so the scaleX(-1) mirror
747
- // itself is only observable in a browser (see the dir="rtl" Storybook
748
- // story). These tests pin the structure the fix depends on: both nav
749
- // chevrons render inside the navIcon wrapper that carries the
750
- // ':is([dir="rtl"] *)' conditional transform.
751
- it('wraps both nav chevrons in the RTL-mirroring navIcon wrapper', () => {
752
- render(<Calendar focusDate="2026-01-01" />);
753
-
754
- const {className: navIconClass} = stylex.props(calendarStyles.navIcon);
755
- expect(navIconClass).toBeTruthy();
756
-
757
- for (const name of ['Previous month', 'Next month']) {
758
- const button = screen.getByRole('button', {name});
759
- const wrappers = Array.from(button.querySelectorAll('span')).filter(
760
- span => span.className === navIconClass,
761
- );
762
- expect(wrappers.length).toBe(1);
763
- }
764
- });
765
-
766
- it('keeps navigation semantics unchanged under dir="rtl"', async () => {
767
- const user = userEvent.setup();
768
-
769
- render(
770
- <div dir="rtl">
771
- <Calendar focusDate="2026-02-01" />
772
- </div>,
773
- );
774
-
775
- expect(screen.getByText('February 2026')).toBeInTheDocument();
776
-
777
- // DOM order and handlers must not change in RTL: flexbox already
778
- // places "Previous month" at the visual right; only the glyph mirrors.
779
- await user.click(screen.getByRole('button', {name: 'Previous month'}));
780
- expect(screen.getByText('January 2026')).toBeInTheDocument();
781
-
782
- await user.click(screen.getByRole('button', {name: 'Next month'}));
783
- expect(screen.getByText('February 2026')).toBeInTheDocument();
784
- });
785
- });
786
678
  });
@@ -203,7 +203,7 @@ export function Calendar({ref, ...props}: CalendarProps) {
203
203
  value,
204
204
  defaultValue,
205
205
  onChange,
206
- numberOfMonths: numberOfMonthsProp = 1,
206
+ numberOfMonths = 1,
207
207
  min,
208
208
  max,
209
209
  dateConstraints,
@@ -223,11 +223,6 @@ export function Calendar({ref, ...props}: CalendarProps) {
223
223
  // DayOfWeek so all downstream date math keeps working with an index.
224
224
  const weekStartsOn = normalizeDayOfWeek(weekStartsOnProp);
225
225
 
226
- // `numberOfMonths` is typed `1 | 2`; defensively clamp anything else that
227
- // slips through at runtime to 1 so `Array.from({length})` can't render an
228
- // absurd number of month grids (e.g. `numberOfMonths={1000}`).
229
- const numberOfMonths = numberOfMonthsProp === 2 ? 2 : 1;
230
-
231
226
  // Today's date (memoized)
232
227
  const today = useMemo(() => plainDateToday(), []);
233
228
 
@@ -431,14 +426,7 @@ export function Calendar({ref, ...props}: CalendarProps) {
431
426
  <Button
432
427
  label="Previous month"
433
428
  variant="ghost"
434
- icon={
435
- // Wrapper span (not Icon props): Icon's string mode clobbers
436
- // caller classNames, so the RTL mirror must live on its own
437
- // element.
438
- <span {...stylex.props(calendarStyles.navIcon)}>
439
- <Icon icon="chevronLeft" size="sm" color="inherit" />
440
- </span>
441
- }
429
+ icon={<Icon icon="chevronLeft" size="sm" color="inherit" />}
442
430
  onClick={() => navigateMonth(-1)}
443
431
  isDisabled={!canNavigatePrevious}
444
432
  isIconOnly
@@ -451,11 +439,7 @@ export function Calendar({ref, ...props}: CalendarProps) {
451
439
  <Button
452
440
  label="Next month"
453
441
  variant="ghost"
454
- icon={
455
- <span {...stylex.props(calendarStyles.navIcon)}>
456
- <Icon icon="chevronRight" size="sm" color="inherit" />
457
- </span>
458
- }
442
+ icon={<Icon icon="chevronRight" size="sm" color="inherit" />}
459
443
  onClick={() => navigateMonth(1)}
460
444
  isDisabled={!canNavigateNext}
461
445
  isIconOnly
@@ -928,10 +912,7 @@ function DayCell({
928
912
  });
929
913
 
930
914
  return (
931
- <div
932
- role="gridcell"
933
- aria-selected={state.isSelected || state.isInRange || undefined}
934
- {...stylex.props(dayCellStyles.cell)}>
915
+ <div role="gridcell" {...stylex.props(dayCellStyles.cell)}>
935
916
  {/* Range background */}
936
917
  {state.isInRange && (
937
918
  <div
@@ -971,10 +952,8 @@ function DayCell({
971
952
  type="button"
972
953
  data-date={day.iso}
973
954
  aria-label={plainDateFormat(date, DATE_FORMAT_WITH_WEEKDAY)}
955
+ aria-selected={state.isSelected || state.isInRange || undefined}
974
956
  aria-disabled={state.effectivelyDisabled || undefined}
975
- // Mark today's cell programmatically (APG date-picker pattern), not just
976
- // visually, so screen-reader users can identify the current date.
977
- aria-current={state.isToday ? 'date' : undefined}
978
957
  disabled={isDisabled}
979
958
  // Initial roving tab-stop seed; useGridFocus owns it after mount.
980
959
  tabIndex={isTabbableDay ? 0 : -1}
@@ -64,19 +64,9 @@ export const calendarStyles = stylex.create({
64
64
  whiteSpace: 'nowrap',
65
65
  borderWidth: 0,
66
66
  },
67
- /**
68
- * Wrapper for the month nav chevrons. In RTL the flex header already
69
- * swaps the buttons' visual sides; the glyphs must mirror with them so
70
- * "Previous month" points outward (visually right) instead of inward.
71
- * Keyed on the dir attribute (dir="rtl"), same as MobileNav's drawer
72
- * transform — bare CSS `direction: rtl` alone won't trigger it.
73
- */
74
67
  navIcon: {
75
- display: 'inline-flex',
76
- transform: {
77
- default: null,
78
- ':is([dir="rtl"] *)': 'scaleX(-1)',
79
- },
68
+ width: spacingVars['--spacing-4'],
69
+ height: spacingVars['--spacing-4'],
80
70
  },
81
71
  });
82
72
 
@@ -20,15 +20,15 @@ export const docs = {
20
20
  ],
21
21
  anatomy: [
22
22
  {name: 'Scroll container', required: true, description: 'The horizontal overflow area that holds all items.'},
23
- {name: 'Items', required: true, description: 'The children rendered in a row inside the scroll container. With hasSnap, each item snaps to the start edge.'},
23
+ {name: 'Items', required: true, description: 'The children rendered in a row. Each item is animated with a scroll-driven scale effect.'},
24
24
  {name: 'Fade edges', required: false, description: 'Gradient fades on the left and right edges that indicate more content is available. Enabled by default, disable with hasEdgeFade={false}.'},
25
- {name: 'Navigation buttons', required: false, description: 'Prev/next buttons that appear when the content can scroll in that direction. Enabled by default, disable with hasButtons={false}.'},
25
+ {name: 'Navigation buttons', required: false, description: 'Prev/next buttons that appear on hover. Enabled by default, disable with hasButtons={false}.'},
26
26
  ],
27
27
  },
28
28
  props: [
29
29
  {name: 'children', type: 'ReactNode', description: 'Carousel items rendered in a horizontal scroll container.', required: true},
30
30
  {name: 'gap', type: "0 | 0.5 | 1 | 1.5 | 2 | 3 | 4", description: 'Gap between items using the spacing token scale.', default: '1'},
31
- {name: 'hasButtons', type: 'boolean', description: 'Show prev/next navigation buttons when content is scrollable.', default: 'true'},
31
+ {name: 'hasButtons', type: 'boolean', description: 'Show prev/next navigation buttons on hover (desktop only).', default: 'true'},
32
32
  {name: 'hasEdgeFade', type: 'boolean', description: 'Show a gradient edge-fade mask when content overflows, signalling that more items exist off-screen.', default: 'true'},
33
33
  {name: 'hasSnap', type: 'boolean', description: 'Enable scroll-snap so each child snaps to the start edge.', default: 'false'},
34
34
  {name: 'padding', type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10', description: 'Inline padding inside the scroll container, with matching scroll-padding so snap points align to the content edge.'},
@@ -59,20 +59,20 @@ export const docs = {
59
59
  export const docsZh = {
60
60
  usage: {
61
61
  description:
62
- 'Carousel 在项目超出可用宽度时将一行项目水平滚动。适用于卡片网格、图片画廊、产品列表,或任何希望无需占据整页即可浏览的项目集合。',
62
+ 'Carousel scrolls a row of items horizontally when they overflow the available width. Use it for card grids, image galleries, product lists, or any set of items that should be browsable without taking up the full page.',
63
63
  bestPractices: [
64
- {guidance: true, description: '当每个项目应精确对齐到起始边缘时启用滚动吸附,例如画廊或产品列表。'},
65
- {guidance: true, description: '始终提供描述轮播内容的 aria-label,例如"精选产品""团队成员"'},
66
- {guidance: true, description: '使用一致的间距和项目宽度,让轮播看起来是有意为之,而不是内容意外溢出。'},
67
- {guidance: false, description: '将每位用户都必须看到的内容放入轮播。并非所有人都会水平滚动,关键内容应放在首屏。'},
68
- {guidance: false, description: '自动轮播项目。让用户按自己的节奏滚动。'},
69
- {guidance: false, description: '嵌套轮播。轮播中嵌套轮播会造成困惑并破坏键盘导航。'},
64
+ {guidance: true, description: 'Enable scroll-snap when each item should land precisely at the start edge, like a gallery or product list.'},
65
+ {guidance: true, description: 'Always provide an aria-label that describes what the carousel contains, like "Featured products" or "Team members".'},
66
+ {guidance: true, description: 'Use a consistent gap and item width so the carousel looks intentional, not like content overflowing by accident.'},
67
+ {guidance: false, description: 'Use a carousel for content every user must see. Not everyone scrolls horizontally, so put critical content above the fold.'},
68
+ {guidance: false, description: 'Auto-advance items. Let the user scroll at their own pace.'},
69
+ {guidance: false, description: 'Nest carousels. A carousel inside a carousel is confusing and breaks keyboard navigation.'},
70
70
  ],
71
71
  },
72
72
  propDescriptions: {
73
73
  children: '在水平滚动容器中渲染的轮播项目。',
74
74
  gap: '使用间距令牌比例的项目间距。',
75
- hasButtons: '内容可滚动时显示上一个/下一个导航按钮。',
75
+ hasButtons: '悬停时显示上一个/下一个导航按钮(仅桌面端)。',
76
76
  hasEdgeFade: '内容溢出时显示渐变边缘遮罩,提示屏幕外还有更多项目。',
77
77
  hasSnap: '启用滚动吸附,使每个子元素吸附到起始边缘。',
78
78
  padding: '滚动容器内的内联内边距,并设置匹配的 scroll-padding,使吸附点与内容边缘对齐。',
@@ -98,7 +98,7 @@ export const docsDense = {
98
98
  propDescriptions: {
99
99
  children: 'carousel items in horizontal scroll',
100
100
  gap: 'item spacing via spacing token scale',
101
- hasButtons: 'prev/next buttons when content is scrollable',
101
+ hasButtons: 'prev/next buttons on hover (desktop)',
102
102
  hasEdgeFade: 'gradient edge-fade mask on overflow',
103
103
  hasSnap: 'scroll-snap; children snap to start edge',
104
104
  padding: 'inline padding; scroll-padding keeps snap points on content edge',
@@ -38,19 +38,6 @@ describe('Carousel', () => {
38
38
  expect(region).toHaveAttribute('aria-roledescription', 'carousel');
39
39
  });
40
40
 
41
- it('makes the scroll container keyboard-focusable', () => {
42
- render(
43
- <Carousel aria-label="Photos">
44
- <div>Item</div>
45
- </Carousel>,
46
- );
47
- // The inner scroll container overflows, so it must be reachable by
48
- // keyboard (axe: scrollable-region-focusable).
49
- const region = screen.getByRole('region', {name: 'Photos'});
50
- const scroller = region.firstElementChild;
51
- expect(scroller).toHaveAttribute('tabindex', '0');
52
- });
53
-
54
41
  it('applies data-testid', () => {
55
42
  render(
56
43
  <Carousel data-testid="my-carousel">
@@ -340,7 +340,6 @@ export function Carousel({
340
340
  {...htmlProps}>
341
341
  <div
342
342
  ref={composedRef}
343
- tabIndex={0}
344
343
  {...stylex.props(
345
344
  styles.scroller,
346
345
  gapStyles[gap],
@@ -17,7 +17,7 @@ export const docs = {
17
17
  {className: 'astryx-chat-composer', visualProps: ['density']},
18
18
  {className: 'astryx-chat-composer-input'},
19
19
  {className: 'astryx-chat-composer-drawer', visualProps: ['collapsed']},
20
- {className: 'astryx-chat-message', visualProps: ['sender', 'density']},
20
+ {className: 'astryx-chat-message', visualProps: ['sender']},
21
21
  {
22
22
  className: 'astryx-chat-message-bubble',
23
23
  visualProps: ['sender', 'variant', 'density'],
@@ -97,59 +97,4 @@ describe('ChatToolCalls', () => {
97
97
  );
98
98
  expect(screen.getByText('git status')).toBeInTheDocument();
99
99
  });
100
-
101
- it('exposes no aria-expanded on a call row without resultDetail', () => {
102
- const {container} = render(
103
- <ChatToolCalls calls={[{name: 'bash', status: 'complete'}]} />,
104
- );
105
- expect(container.querySelector('[aria-expanded]')).toBeNull();
106
- });
107
-
108
- it('wires disclosure semantics on an expandable call row', () => {
109
- render(
110
- <ChatToolCalls
111
- calls={[
112
- {
113
- name: 'readFile',
114
- status: 'complete',
115
- resultDetail: <div>file contents here</div>,
116
- },
117
- ]}
118
- />,
119
- );
120
- const row = screen.getByRole('button');
121
- expect(row).toHaveAttribute('aria-expanded', 'false');
122
- // Detail panel is conditionally mounted, so no aria-controls while closed.
123
- expect(row).not.toHaveAttribute('aria-controls');
124
- expect(screen.queryByText('file contents here')).not.toBeInTheDocument();
125
-
126
- fireEvent.click(row);
127
-
128
- expect(row).toHaveAttribute('aria-expanded', 'true');
129
- const detailId = row.getAttribute('aria-controls');
130
- expect(detailId).toBeTruthy();
131
- const panel = document.getElementById(detailId as string);
132
- expect(panel).not.toBeNull();
133
- expect(panel).toHaveTextContent('file contents here');
134
- });
135
-
136
- it('points the group header aria-controls at the content region', () => {
137
- render(
138
- <ChatToolCalls
139
- defaultIsExpanded={true}
140
- calls={[
141
- {name: 'searchCode', status: 'complete'},
142
- {name: 'readFile', status: 'complete'},
143
- ]}
144
- />,
145
- );
146
- const header = screen.getByRole('button');
147
- expect(header).toHaveAttribute('aria-expanded', 'true');
148
- const regionId = header.getAttribute('aria-controls');
149
- expect(regionId).toBeTruthy();
150
- const region = document.getElementById(regionId as string);
151
- expect(region).not.toBeNull();
152
- expect(region).toHaveTextContent('searchCode');
153
- expect(region).toHaveTextContent('readFile');
154
- });
155
100
  });
@@ -18,7 +18,7 @@
18
18
  * for builders to wire up.
19
19
  */
20
20
 
21
- import React, {useState, useCallback, useId, type ReactNode} from 'react';
21
+ import React, {useState, useCallback, type ReactNode} from 'react';
22
22
  import type {BaseProps} from '../BaseProps';
23
23
  import * as stylex from '@stylexjs/stylex';
24
24
  import {
@@ -41,7 +41,11 @@ import {themeProps} from '../utils/themeProps';
41
41
  // Types
42
42
  // =============================================================================
43
43
 
44
- export type ChatToolCallStatus = 'pending' | 'running' | 'complete' | 'error';
44
+ export type ChatToolCallStatus =
45
+ | 'pending'
46
+ | 'running'
47
+ | 'complete'
48
+ | 'error';
45
49
 
46
50
  export interface ChatToolCallItem {
47
51
  /** Tool/function name. */
@@ -352,7 +356,6 @@ function CallRow({call}: {call: ChatToolCallItem}) {
352
356
  const status = call.status ?? 'complete';
353
357
  const hasDetail = call.resultDetail != null;
354
358
  const [isDetailOpen, setIsDetailOpen] = useState(false);
355
- const detailId = useId();
356
359
 
357
360
  const toggleDetail = hasDetail
358
361
  ? () => setIsDetailOpen(prev => !prev)
@@ -362,8 +365,6 @@ function CallRow({call}: {call: ChatToolCallItem}) {
362
365
  <div
363
366
  role={hasDetail ? 'button' : undefined}
364
367
  tabIndex={hasDetail ? 0 : undefined}
365
- aria-expanded={hasDetail ? isDetailOpen : undefined}
366
- aria-controls={hasDetail && isDetailOpen ? detailId : undefined}
367
368
  onClick={toggleDetail}
368
369
  onKeyDown={
369
370
  hasDetail
@@ -398,7 +399,11 @@ function CallRow({call}: {call: ChatToolCallItem}) {
398
399
  </span>
399
400
  <span {...stylex.props(styles.callName)}>{call.name}</span>
400
401
  {call.node != null && (
401
- <Badge label={call.node} variant="neutral" xstyle={styles.nodePill} />
402
+ <Badge
403
+ label={call.node}
404
+ variant="neutral"
405
+ xstyle={styles.nodePill}
406
+ />
402
407
  )}
403
408
  {call.target != null && (
404
409
  <span {...stylex.props(styles.callLabel)}>{call.target}</span>
@@ -443,7 +448,7 @@ function CallRow({call}: {call: ChatToolCallItem}) {
443
448
  <div>
444
449
  {row}
445
450
  {isDetailOpen && (
446
- <div id={detailId} {...stylex.props(styles.callDetailContent)}>
451
+ <div {...stylex.props(styles.callDetailContent)}>
447
452
  {call.resultDetail}
448
453
  </div>
449
454
  )}
@@ -492,7 +497,6 @@ export function ChatToolCalls(props: ChatToolCallsProps) {
492
497
 
493
498
  const autoDefault = defaultIsExpanded ?? false;
494
499
  const [internalExpanded, setInternalExpanded] = useState(autoDefault);
495
- const contentId = useId();
496
500
  const isControlled = controlledExpanded !== undefined;
497
501
  const isExpanded = isControlled ? controlledExpanded : internalExpanded;
498
502
 
@@ -544,7 +548,6 @@ export function ChatToolCalls(props: ChatToolCallsProps) {
544
548
  role="button"
545
549
  tabIndex={0}
546
550
  aria-expanded={isExpanded}
547
- aria-controls={contentId}
548
551
  onClick={toggle}
549
552
  onKeyDown={e => {
550
553
  if (e.key === 'Enter' || e.key === ' ') {
@@ -610,7 +613,6 @@ export function ChatToolCalls(props: ChatToolCallsProps) {
610
613
 
611
614
  {/* Expanded: all calls with full metadata */}
612
615
  <div
613
- id={contentId}
614
616
  {...stylex.props(
615
617
  styles.groupContent,
616
618
  isExpanded && styles.groupContentExpanded,
@@ -125,7 +125,7 @@ export const docs = {
125
125
  theming: {
126
126
  targets: [
127
127
  {className: 'astryx-checkbox-input', visualProps: ['size']},
128
- {className: 'astryx-checkbox', visualProps: ['size'], states: ['checked', 'disabled']},
128
+ {className: 'astryx-checkbox'},
129
129
  ],
130
130
  },
131
131
  usage: {
@@ -178,7 +178,7 @@ export const docsZh = {
178
178
  {name: 'isLoading', type: 'boolean', description: '复选框是否处于加载状态。显示旋转器并阻止交互。', default: 'false'},
179
179
  {name: 'isDisabled', type: 'boolean', description: '复选框是否禁用。', default: 'false'},
180
180
  {name: 'htmlName', type: 'string', description: '底层复选框输入的 HTML name 属性,用于表单提交(勾选时提交 "on")。'},
181
- {name: 'disabledMessage', type: 'string', description: 'Explains why the checkbox is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkbox focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxInput in Tooltip: disabled controls swallow the hover events an external Tooltip needs.'},
181
+ {name: 'disabledMessage', type: 'string', description: 'Explains why the checkbox is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkbox focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxInput in Tooltip disabled controls swallow the hover events an external Tooltip needs.'},
182
182
  {name: 'isReadOnly', type: 'boolean', description: '复选框是否为只读。以完整不透明度显示当前状态但阻止交互。与 isDisabled 不同,只读复选框不会变暗。', default: 'false'},
183
183
  {name: 'isOptional', type: 'boolean', description: '字段是否可选。与 isRequired 互斥。', default: 'false'},
184
184
  {name: 'isRequired', type: 'boolean', description: '复选框是否必填。与 isOptional 互斥。', default: 'false'},
@@ -200,7 +200,7 @@ export const docsZh = {
200
200
  'size',
201
201
  ],
202
202
  },
203
- {className: 'astryx-checkbox', visualProps: ['size'], states: ['checked', 'disabled']},
203
+ {className: 'astryx-checkbox'},
204
204
  ],
205
205
  },
206
206
  };