@astryxdesign/core 0.1.3 → 0.1.4-canary.02643db

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 (168) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/Button/Button.d.ts.map +1 -1
  3. package/dist/Button/Button.js +29 -8
  4. package/dist/Calendar/Calendar.d.ts.map +1 -1
  5. package/dist/Calendar/Calendar.js +49 -20
  6. package/dist/Calendar/dayCellUtils.d.ts +67 -5
  7. package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
  8. package/dist/Calendar/dayCellUtils.js +139 -18
  9. package/dist/CheckboxInput/CheckboxInput.d.ts +6 -1
  10. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.js +7 -1
  12. package/dist/Code/Code.d.ts +15 -1
  13. package/dist/Code/Code.d.ts.map +1 -1
  14. package/dist/Code/Code.js +32 -2
  15. package/dist/Code/index.d.ts +1 -1
  16. package/dist/Code/index.d.ts.map +1 -1
  17. package/dist/CodeBlock/index.d.ts +1 -1
  18. package/dist/CodeBlock/index.d.ts.map +1 -1
  19. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +1 -1
  20. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  21. package/dist/CommandPalette/CommandPaletteEmpty.js +24 -4
  22. package/dist/ContextMenu/ContextMenu.d.ts +9 -1
  23. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  24. package/dist/ContextMenu/ContextMenu.js +6 -3
  25. package/dist/DateRangeInput/DateRangeInput.js +2 -2
  26. package/dist/DateTimeInput/DateTimeInput.d.ts +0 -5
  27. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  28. package/dist/Divider/Divider.d.ts +0 -22
  29. package/dist/Divider/Divider.d.ts.map +1 -1
  30. package/dist/Markdown/parser.d.ts +7 -0
  31. package/dist/Markdown/parser.d.ts.map +1 -1
  32. package/dist/Markdown/parser.js +300 -6
  33. package/dist/MultiSelector/MultiSelector.d.ts +7 -1
  34. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  35. package/dist/MultiSelector/MultiSelector.js +10 -1
  36. package/dist/RadioList/RadioList.d.ts +7 -1
  37. package/dist/RadioList/RadioList.d.ts.map +1 -1
  38. package/dist/RadioList/RadioList.js +3 -1
  39. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  40. package/dist/RadioList/RadioListItem.js +6 -1
  41. package/dist/Selector/Selector.d.ts +6 -0
  42. package/dist/Selector/Selector.d.ts.map +1 -1
  43. package/dist/Selector/Selector.js +9 -0
  44. package/dist/Slider/Slider.d.ts +6 -0
  45. package/dist/Slider/Slider.d.ts.map +1 -1
  46. package/dist/Slider/Slider.js +10 -1
  47. package/dist/Spinner/Spinner.d.ts.map +1 -1
  48. package/dist/Spinner/Spinner.js +1 -1
  49. package/dist/Switch/Switch.d.ts +6 -1
  50. package/dist/Switch/Switch.d.ts.map +1 -1
  51. package/dist/Switch/Switch.js +7 -1
  52. package/dist/Table/BaseTable.d.ts.map +1 -1
  53. package/dist/Table/BaseTable.js +8 -1
  54. package/dist/Table/TableCell.d.ts.map +1 -1
  55. package/dist/Table/TableCell.js +73 -4
  56. package/dist/Table/index.d.ts +2 -0
  57. package/dist/Table/index.d.ts.map +1 -1
  58. package/dist/Table/index.js +1 -0
  59. package/dist/Table/plugins/rowExpansion/index.d.ts +3 -0
  60. package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -0
  61. package/dist/Table/plugins/rowExpansion/index.js +3 -0
  62. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +90 -0
  63. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -0
  64. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +398 -0
  65. package/dist/Table/tableContextMenu.d.ts +5 -1
  66. package/dist/Table/tableContextMenu.d.ts.map +1 -1
  67. package/dist/Table/tableContextMenu.js +10 -3
  68. package/dist/Text/Text.d.ts +1 -1
  69. package/dist/Text/Text.d.ts.map +1 -1
  70. package/dist/Text/Text.js +4 -3
  71. package/dist/Text/text.stylex.d.ts +35 -0
  72. package/dist/Text/text.stylex.d.ts.map +1 -1
  73. package/dist/Text/text.stylex.js +52 -1
  74. package/dist/Tokenizer/Tokenizer.d.ts +6 -1
  75. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  76. package/dist/Tokenizer/Tokenizer.js +10 -1
  77. package/dist/astryx.css +16 -0
  78. package/dist/astryx.umd.js +51 -49
  79. package/dist/astryx.umd.js.map +4 -4
  80. package/dist/docs-types.d.ts +12 -0
  81. package/dist/docs-types.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/AspectRatio/AspectRatio.doc.mjs +1 -1
  84. package/src/Avatar/Avatar.doc.mjs +2 -2
  85. package/src/Button/Button.tsx +39 -6
  86. package/src/ButtonGroup/ButtonGroup.doc.mjs +1 -1
  87. package/src/Calendar/Calendar.test.tsx +106 -0
  88. package/src/Calendar/Calendar.tsx +59 -22
  89. package/src/Calendar/dayCellUtils.test.ts +306 -1
  90. package/src/Calendar/dayCellUtils.ts +206 -13
  91. package/src/Card/Card.doc.mjs +1 -1
  92. package/src/Chat/useChatNewMessages.test.tsx +4 -4
  93. package/src/CheckboxInput/CheckboxInput.doc.mjs +9 -1
  94. package/src/CheckboxInput/CheckboxInput.test.tsx +36 -0
  95. package/src/CheckboxInput/CheckboxInput.tsx +11 -0
  96. package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
  97. package/src/Code/Code.test.tsx +54 -0
  98. package/src/Code/Code.tsx +48 -4
  99. package/src/Code/index.ts +1 -1
  100. package/src/CodeBlock/index.ts +1 -1
  101. package/src/CommandPalette/CommandPaletteEmpty.tsx +15 -1
  102. package/src/ContextMenu/ContextMenu.tsx +17 -1
  103. package/src/DateInput/DateInput.doc.mjs +6 -1
  104. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  105. package/src/DateRangeInput/DateRangeInput.tsx +2 -2
  106. package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
  107. package/src/DateTimeInput/DateTimeInput.tsx +1 -6
  108. package/src/Divider/Divider.tsx +1 -23
  109. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -3
  110. package/src/EmptyState/EmptyState.doc.mjs +1 -1
  111. package/src/Field/Field.doc.mjs +1 -1
  112. package/src/FileInput/FileInput.doc.mjs +1 -1
  113. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
  114. package/src/Grid/Grid.doc.mjs +3 -3
  115. package/src/Layout/__tests__/edgeCompensation.test.tsx +72 -0
  116. package/src/Markdown/Markdown.test.tsx +23 -0
  117. package/src/Markdown/incremental.test.ts +36 -0
  118. package/src/Markdown/parser.test.ts +210 -0
  119. package/src/Markdown/parser.ts +301 -6
  120. package/src/MobileNav/MobileNav.doc.mjs +20 -0
  121. package/src/MultiSelector/MultiSelector.doc.mjs +14 -1
  122. package/src/MultiSelector/MultiSelector.test.tsx +33 -0
  123. package/src/MultiSelector/MultiSelector.tsx +20 -0
  124. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  125. package/src/RadioList/RadioList.doc.mjs +7 -1
  126. package/src/RadioList/RadioList.test.tsx +46 -0
  127. package/src/RadioList/RadioList.tsx +10 -1
  128. package/src/RadioList/RadioListItem.tsx +4 -0
  129. package/src/Section/Section.test.tsx +4 -10
  130. package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
  131. package/src/Selector/Selector.doc.mjs +12 -1
  132. package/src/Selector/Selector.test.tsx +30 -0
  133. package/src/Selector/Selector.tsx +18 -0
  134. package/src/Slider/Slider.doc.mjs +14 -2
  135. package/src/Slider/Slider.test.tsx +30 -0
  136. package/src/Slider/Slider.tsx +20 -0
  137. package/src/Spinner/Spinner.tsx +1 -0
  138. package/src/Switch/Switch.doc.mjs +14 -2
  139. package/src/Switch/Switch.test.tsx +38 -0
  140. package/src/Switch/Switch.tsx +12 -0
  141. package/src/TabList/TabList.doc.mjs +3 -3
  142. package/src/Table/BaseTable.tsx +9 -1
  143. package/src/Table/Table.test.tsx +23 -0
  144. package/src/Table/TableCell.tsx +87 -8
  145. package/src/Table/index.ts +5 -0
  146. package/src/Table/plugins/rowExpansion/index.ts +7 -0
  147. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +155 -0
  148. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +590 -0
  149. package/src/Table/tableContextMenu.test.tsx +27 -3
  150. package/src/Table/tableContextMenu.tsx +20 -2
  151. package/src/Table/useTablePagination.doc.mjs +4 -1
  152. package/src/Table/useTableRowExpansion.doc.mjs +80 -0
  153. package/src/Table/useTableSortable.doc.mjs +4 -1
  154. package/src/Text/Text.doc.mjs +2 -2
  155. package/src/Text/Text.test.tsx +11 -0
  156. package/src/Text/Text.tsx +4 -2
  157. package/src/Text/text.stylex.ts +41 -0
  158. package/src/TextArea/TextArea.doc.mjs +1 -1
  159. package/src/TextInput/TextInput.doc.mjs +1 -1
  160. package/src/TimeInput/TimeInput.doc.mjs +11 -6
  161. package/src/Tokenizer/Tokenizer.doc.mjs +14 -2
  162. package/src/Tokenizer/Tokenizer.test.tsx +33 -0
  163. package/src/Tokenizer/Tokenizer.tsx +19 -0
  164. package/src/Toolbar/Toolbar.test.tsx +1 -1
  165. package/src/Typeahead/BaseTypeahead.doc.mjs +10 -0
  166. package/src/Typeahead/Typeahead.doc.mjs +6 -1
  167. package/src/docs-types.ts +12 -0
  168. package/src/hooks/useKeyboardHint.doc.mjs +6 -6
