@astryxdesign/core 0.2.0-canary.ea693ac → 0.2.0

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 (121) hide show
  1. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  2. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  3. package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -6
  4. package/dist/Button/Button.d.ts +1 -1
  5. package/dist/Button/Button.d.ts.map +1 -1
  6. package/dist/Button/Button.js +2 -4
  7. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  8. package/dist/CheckboxInput/CheckboxInput.js +2 -2
  9. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  10. package/dist/CheckboxList/CheckboxListItem.js +4 -19
  11. package/dist/CodeBlock/CodeBlock.js +2 -2
  12. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  13. package/dist/ContextMenu/ContextMenu.js +1 -10
  14. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +11 -8
  15. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  16. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -3
  17. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  18. package/dist/DropdownMenu/DropdownMenuRadioItem.js +1 -1
  19. package/dist/Field/FieldLabel.d.ts +1 -2
  20. package/dist/Field/FieldLabel.d.ts.map +1 -1
  21. package/dist/Field/FieldLabel.js +3 -7
  22. package/dist/Item/Item.d.ts +3 -16
  23. package/dist/Item/Item.d.ts.map +1 -1
  24. package/dist/Item/Item.js +6 -31
  25. package/dist/List/ListItem.d.ts +1 -11
  26. package/dist/List/ListItem.d.ts.map +1 -1
  27. package/dist/List/ListItem.js +0 -2
  28. package/dist/MetadataList/MetadataList.d.ts +1 -1
  29. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  30. package/dist/MetadataList/MetadataList.js +2 -4
  31. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  32. package/dist/MultiSelector/MultiSelector.js +2 -13
  33. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  34. package/dist/RadioList/RadioListItem.js +1 -1
  35. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  36. package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
  37. package/dist/Selector/Selector.d.ts.map +1 -1
  38. package/dist/Selector/Selector.js +0 -5
  39. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  40. package/dist/Skeleton/Skeleton.js +2 -2
  41. package/dist/Switch/Switch.d.ts.map +1 -1
  42. package/dist/Switch/Switch.js +4 -15
  43. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  44. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +1 -1
  45. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  46. package/dist/Toast/ToastViewport.js +9 -61
  47. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  48. package/dist/ToggleButton/ToggleButton.js +1 -3
  49. package/dist/Token/Token.d.ts +1 -4
  50. package/dist/Token/Token.d.ts.map +1 -1
  51. package/dist/Token/Token.js +43 -68
  52. package/dist/Typeahead/BaseTypeahead.d.ts +0 -9
  53. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  54. package/dist/Typeahead/BaseTypeahead.js +0 -2
  55. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/Typeahead.js +1 -8
  57. package/dist/astryx.css +3 -16
  58. package/dist/astryx.umd.js +48 -48
  59. package/dist/astryx.umd.js.map +4 -4
  60. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  61. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  62. package/dist/hooks/useKeyboardHint.js +2 -4
  63. package/locales/en.json +0 -32
  64. package/locales/pseudo.json +0 -24
  65. package/package.json +1 -1
  66. package/src/AvatarGroup/AvatarGroup.test.tsx +1 -21
  67. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -4
  68. package/src/Breadcrumbs/Breadcrumbs.test.tsx +1 -1
  69. package/src/Button/Button.test.tsx +0 -16
  70. package/src/Button/Button.tsx +2 -4
  71. package/src/CheckboxInput/CheckboxInput.test.tsx +0 -23
  72. package/src/CheckboxInput/CheckboxInput.tsx +2 -17
  73. package/src/CheckboxList/CheckboxList.test.tsx +3 -94
  74. package/src/CheckboxList/CheckboxListItem.tsx +10 -17
  75. package/src/CodeBlock/CodeBlock.test.tsx +0 -17
  76. package/src/CodeBlock/CodeBlock.tsx +2 -2
  77. package/src/ContextMenu/ContextMenu.test.tsx +2 -17
  78. package/src/ContextMenu/ContextMenu.tsx +1 -10
  79. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +12 -4
  80. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +10 -9
  81. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
  82. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +2 -20
  83. package/src/Field/FieldLabel.test.tsx +0 -25
  84. package/src/Field/FieldLabel.tsx +2 -11
  85. package/src/Item/Item.doc.mjs +0 -5
  86. package/src/Item/Item.test.tsx +0 -88
  87. package/src/Item/Item.tsx +11 -57
  88. package/src/List/ListItem.doc.mjs +0 -12
  89. package/src/List/ListItem.tsx +0 -13
  90. package/src/MetadataList/MetadataList.test.tsx +0 -24
  91. package/src/MetadataList/MetadataList.tsx +2 -6
  92. package/src/MultiSelector/MultiSelector.test.tsx +0 -76
  93. package/src/MultiSelector/MultiSelector.tsx +2 -22
  94. package/src/RadioList/RadioList.test.tsx +4 -26
  95. package/src/RadioList/RadioListItem.tsx +1 -8
  96. package/src/SegmentedControl/SegmentedControl.test.tsx +0 -28
  97. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -20
  98. package/src/Selector/Selector.test.tsx +0 -22
  99. package/src/Selector/Selector.tsx +1 -8
  100. package/src/Skeleton/Skeleton.test.tsx +0 -16
  101. package/src/Skeleton/Skeleton.tsx +1 -13
  102. package/src/Switch/Switch.test.tsx +0 -37
  103. package/src/Switch/Switch.tsx +3 -52
  104. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -19
  105. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -3
  106. package/src/Toast/ToastViewport.test.tsx +8 -218
  107. package/src/Toast/ToastViewport.tsx +12 -71
  108. package/src/ToggleButton/ToggleButton.test.tsx +0 -24
  109. package/src/ToggleButton/ToggleButton.tsx +1 -19
  110. package/src/Token/Token.test.tsx +0 -145
  111. package/src/Token/Token.tsx +33 -73
  112. package/src/Typeahead/BaseTypeahead.tsx +0 -12
  113. package/src/Typeahead/Typeahead.test.tsx +0 -62
  114. package/src/Typeahead/Typeahead.tsx +0 -6
  115. package/src/hooks/useKeyboardHint.test.tsx +0 -22
  116. package/src/hooks/useKeyboardHint.tsx +2 -6
  117. package/dist/Token/TokenLink.d.ts +0 -62
  118. package/dist/Token/TokenLink.d.ts.map +0 -1
  119. package/dist/Token/TokenLink.js +0 -76
  120. package/src/Token/TokenLink.tsx +0 -107
  121. package/src/__tests__/forcedColors.ts +0 -60