@@ -392,4 +392,40 @@ describe('CheckboxInput', () => {
392
392
  expect(screen.getByRole('checkbox')).toBeDisabled();
393
393
  });
394
394
  });
395
+ describe('form participation', () => {
396
+ it('submits under htmlName when checked', () => {
397
+ const {container} = render(
398
+ <form>
399
+ <CheckboxInput label="Terms" htmlName="terms" value={true} onChange={() => {}} />
400
+ </form>,
401
+ );
402
+ const data = new FormData(container.querySelector('form')!);
403
+ expect(data.get('terms')).toBe('on');
404
+ });
405
+
406
+ it('is excluded from form data when disabled, even with a disabledMessage', () => {
407
+ const {container} = render(
408
+ <form>
409
+ <CheckboxInput
410
+ label="Terms"
411
+ htmlName="terms"
412
+ value={true}
413
+ onChange={() => {}}
414
+ isDisabled
415
+ disabledMessage="Locked"
416
+ />
417
+ </form>,
418
+ );
419
+ expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
420
+ });
421
+
422
+ it('submits nothing when unchecked', () => {
423
+ const {container} = render(
424
+ <form>
425
+ <CheckboxInput label="Terms" htmlName="terms" value={false} onChange={() => {}} />
426
+ </form>,
427
+ );
428
+ expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
429
+ });
430
+ });
395
431
  });
@@ -272,6 +272,12 @@ export interface CheckboxInputProps extends Omit<BaseProps, 'onChange'> {
272
272
  * @default false
273
273
  */
274
274
  isDisabled?: boolean;
275
+
276
+ /**
277
+ * The HTML name attribute for the underlying checkbox input.
278
+ * Useful for form submissions.
279
+ */
280
+ htmlName?: string;
275
281
  /**
276
282
  * Explains why the checkbox is disabled. When set together with
277
283
  * `isDisabled`, the checkbox shows a tooltip with this text on hover and
@@ -374,6 +380,7 @@ export function CheckboxInput({
374
380
  isLoading = false,
375
381
  value,
376
382
  isDisabled = false,
383
+ htmlName,
377
384
  disabledMessage,
378
385
  isReadOnly = false,
379
386
  isOptional = false,
@@ -484,6 +491,10 @@ export function CheckboxInput({
484
491
  )}
485
492
  id={id}
486
493
  type="checkbox"
494
+ // Withhold the name while disabled: with a disabledMessage the
495
+ // input stays focusable (not natively disabled), and a disabled
496
+ // control must not submit.
497
+ name={isDisabled ? undefined : htmlName}
487
498
  checked={isChecked}
488
499
  // With a disabledMessage the checkbox keeps focusability via
489
500
  // aria-disabled so the reason is focus-discoverable; toggling is
@@ -81,7 +81,7 @@ export const docs = {
81
81
  name: 'disabledMessage',
82
82
  type: 'string',
83
83
  description:
84
- 'Explains why the group is disabled. Applies to the whole-group disabled state (isDisabled), not per item. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkboxes focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxList in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
84
+ 'Explains why the group is disabled. Applies to the whole-group disabled state (isDisabled), not per item. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkboxes focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxList in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
85
85
  },
86
86
  {
87
87
  name: 'status',
@@ -0,0 +1,54 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Code.test.tsx
5
+ * @input Uses vitest, @testing-library/react, Code component
6
+ * @output Unit tests for Code component behavior
7
+ * @position Testing; validates Code.tsx implementation
8
+ *
9
+ * SYNC: When Code.tsx changes, update tests to match new behavior
10
+ */
11
+
12
+ import {describe, it, expect, vi} from 'vitest';
13
+ import {render, screen} from '@testing-library/react';
14
+ import {Code} from './Code';
15
+
16
+ describe('Code', () => {
17
+ it('renders children inside a <code> element', () => {
18
+ render(<Code>const x = 1</Code>);
19
+ const el = screen.getByText('const x = 1');
20
+ expect(el.tagName).toBe('CODE');
21
+ });
22
+
23
+ it('forwards ref to the root element', () => {
24
+ const ref = vi.fn();
25
+ render(<Code ref={ref}>code</Code>);
26
+ expect(ref).toHaveBeenCalledWith(expect.any(HTMLElement));
27
+ });
28
+
29
+ it('defaults color to primary', () => {
30
+ render(<Code>code</Code>);
31
+ expect(screen.getByText('code')).toHaveClass('astryx-code', 'primary');
32
+ });
33
+
34
+ it('applies the secondary color', () => {
35
+ render(<Code color="secondary">code</Code>);
36
+ expect(screen.getByText('code')).toHaveClass('astryx-code', 'secondary');
37
+ });
38
+
39
+ it('applies the inherit color', () => {
40
+ render(<Code color="inherit">code</Code>);
41
+ expect(screen.getByText('code')).toHaveClass('astryx-code', 'inherit');
42
+ });
43
+
44
+ it('adds a size class when size="inherit" (font-size + line-height inherit)', () => {
45
+ const {rerender} = render(<Code>code</Code>);
46
+ const defaultClass = screen.getByText('code').getAttribute('class');
47
+
48
+ rerender(<Code size="inherit">code</Code>);
49
+ const inheritClass = screen.getByText('code').getAttribute('class');
50
+
51
+ // size="inherit" adds an extra StyleX class beyond the default rendering.
52
+ expect(inheritClass).not.toEqual(defaultClass);
53
+ });
54
+ });
package/src/Code/Code.tsx CHANGED
@@ -42,9 +42,46 @@ const styles = stylex.create({
42
42
  },
43
43
  });