@@ -9,7 +9,6 @@
9
9
  * @position Core implementation; consumed by index.ts, tested by Token.test.tsx
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
12
- * - /packages/core/src/Token/TokenLink.tsx (interactive wrapper for href+onRemove)
13
12
  * - /packages/core/src/Token/Token.doc.mjs (props table, features, implementation notes)
14
13
  * - /packages/core/src/Token/Token.test.tsx (tests for new/changed behavior)
15
14
  * - /packages/core/src/Token/index.ts (exports if types change)
@@ -33,7 +32,6 @@ import {Icon} from '../Icon';
33
32
  import {mergeProps} from '../utils';
34
33
  import {useLinkComponent} from '../Link/useLinkComponent';
35
34
  import {useInteractiveRole} from '../hooks/useInteractiveRole';
36
- import {TokenLink} from './TokenLink';
37
35
  import type {BaseProps} from '../BaseProps';
38
36
  import {themeProps} from '../utils/themeProps';
39
37
  import {useTranslator} from '../i18n';
@@ -315,9 +313,7 @@ const colorStyles = stylex.create({
315
313
  * `<span>` container with an invisible `<button>` when `onClick` is provided.
316
314
  * The invisible button pattern provides real button semantics for accessibility
317
315
  * while the container uses `:focus-within` to show focus outlines around the
318
- * entire token. When both `href` and `onRemove` are provided, the same
319
- * container pattern is used with an invisible `<a>` so the remove `<button>`
320
- * renders as a sibling of the link rather than nested inside it.
316
+ * entire token.
321
317
  *
322
318
  * @example
323
319
  * ```
@@ -354,20 +350,6 @@ export function Token({
354
350
  // if onClick was provided — the popover attaches its own handler.
355
351
  const effectiveOnClick = onClick ?? (role === 'button' ? () => {} : null);
356
352
 
357
- const removeButton = onRemove != null && (
358
- <button
359
- type="button"
360
- aria-label={t('@astryx.token.remove', {label})}
361
- onClick={e => {
362
- e.stopPropagation();
363
- onRemove(e);
364
- }}
365
- disabled={isDisabled}
366
- {...stylex.props(styles.removeButton)}>
367
- <Icon icon="close" size="xsm" color="inherit" />
368
- </button>
369
- );
370
-
371
353
  const content = (
372
354
  <>
373
355
  {icon}
@@ -376,7 +358,19 @@ export function Token({
376
358
  {label}
377
359
  </span>
378
360
  {endContent}
379
- {removeButton}
361
+ {onRemove != null && (
362
+ <button
363
+ type="button"
364
+ aria-label={t('@astryx.token.remove', {label})}
365
+ onClick={e => {
366
+ e.stopPropagation();
367
+ onRemove(e);
368
+ }}
369
+ disabled={isDisabled}
370
+ {...stylex.props(styles.removeButton)}>
371
+ <Icon icon="close" size="xsm" color="inherit" />
372
+ </button>
373
+ )}
380
374
  </>
381
375
  );
382
376
 
@@ -387,57 +381,11 @@ export function Token({
387
381
  };
388
382
 
389
383
  if (role === 'link') {
390
- if (onRemove == null) {
391
- return (
392
- <LinkComponent
393
- ref={ref as React.Ref<HTMLAnchorElement>}
394
- href={href as string}
395
- aria-disabled={isDisabled || undefined}
396
- {...sharedProps}
397
- {...mergeProps(
398
- themeProps('token', {color, size}),
399
- stylex.props(
400
- styles.base,
401
- sizeStyles[size],
402
- colorStyles[color],
403
- styles.interactive,
404
- isDisabled && styles.disabled,
405
- xstyle,
406
- ),
407
- className,
408
- style,
409
- )}>
410
- {content}
411
- </LinkComponent>
412
- );
413
- }
414
-
415
- // With a remove button, the link cannot be the root — nesting a <button>
416
- // inside an <a> is invalid HTML (WCAG 4.1.2). Delegate to TokenLink, a
417
- // non-presentational wrapper that renders a <span> container with the link
418
- // and the remove button as siblings and wires up useClickableContainer so
419
- // the whole token surface activates the link (including middle-click and
420
- // cmd/ctrl-click to open in a new tab). Token itself stays presentational
421
- // (no refs/effects) per the @astryx/presentational-component rule.
422
384
  return (
423
- <TokenLink
424
- ref={ref}
385
+ <LinkComponent
386
+ ref={ref as React.Ref<HTMLAnchorElement>}
425
387
  href={href as string}
426
- isDisabled={isDisabled}
427
- LinkComponent={LinkComponent}
428
- icon={icon}
429
- endContent={endContent}
430
- removeButton={removeButton}
431
- linkStyleProps={stylex.props(styles.invisibleButton)}
432
- labelContent={
433
- <span
434
- {...stylex.props(
435
- styles.label,
436
- isLabelHidden && styles.labelHidden,
437
- )}>
438
- {label}
439
- </span>
440
- }
388
+ aria-disabled={isDisabled || undefined}
441
389
  {...sharedProps}
442
390
  {...mergeProps(
443
391
  themeProps('token', {color, size}),
@@ -446,14 +394,14 @@ export function Token({
446
394
  sizeStyles[size],
447
395
  colorStyles[color],
448
396
  styles.interactive,
449
- styles.focusVisibleOutline,
450
397
  isDisabled && styles.disabled,
451
398
  xstyle,
452
399
  ),
453
400
  className,
454
401
  style,
455
- )}
456
- />
402
+ )}>
403
+ {content}
404
+ </LinkComponent>
457
405
  );
458
406
  }
459
407
 
@@ -500,7 +448,19 @@ export function Token({
500
448
  </span>
501
449
  </button>
502
450
  {endContent}
503
- {removeButton}
451
+ {onRemove != null && (
452
+ <button
453
+ type="button"
454
+ aria-label={t('@astryx.token.remove', {label})}
455
+ onClick={e => {
456
+ e.stopPropagation();
457
+ onRemove(e);
458
+ }}
459
+ disabled={isDisabled}
460
+ {...stylex.props(styles.removeButton)}>
461
+ <Icon icon="close" size="xsm" color="inherit" />
462
+ </button>
463
+ )}
504
464
  </span>
505
465
  );
506
466
  }
@@ -157,16 +157,6 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
157
157
  */
158
158
  inputXStyle?: StyleXStyles;
159
159
 
160
- /**
161
- * Tab-order override for the input element. Typeahead passes `-1` while
162
- * its selected-value token is shown: the input is visually collapsed
163
- * (width 0 / opacity 0) but must stay programmatically focusable for
164
- * token edit/clear interactions, so removing it from the Tab order is
165
- * what prevents an invisible tab stop (WCAG 2.4.3 / 2.4.7). The input
166
- * remains focusable via `.focus()` regardless of this value.
167
- */
168
- inputTabIndex?: number;
169
-
170
160
  /**
171
161
  * Ref to the anchor element for dropdown positioning.
172
162
  * The dropdown will be positioned relative to this element.
@@ -326,7 +316,6 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
326
316
  ariaDescribedBy,
327
317
  ariaLabelledBy,
328
318
  inputXStyle,
329
- inputTabIndex,
330
319
  anchorRef,
331
320
  onKeyDown: externalOnKeyDown,
332
321
  debounceMs = 150,
@@ -759,7 +748,6 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
759
748
  aria-describedby={ariaDescribedBy}
760
749
  aria-labelledby={ariaLabelledBy}
761
750
  aria-disabled={isFocusableDisabled ? 'true' : undefined}
762
- tabIndex={inputTabIndex}
763
751
  value={query}
764
752
  onChange={handleInputChange}
765
753
  onPointerDown={() => {
@@ -737,68 +737,6 @@ describe('Typeahead edit mode', () => {
737
737
  });
738
738
  });
739
739
 
740
- describe('Typeahead collapsed input tab order', () => {
741
- it('removes the invisible input from the Tab order while a token is shown', () => {
742
- render(
743
- <Typeahead
744
- label="Fruit"
745
- searchSource={fruitSource}
746
- value={fruits[0]}
747
- onChange={() => {}}
748
- />,
749
- );
750
- // While the token is shown the input is collapsed (width 0 / opacity 0);
751
- // it must stay programmatically focusable for token interactions but must
752
- // not be an invisible Tab stop (WCAG 2.4.3 / 2.4.7).
753
- expect(screen.getByRole('combobox')).toHaveAttribute('tabindex', '-1');
754
- });
755
-
756
- it('Tab from the token skips the invisible input', async () => {
757
- const user = userEvent.setup();
758
- render(
759
- <Typeahead
760
- label="Fruit"
761
- searchSource={fruitSource}
762
- value={fruits[0]}
763
- onChange={() => {}}
764
- />,
765
- );
766
- // Focus the token's internal button, then Tab away — focus must not land
767
- // on the visually hidden combobox input.
768
- const tokenButton = screen.getByRole('button', {name: fruits[0].label});
769
- tokenButton.focus();
770
- await user.tab();
771
- expect(screen.getByRole('combobox')).not.toHaveFocus();
772
- });
773
-
774
- it('keeps the input in the Tab order when no token is shown', () => {
775
- render(
776
- <Typeahead
777
- label="Fruit"
778
- searchSource={fruitSource}
779
- value={null}
780
- onChange={() => {}}
781
- />,
782
- );
783
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex');
784
- });
785
-
786
- it('restores the input to the Tab order in edit mode', () => {
787
- render(
788
- <Typeahead
789
- label="Fruit"
790
- searchSource={fruitSource}
791
- value={fruits[0]}
792
- onChange={() => {}}
793
- />,
794
- );
795
- // Entering edit mode removes the token and uncollapses the input
796
- const tokenText = screen.getByText(fruits[0].label);
797
- fireEvent.click(tokenText.closest('div')!);
798
- expect(screen.getByRole('combobox')).not.toHaveAttribute('tabindex');
799
- });
800
- });
801
-
802
740
  describe('BaseTypeahead paste behavior', () => {
803
741
  it('pasting text triggers search results like typing', async () => {
804
742
  const user = userEvent.setup();
@@ -467,12 +467,6 @@ export function Typeahead<T extends SearchableItem>({
467
467
  anchorRef={wrapperRef}
468
468
  onKeyDown={handleKeyDown}
469
469
  inputXStyle={showToken ? styles.inputHidden : undefined}
470
- // While the token is shown the input is collapsed (width 0 /
471
- // opacity 0) — take it out of the Tab order so keyboard users
472
- // don't hit an invisible stop (WCAG 2.4.3 / 2.4.7). It stays
473
- // programmatically focusable: entering edit mode and clearing
474
- // both refocus it after it uncollapses.
475
- inputTabIndex={showToken ? -1 : undefined}
476
470
  size={size}
477
471
  />
478
472
  {hasClear && value && !isDisabled && (
@@ -9,7 +9,6 @@
9
9
  import {render} from '@testing-library/react';
10
10
  import {describe, expect, it} from 'vitest';
11
11
  import {useKeyboardHint, type KeyboardHintOrientation} from './useKeyboardHint';
12
- import {InternationalizationProvider} from '../i18n';
13
12
 
14
13
  function TestHint({orientation}: {orientation?: KeyboardHintOrientation}) {
15
14
  const {hintElement} = useKeyboardHint({orientation});
@@ -68,25 +67,4 @@ describe('useKeyboardHint', () => {
68
67
 
69
68
  expect(hint.className).toContain('useKeyboardHint__styles.hint');
70
69
  });
71
-
72
- it('renders the "to navigate" label from the i18n catalog', () => {
73
- const {container} = render(<TestHint />);
74
- const hint = container.querySelector('[popover="manual"]') as HTMLElement;
75
-
76
- expect(hint.textContent).toContain('to navigate');
77
- });
78
-
79
- it('localizes the "to navigate" label through the i18n catalog', () => {
80
- const {container} = render(
81
- <InternationalizationProvider
82
- locale="fr"
83
- overrides={{fr: {'@astryx.keyboardHint.toNavigate': 'pour naviguer'}}}>
84
- <TestHint />
85
- </InternationalizationProvider>,
86
- );
87
- const hint = container.querySelector('[popover="manual"]') as HTMLElement;
88
-
89
- expect(hint.textContent).toContain('pour naviguer');
90
- expect(hint.textContent).not.toContain('to navigate');
91
- });
92
70
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file useKeyboardHint.tsx
7
- * @input Uses React, StyleX, Kbd, useLayer, i18n (useTranslator)
7
+ * @input Uses React, StyleX, Kbd, useLayer
8
8
  * @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
9
9
  * @position Core hook; shows sighted keyboard users how to navigate composite
10
10
  * widgets that use roving tabindex (single Tab stop, arrows inside)
@@ -27,7 +27,6 @@ import {
27
27
  } from '../theme/tokens.stylex';
28
28
  import {Kbd} from '../Kbd';
29
29
  import {useLayer} from '../Layer/useLayer';
30
- import {useTranslator} from '../i18n';
31
30
 
32
31
  // ---------------------------------------------------------------------------
33
32
  // Types
@@ -180,7 +179,6 @@ export function useKeyboardHint(
180
179
  isEnabled = true,
181
180
  } = options;
182
181
 
183
- const t = useTranslator();
184
182
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
185
183
  const dismissedRef = useRef(false);
186
184
  const isVisibleRef = useRef(false);
@@ -318,9 +316,7 @@ export function useKeyboardHint(
318
316
  const hintElement = layer.render(
319
317
  <span aria-hidden="true">
320
318
  {arrowContent}
321
- <span {...stylex.props(styles.label)}>
322
- {t('@astryx.keyboardHint.toNavigate')}
323
- </span>
319
+ <span {...stylex.props(styles.label)}>to navigate</span>
324
320
  </span>,
325
321
  {
326
322
  placement: 'below',
@@ -1,62 +0,0 @@
1
- /**
2
- * @file TokenLink.tsx
3
- * @input Rendered token pieces (icon, label, endContent, remove button) plus
4
- * container/link styling props and the resolved LinkComponent
5
- * @output Exports TokenLink, the interactive container for the href+onRemove Token variant
6
- * @position Internal to Token; keeps Token.tsx presentational
7
- *
8
- * Token is a presentational component (see
9
- * /internal/eslint-plugin-astryx/presentational-component.js) and must not use
10
- * refs or effects. When a Token has both `href` and `onRemove`, the remove
11
- * <button> must be a sibling of the link (nesting a button inside an anchor is
12
- * invalid HTML — WCAG 4.1.2), so the surface becomes a clickable container that
13
- * delegates to an inner link. That delegation needs refs, so it lives here.
14
- *
15
- * useClickableContainer gives the container middle-click and cmd/ctrl-click
16
- * open-in-new-tab behavior for the link, ignores clicks on nested interactive
17
- * elements (the remove button), and skips activation during text selection.
18
- *
19
- * SYNC: When modified, update these files to stay in sync:
20
- * - /packages/core/src/Token/Token.tsx (renders TokenLink for the href+onRemove case)
21
- * - /packages/core/src/Token/Token.test.tsx (tests for delegated behavior)
22
- */
23
- import type { ReactNode, CSSProperties, Ref, HTMLAttributes } from 'react';
24
- import type { LinkComponentType } from '../Link/types';
25
- export interface TokenLinkProps extends HTMLAttributes<HTMLElement> {
26
- /** Ref forwarded to the container element. */
27
- ref?: Ref<HTMLElement>;
28
- /** Link URL. */
29
- href: string;
30
- /** Whether the token is disabled. */
31
- isDisabled: boolean;
32
- /** Resolved link component (native `<a>` or a router link). */
33
- LinkComponent: LinkComponentType;
34
- /** `stylex.props()` output applied to the invisible inner link. */
35
- linkStyleProps: {
36
- className?: string;
37
- style?: CSSProperties;
38
- };
39
- /** Visible (or visually hidden) label node rendered inside the link. */
40
- labelContent: ReactNode;
41
- /** Optional leading icon. */
42
- icon?: ReactNode;
43
- /** Content rendered after the label, before the remove button. */
44
- endContent?: ReactNode;
45
- /** The remove button node, rendered as a sibling of the link. */
46
- removeButton: ReactNode;
47
- /** Allow data-* attributes (e.g. data-testid, theme targets) on the container. */
48
- [dataAttr: `data-${string}`]: unknown;
49
- }
50
- /**
51
- * Interactive container for a Token that is both a link and removable.
52
- *
53
- * Renders a `<span>` wrapping an invisible inner link and the remove button as
54
- * siblings. useClickableContainer delegates clicks on the surface to the link
55
- * (including middle-click / cmd+click to open in a new tab) while the remove
56
- * button keeps handling its own clicks.
57
- */
58
- export declare function TokenLink({ ref, href, isDisabled, LinkComponent, linkStyleProps, labelContent, icon, endContent, removeButton, ...containerProps }: TokenLinkProps): import("react").JSX.Element;
59
- export declare namespace TokenLink {
60
- var displayName: string;
61
- }
62
- //# sourceMappingURL=TokenLink.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TokenLink.d.ts","sourceRoot":"","sources":["../../src/Token/TokenLink.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,EAAC,MAAM,OAAO,CAAC;AAIzE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAErD,MAAM,WAAW,cAAe,SAAQ,cAAc,CAAC,WAAW,CAAC;IACjE,8CAA8C;IAC9C,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,UAAU,EAAE,OAAO,CAAC;IACpB,+DAA+D;IAC/D,aAAa,EAAE,iBAAiB,CAAC;IACjC,mEAAmE;IACnE,cAAc,EAAE;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,aAAa,CAAA;KAAC,CAAC;IAC5D,wEAAwE;IACxE,YAAY,EAAE,SAAS,CAAC;IACxB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,iEAAiE;IACjE,YAAY,EAAE,SAAS,CAAC;IACxB,kFAAkF;IAClF,CAAC,QAAQ,EAAE,QAAQ,MAAM,EAAE,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACxB,GAAG,EACH,IAAI,EACJ,UAAU,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,GAAG,cAAc,EAClB,EAAE,cAAc,+BA6BhB;yBAxCe,SAAS"}
@@ -1,76 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- /**
6
- * @file TokenLink.tsx
7
- * @input Rendered token pieces (icon, label, endContent, remove button) plus
8
- * container/link styling props and the resolved LinkComponent
9
- * @output Exports TokenLink, the interactive container for the href+onRemove Token variant
10
- * @position Internal to Token; keeps Token.tsx presentational
11
- *
12
- * Token is a presentational component (see
13
- * /internal/eslint-plugin-astryx/presentational-component.js) and must not use
14
- * refs or effects. When a Token has both `href` and `onRemove`, the remove
15
- * <button> must be a sibling of the link (nesting a button inside an anchor is
16
- * invalid HTML — WCAG 4.1.2), so the surface becomes a clickable container that
17
- * delegates to an inner link. That delegation needs refs, so it lives here.
18
- *
19
- * useClickableContainer gives the container middle-click and cmd/ctrl-click
20
- * open-in-new-tab behavior for the link, ignores clicks on nested interactive
21
- * elements (the remove button), and skips activation during text selection.
22
- *
23
- * SYNC: When modified, update these files to stay in sync:
24
- * - /packages/core/src/Token/Token.tsx (renders TokenLink for the href+onRemove case)
25
- * - /packages/core/src/Token/Token.test.tsx (tests for delegated behavior)
26
- */
27
- import { useRef } from 'react';
28
- import { mergeRefs } from "../utils/index.js";
29
- import { useClickableContainer } from "../hooks/useClickableContainer.js";
30
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
31
- /**
32
- * Interactive container for a Token that is both a link and removable.
33
- *
34
- * Renders a `<span>` wrapping an invisible inner link and the remove button as
35
- * siblings. useClickableContainer delegates clicks on the surface to the link
36
- * (including middle-click / cmd+click to open in a new tab) while the remove
37
- * button keeps handling its own clicks.
38
- */
39
- export function TokenLink({
40
- ref,
41
- href,
42
- isDisabled,
43
- LinkComponent,
44
- linkStyleProps,
45
- labelContent,
46
- icon,
47
- endContent,
48
- removeButton,
49
- ...containerProps
50
- }) {
51
- const containerRef = useRef(null);
52
- const linkRef = useRef(null);
53
- const {
54
- onClick,
55
- onMouseUp
56
- } = useClickableContainer({
57
- containerRef,
58
- interactiveRef: linkRef,
59
- href,
60
- disabled: isDisabled
61
- });
62
- return /*#__PURE__*/_jsxs("span", {
63
- ref: mergeRefs(ref, containerRef),
64
- onClick: isDisabled ? undefined : onClick,
65
- onMouseUp: isDisabled ? undefined : onMouseUp,
66
- ...containerProps,
67
- children: [icon, /*#__PURE__*/_jsx(LinkComponent, {
68
- ref: linkRef,
69
- href: href,
70
- "aria-disabled": isDisabled || undefined,
71
- ...linkStyleProps,
72
- children: labelContent
73
- }), endContent, removeButton]
74
- });
75
- }
76
- TokenLink.displayName = 'TokenLink';
@@ -1,107 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- /**
6
- * @file TokenLink.tsx
7
- * @input Rendered token pieces (icon, label, endContent, remove button) plus
8
- * container/link styling props and the resolved LinkComponent
9
- * @output Exports TokenLink, the interactive container for the href+onRemove Token variant
10
- * @position Internal to Token; keeps Token.tsx presentational
11
- *
12
- * Token is a presentational component (see
13
- * /internal/eslint-plugin-astryx/presentational-component.js) and must not use
14
- * refs or effects. When a Token has both `href` and `onRemove`, the remove
15
- * <button> must be a sibling of the link (nesting a button inside an anchor is
16
- * invalid HTML — WCAG 4.1.2), so the surface becomes a clickable container that
17
- * delegates to an inner link. That delegation needs refs, so it lives here.
18
- *
19
- * useClickableContainer gives the container middle-click and cmd/ctrl-click
20
- * open-in-new-tab behavior for the link, ignores clicks on nested interactive
21
- * elements (the remove button), and skips activation during text selection.
22
- *
23
- * SYNC: When modified, update these files to stay in sync:
24
- * - /packages/core/src/Token/Token.tsx (renders TokenLink for the href+onRemove case)
25
- * - /packages/core/src/Token/Token.test.tsx (tests for delegated behavior)
26
- */
27
-
28
- import type {ReactNode, CSSProperties, Ref, HTMLAttributes} from 'react';
29
- import {useRef} from 'react';
30
- import {mergeRefs} from '../utils';
31
- import {useClickableContainer} from '../hooks/useClickableContainer';
32
- import type {LinkComponentType} from '../Link/types';
33
-
34
- export interface TokenLinkProps extends HTMLAttributes<HTMLElement> {
35
- /** Ref forwarded to the container element. */
36
- ref?: Ref<HTMLElement>;
37
- /** Link URL. */
38
- href: string;
39
- /** Whether the token is disabled. */
40
- isDisabled: boolean;
41
- /** Resolved link component (native `<a>` or a router link). */
42
- LinkComponent: LinkComponentType;
43
- /** `stylex.props()` output applied to the invisible inner link. */
44
- linkStyleProps: {className?: string; style?: CSSProperties};
45
- /** Visible (or visually hidden) label node rendered inside the link. */
46
- labelContent: ReactNode;
47
- /** Optional leading icon. */
48
- icon?: ReactNode;
49
- /** Content rendered after the label, before the remove button. */
50
- endContent?: ReactNode;
51
- /** The remove button node, rendered as a sibling of the link. */
52
- removeButton: ReactNode;
53
- /** Allow data-* attributes (e.g. data-testid, theme targets) on the container. */
54
- [dataAttr: `data-${string}`]: unknown;
55
- }
56
-
57
- /**
58
- * Interactive container for a Token that is both a link and removable.
59
- *
60
- * Renders a `<span>` wrapping an invisible inner link and the remove button as
61
- * siblings. useClickableContainer delegates clicks on the surface to the link
62
- * (including middle-click / cmd+click to open in a new tab) while the remove
63
- * button keeps handling its own clicks.
64
- */
65
- export function TokenLink({
66
- ref,
67
- href,
68
- isDisabled,
69
- LinkComponent,
70
- linkStyleProps,
71
- labelContent,
72
- icon,
73
- endContent,
74
- removeButton,
75
- ...containerProps
76
- }: TokenLinkProps) {
77
- const containerRef = useRef<HTMLElement | null>(null);
78
- const linkRef = useRef<HTMLElement | null>(null);
79
-
80
- const {onClick, onMouseUp} = useClickableContainer({
81
- containerRef,
82
- interactiveRef: linkRef,
83
- href,
84
- disabled: isDisabled,
85
- });
86
-
87
- return (
88
- <span
89
- ref={mergeRefs(ref, containerRef)}
90
- onClick={isDisabled ? undefined : onClick}
91
- onMouseUp={isDisabled ? undefined : onMouseUp}
92
- {...containerProps}>
93
- {icon}
94
- <LinkComponent
95
- ref={linkRef as Ref<HTMLAnchorElement>}
96
- href={href}
97
- aria-disabled={isDisabled || undefined}
98
- {...linkStyleProps}>
99
- {labelContent}
100
- </LinkComponent>
101
- {endContent}
102
- {removeButton}
103
- </span>
104
- );
105
- }
106
-
107
- TokenLink.displayName = 'TokenLink';
@@ -1,60 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file forcedColors.ts
5
- * @input Uses the jsdom document (StyleX dev runtime injects rules into it)
6
- * @output Exports getForcedColorsRules, getAllInjectedCss test helpers
7
- * @position Shared test utility for asserting forced-colors style output
8
- *
9
- * jsdom cannot emulate `@media (forced-colors: active)` rendering, so
10
- * component tests assert the next-best thing: that the StyleX dev runtime
11
- * (runtimeInjection in vitest.config.ts) actually injected the forced-colors
12
- * rules a component relies on for Windows High Contrast support (WCAG 1.4.11).
13
- * Visual behavior needs manual verification in a forced-colors environment.
14
- */
15
-
16
- /**
17
- * Collects the cssText of every injected CSS rule scoped to
18
- * `@media (forced-colors: active)`, including rules nested in other
19
- * conditions. Returns one string for substring assertions.
20
- */
21
- export function getForcedColorsRules(): string {
22
- const chunks: string[] = [];
23
- for (const sheet of Array.from(document.styleSheets)) {
24
- let rules: CSSRule[];
25
- try {
26
- rules = Array.from(sheet.cssRules);
27
- } catch {
28
- continue;
29
- }
30
- for (const rule of rules) {
31
- if (rule.cssText.includes('forced-colors: active')) {
32
- chunks.push(rule.cssText);
33
- }
34
- }
35
- }
36
- return chunks.join('\n');
37
- }
38
-
39
- /**
40
- * Collects the cssText of every injected CSS rule, regardless of condition.
41
- * Use to assert declarations that live OUTSIDE `@media (forced-colors: active)`
42
- * yet still exist for forced-colors support — e.g. `forced-color-adjust: none`
43
- * (an unconditional declaration) or a hover tint gated behind
44
- * `(forced-colors: none)` so it cannot override the forced-colors state.
45
- */
46
- export function getAllInjectedCss(): string {
47
- const chunks: string[] = [];
48
- for (const sheet of Array.from(document.styleSheets)) {
49
- let rules: CSSRule[];
50
- try {
51
- rules = Array.from(sheet.cssRules);
52
- } catch {
53
- continue;
54
- }
55
- for (const rule of rules) {
56
- chunks.push(rule.cssText);
57
- }
58
- }
59
- return chunks.join('\n');
60
- }