44
44
 
45
+ const colorStyles = stylex.create({
46
+ primary: {
47
+ color: colorVars['--color-text-primary'],
48
+ },
49
+ secondary: {
50
+ color: colorVars['--color-text-secondary'],
51
+ },
52
+ inherit: {
53
+ color: 'inherit',
54
+ },
55
+ });
56
+
57
+ const sizeStyles = stylex.create({
58
+ // Adopt the surrounding text's font-size and line-height, for inline code
59
+ // that should match differently-sized text around it.
60
+ inherit: {
61
+ fontSize: 'inherit',
62
+ lineHeight: 'inherit',
63
+ },
64
+ });
65
+
66
+ /** Text color for {@link Code}, mirroring the primary/secondary/inherit subset of Text. */
67
+ export type CodeColor = 'primary' | 'secondary' | 'inherit';
68
+
69
+ /** Font size for {@link Code}. `'inherit'` adopts the surrounding text size. */
70
+ export type CodeSize = 'inherit';
71
+
45
72
  export interface CodeProps extends BaseProps<HTMLElement> {
46
73
  /** Ref forwarded to the root element */
47
74
  ref?: React.Ref<HTMLElement>;
75
+ /**
76
+ * Text color. Mirrors the Text color subset.
77
+ * @default 'primary'
78
+ */
79
+ color?: CodeColor;
80
+ /**
81
+ * Font size. Set to `'inherit'` to adopt the surrounding text's font-size
82
+ * and line-height (useful for inline code inside larger/smaller text).
83
+ */
84
+ size?: CodeSize;
48
85
  /** Code content */
49
86
  children: ReactNode;
50
87
  }
@@ -64,6 +101,8 @@ export interface CodeProps extends BaseProps<HTMLElement> {
64
101
  */
65
102
  export function Code({
66
103
  children,
104
+ color = 'primary',
105
+ size,
67
106
  xstyle,
68
107
  className,
69
108
  style,
@@ -73,13 +112,18 @@ export function Code({
73
112
  return (
74
113
  <code
75
114
  ref={ref}
115
+ {...props}
76
116
  {...mergeProps(
77
- themeProps('code'),
78
- stylex.props(styles.base, xstyle),
117
+ themeProps('code', {color}),
118
+ stylex.props(
119
+ styles.base,
120
+ colorStyles[color],
121
+ size && sizeStyles[size],
122
+ xstyle,
123
+ ),
79
124
  className,
80
125
  style,
81
- )}
82
- data-testid={props['data-testid']}>
126
+ )}>
83
127
  {children}
84
128
  </code>
85
129
  );
package/src/Code/index.ts CHANGED
@@ -7,4 +7,4 @@
7
7
  * @position Entry point for @astryxdesign/core/Code subpath export
8
8
  */
9
9
 
10
- export {Code, type CodeProps} from './Code';
10
+ export {Code, type CodeProps, type CodeColor, type CodeSize} from './Code';
@@ -15,7 +15,7 @@ export {CodeBlock} from './CodeBlock';
15
15
  export type {CodeBlockProps} from './CodeBlock';
16
16
 
17
17
  export {Code} from '../Code';
18
- export type {CodeProps} from '../Code';
18
+ export type {CodeProps, CodeColor, CodeSize} from '../Code';
19
19
 
20
20
  export {
21
21
  tokenize,
@@ -20,7 +20,9 @@ import {
20
20
  typeScaleVars,
21
21
  typographyVars,
22
22
  } from '../theme/tokens.stylex';
23
+ import {mergeProps} from '../utils';
23
24
  import type {BaseProps} from '../BaseProps';
25
+ import {themeProps} from '../utils/themeProps';
24
26
 
25
27
  const styles = stylex.create({
26
28
  empty: {
@@ -64,9 +66,21 @@ export interface CommandPaletteEmptyProps extends BaseProps<HTMLDivElement> {
64
66
  export function CommandPaletteEmpty({
65
67
  ref,
66
68
  children,
69
+ xstyle,
70
+ className,
71
+ style,
72
+ ...props
67
73
  }: CommandPaletteEmptyProps) {
68
74
  return (
69
- <div ref={ref} {...stylex.props(styles.empty)}>
75
+ <div
76
+ ref={ref}
77
+ {...mergeProps(
78
+ themeProps('command-palette-empty'),
79
+ stylex.props(styles.empty, xstyle),
80
+ className,
81
+ style,
82
+ )}
83
+ {...props}>
70
84
  {children}
71
85
  </div>
72
86
  );
@@ -55,6 +55,7 @@ import {
55
55
  } from '../theme/tokens.stylex';
56
56
  import {mergeProps} from '../utils';
57
57
  import type {BaseProps} from '../BaseProps';
58
+ import type {StyleXStyles} from '../theme/types';
58
59
  import {themeProps} from '../utils/themeProps';
59
60
  import type {
60
61
  DropdownMenuOption,
@@ -114,6 +115,13 @@ export type ContextMenuOption = DropdownMenuOption;
114
115
  interface ContextMenuBaseProps extends BaseProps {
115
116
  /** Ref forwarded to the trigger wrapper element. */
116
117
  ref?: React.Ref<HTMLDivElement>;
118
+ /**
119
+ * Styles applied to the trigger wrapper element (the right-click target).
120
+ * By default the trigger is a plain block that hugs its content — pass a
121
+ * fill style (e.g. `width/height: 100%`) when the whole parent area should
122
+ * be right-clickable (as the Table does for full-cell context menus).
123
+ */
124
+ triggerXstyle?: StyleXStyles | StyleXStyles[];
117
125
  /** The trigger area — right-click on this to open the menu. */
118
126
  children: ReactNode;
119
127
  /** Custom menu width. @default '160px' */
@@ -184,6 +192,7 @@ export function ContextMenu({
184
192
  className,
185
193
  style,
186
194
  xstyle,
195
+ triggerXstyle,
187
196
  'data-testid': testId,
188
197
  ...props
189
198
  }: ContextMenuProps) {
@@ -382,7 +391,14 @@ export function ContextMenu({
382
391
  onContextMenu={handleContextMenu}
383
392
  {...longPressHandlers}
384
393
  data-testid={testId}
385
- {...stylex.props(styles.trigger)}>
394
+ {...stylex.props(
395
+ styles.trigger,
396
+ ...(triggerXstyle
397
+ ? Array.isArray(triggerXstyle)
398
+ ? triggerXstyle
399
+ : [triggerXstyle]
400
+ : []),
401
+ )}>
386
402
  {children}
387
403
  </div>
388
404
 
@@ -56,7 +56,7 @@ export const docs = {
56
56
  name: 'disabledMessage',
57
57
  type: 'string',
58
58
  description:
59
- 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateInput in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
59
+ 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
60
60
  },
61
61
  {
62
62
  name: 'value',
@@ -432,6 +432,11 @@ export const docsDense = {
432
432
  description:
433
433
  'Show a loading state with changeAction when the date triggers a server-side save.',
434
434
  },
435
+ {
436
+ guidance: true,
437
+ description:
438
+ 'Use inside InputGroup for a short static prefix or suffix, such as a due-date hint.',
439
+ },
435
440
  {
436
441
  guidance: false,
437
442
  description:
@@ -52,7 +52,7 @@ export const docs = {
52
52
  name: 'disabledMessage',
53
53
  type: 'string',
54
54
  description:
55
- 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateRangeInput in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
55
+ 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateRangeInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
56
56
  },
57
57
  {
58
58
  name: 'value',
@@ -146,8 +146,8 @@ const styles = stylex.create({
146
146
  },
147
147
  },
148
148
  fontFamily: typographyVars['--font-family-body'],
149
- fontSize: typeScaleVars['--text-body-size'],
150
- lineHeight: typeScaleVars['--text-body-leading'],
149
+ fontSize: typeScaleVars['--text-label-size'],
150
+ lineHeight: typeScaleVars['--text-label-leading'],
151
151
  color: colorVars['--color-text-primary'],
152
152
  cursor: 'pointer',
153
153
  textAlign: 'start',
@@ -58,7 +58,7 @@ export const docs = {
58
58
  name: 'disabledMessage',
59
59
  type: 'string',
60
60
  description:
61
- 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateTimeInput in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
61
+ 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled DateTimeInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
62
62
  },
63
63
  {
64
64
  name: 'value',
@@ -72,7 +72,7 @@ import {
72
72
  plainDateFormat,
73
73
  DATE_FORMAT_LONG,
74
74
  } from '../utils/plainDate';
75
- import type {StyleXStyles} from '@stylexjs/stylex';
75
+
76
76
  import type {BaseProps} from '../BaseProps';
77
77
  import type {SizeValue} from '../utils/types';
78
78
  import {useSize} from '../SizeContext/SizeContext';
@@ -344,11 +344,6 @@ export interface DateTimeInputProps extends Omit<
344
344
  * @default 1
345
345
  */
346
346
  numberOfMonths?: 1 | 2;
347
-
348
- /**
349
- * Style overrides applied to the outer row container.
350
- */
351
- xstyle?: StyleXStyles;
352
347
  }
353
348
 
354
349
  function splitDateTime(dt: ISODateTimeString | undefined): {
@@ -18,7 +18,7 @@
18
18
  import type {ReactNode} from 'react';
19
19
  import type {BaseProps} from '../BaseProps';
20
20
  import * as stylex from '@stylexjs/stylex';
21
- import type {StyleXStyles} from '@stylexjs/stylex';
21
+
22
22
  import {
23
23
  colorVars,
24
24
  spacingVars,
@@ -80,28 +80,6 @@ export interface DividerProps extends BaseProps<HTMLDivElement> {
80
80
  * @default false
81
81
  */
82
82
  isFullBleed?: boolean;
83
-
84
- /**
85
- * StyleX styles created via `stylex.create()`. Merged with the component's
86
- * base styles inside a single `stylex.props()` call for optimal deduplication.
87
- *
88
- * @example
89
- * ```
90
- * const overrides = stylex.create({ root: { marginBottom: 8 } });
91
- * <Component xstyle={overrides.root} />
92
- * ```
93
- */
94
- xstyle?: StyleXStyles;
95
- /**
96
- * CSS class name(s) appended to the root element.
97
- * If you're using StyleX, prefer `xstyle` for optimal style deduplication.
98
- */
99
- className?: string;
100
- /**
101
- * Inline styles to apply to the root element. Spread after StyleX
102
- * inline styles, so these values take priority.
103
- */
104
- style?: React.CSSProperties;
105
83
  }
106
84
 
107
85
  const baseStyles = stylex.create({
@@ -73,7 +73,9 @@ describe('DropdownMenu', () => {
73
73
  // The popup exposes its own role="menu"; it must not be nested inside a
74
74
  // modal dialog, which would announce an unnamed dialog around the menu
75
75
  // while focus stays on the trigger.
76
- expect(screen.queryByRole('dialog', {hidden: true})).not.toBeInTheDocument();
76
+ expect(
77
+ screen.queryByRole('dialog', {hidden: true}),
78
+ ).not.toBeInTheDocument();
77
79
  expect(
78
80
  document.querySelector('[aria-modal="true"]'),
79
81
  ).not.toBeInTheDocument();
@@ -192,11 +194,11 @@ describe('DropdownMenu light-dismiss race', () => {
192
194
  <DropdownMenu
193
195
  button={{label: 'Actions'}}
194
196
  items={[{label: 'Edit'}]}
195
- data-testid="xds-dropdown-menu"
197
+ data-testid="astryx-dropdown-menu"
196
198
  />,
197
199
  );
198
200
 
199
- const trigger = screen.getByTestId('xds-dropdown-menu');
201
+ const trigger = screen.getByTestId('astryx-dropdown-menu');
200
202
  fireEvent.click(trigger); // open
201
203
  fireEvent.click(trigger); // close (stamps guard)
202
204
  fireEvent.click(trigger); // would re-open without guard
@@ -65,7 +65,7 @@ export const docs = {
65
65
  },
66
66
  theming: {
67
67
  targets: [
68
- {className: 'astryx-empty-state'},
68
+ {className: 'astryx-empty-state', visualProps: ['variant']},
69
69
  ],
70
70
  },
71
71
  usage: {
@@ -27,7 +27,7 @@ export const docs = {
27
27
  },
28
28
  theming: {
29
29
  targets: [
30
- {className: 'astryx-field'},
30
+ {className: 'astryx-field', visualProps: ['layout']},
31
31
  {className: 'astryx-field-label'},
32
32
  {className: 'astryx-field-status', visualProps: ['type', 'variant']},
33
33
  ],
@@ -92,7 +92,7 @@ export const docs = {
92
92
  name: 'disabledMessage',
93
93
  type: 'string',
94
94
  description:
95
- 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (opening the file picker stays blocked). Use this instead of wrapping a disabled FileInput in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
95
+ 'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (opening the file picker stays blocked). Use this instead of wrapping a disabled FileInput in Tooltip; disabled controls swallow the hover events an external Tooltip needs.',
96
96
  },
97
97
  {
98
98
  name: 'isLoading',
@@ -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
@@ -17,7 +17,7 @@ export const docs = {
17
17
  { guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
18
18
  { guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
19
19
  { guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
20
- { guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` — including inside `@media` queries — take effect.' },
20
+ { guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` (including inside `@media` queries) take effect.' },
21
21
  ],
22
22
  },
23
23
  theming: {
@@ -112,7 +112,7 @@ export const docsZh = {
112
112
  { guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
113
113
  { guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
114
114
  { guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
115
- { guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` — including inside `@media` queries — take effect.' },
115
+ { guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` (including inside `@media` queries) take effect.' },
116
116
  ],
117
117
  },
118
118
  };
@@ -128,7 +128,7 @@ export const docsDense = {
128
128
  { guidance: true, description: 'Use repeat: \'fill\' (the default) for consistent item widths. Use \'fit\' when items should stretch to fill leftover space.' },
129
129
  { guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
130
130
  { guidance: false, description: 'Use HStack with wrapping for grids; use Grid instead.' },
131
- { guidance: true, description: 'track templates use CSS-var indirection, not inline styles — xstyle/@media overrides of gridTemplateColumns work.' },
131
+ { guidance: true, description: 'track templates use CSS-var indirection, not inline styles, so xstyle/@media overrides of gridTemplateColumns work.' },
132
132
  ],
133
133
  },
134
134
  };
@@ -116,4 +116,76 @@ describe('Edge Compensation', () => {
116
116
  expect(dismissButton).toHaveAttribute(EDGE_COMP_ATTR);
117
117
  });
118
118
  });
119
+
120
+ // The container's compensation selector is a direct-child combinator:
121
+ // :has(> [data-astryx-edge-comp]:last-child)
122
+ // so an edge-comp marker only counts when it sits on a *direct* child of the
123
+ // slot. Attaching a tooltip must not bury the marker one level deeper — the
124
+ // Button uses the tooltip hook (no wrapper element) so the marker stays on the
125
+ // button itself (#2578).
126
+ describe('Tooltip attachment preserves marker discoverability', () => {
127
+ // Resolve the toolbar slot (the flex wrapper that carries the negative
128
+ // margin) — it is the direct child of the [role="toolbar"] row.
129
+ const findSlot = (
130
+ button: HTMLElement,
131
+ toolbar: HTMLElement,
132
+ ): HTMLElement => {
133
+ let node = button;
134
+ while (node.parentElement && node.parentElement !== toolbar) {
135
+ node = node.parentElement;
136
+ }
137
+ return node;
138
+ };
139
+
140
+ it('keeps the marker on a direct child of the slot without a tooltip', () => {
141
+ const {container} = render(
142
+ <Toolbar
143
+ label="Actions"
144
+ endContent={
145
+ <Button
146
+ label="Close"
147
+ variant="ghost"
148
+ isIconOnly
149
+ icon={<span>x</span>}
150
+ />
151
+ }
152
+ />,
153
+ );
154
+ const button = screen.getByRole('button', {name: 'Close'});
155
+ const toolbar = container.querySelector(
156
+ '[role="toolbar"]',
157
+ ) as HTMLElement;
158
+ const slot = findSlot(button, toolbar);
159
+ const directMarked = slot.querySelector(`:scope > [${EDGE_COMP_ATTR}]`);
160
+ expect(directMarked).not.toBeNull();
161
+ });
162
+
163
+ it('keeps the marker on a direct child of the slot when tooltip is set', () => {
164
+ const {container} = render(
165
+ <Toolbar
166
+ label="Actions"
167
+ endContent={
168
+ <Button
169
+ label="Close"
170
+ variant="ghost"
171
+ isIconOnly
172
+ icon={<span>x</span>}
173
+ tooltip="Close panel"
174
+ />
175
+ }
176
+ />,
177
+ );
178
+ const button = screen.getByRole('button', {name: 'Close'});
179
+ const toolbar = container.querySelector(
180
+ '[role="toolbar"]',
181
+ ) as HTMLElement;
182
+ const slot = findSlot(button, toolbar);
183
+ // The tooltip is attached via the hook (no wrapper element), so the
184
+ // marker stays on the button — a direct child of the container's slot —
185
+ // and the container's direct-child `:has(> [marker]:last-child)` selector
186
+ // still matches.
187
+ const directMarked = slot.querySelector(`:scope > [${EDGE_COMP_ATTR}]`);
188
+ expect(directMarked).not.toBeNull();
189
+ });
190
+ });
119
191
  });
@@ -95,6 +95,29 @@ describe('Markdown', () => {
95
95
  expect(link.getAttribute('rel')).toBe('noopener noreferrer');
96
96
  });
97
97
 
98
+ it('renders a footer reference-style link as an anchor', () => {
99
+ // The XDS parser previously had no reference-definition support, so this
100
+ // rendered as literal `[the docs][docs]` text with the definition leaking
101
+ // as a paragraph. It now resolves to a real anchor.
102
+ render(
103
+ <Markdown>
104
+ {'See [the docs][docs] here.\n\n[docs]: https://example.com/docs\n'}
105
+ </Markdown>,
106
+ );
107
+ const link = screen.getByText('the docs');
108
+ expect(link.tagName).toBe('A');
109
+ expect(link.getAttribute('href')).toBe('https://example.com/docs');
110
+ // The definition line must not leak into the rendered output.
111
+ expect(screen.queryByText(/\[docs\]:/)).toBeNull();
112
+ });
113
+
114
+ it('renders a shortcut reference-style link as an anchor', () => {
115
+ render(<Markdown>{'See [the docs].\n\n[the docs]: /docs'}</Markdown>);
116
+ const link = screen.getByText('the docs');
117
+ expect(link.tagName).toBe('A');
118
+ expect(link.getAttribute('href')).toBe('/docs');
119
+ });
120
+
98
121
  it('does not add target="_blank" to relative links', () => {
99
122
  render(<Markdown>{'[internal](/page)'}</Markdown>);
100
123
  const link = screen.getByText('internal');