@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.
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -6
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +2 -2
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -19
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +1 -10
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +11 -8
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +1 -1
- package/dist/Field/FieldLabel.d.ts +1 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +3 -7
- package/dist/Item/Item.d.ts +3 -16
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +6 -31
- package/dist/List/ListItem.d.ts +1 -11
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +0 -2
- package/dist/MetadataList/MetadataList.d.ts +1 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +2 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +2 -13
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +0 -5
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +2 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +4 -15
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +1 -1
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +9 -61
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +1 -3
- package/dist/Token/Token.d.ts +1 -4
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +43 -68
- package/dist/Typeahead/BaseTypeahead.d.ts +0 -9
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +0 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +1 -8
- package/dist/astryx.css +3 -16
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/useKeyboardHint.d.ts +1 -1
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
- package/dist/hooks/useKeyboardHint.js +2 -4
- package/locales/en.json +0 -32
- package/locales/pseudo.json +0 -24
- package/package.json +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +1 -21
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -4
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +1 -1
- package/src/Button/Button.test.tsx +0 -16
- package/src/Button/Button.tsx +2 -4
- package/src/CheckboxInput/CheckboxInput.test.tsx +0 -23
- package/src/CheckboxInput/CheckboxInput.tsx +2 -17
- package/src/CheckboxList/CheckboxList.test.tsx +3 -94
- package/src/CheckboxList/CheckboxListItem.tsx +10 -17
- package/src/CodeBlock/CodeBlock.test.tsx +0 -17
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/ContextMenu/ContextMenu.test.tsx +2 -17
- package/src/ContextMenu/ContextMenu.tsx +1 -10
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +12 -4
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +10 -9
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +2 -20
- package/src/Field/FieldLabel.test.tsx +0 -25
- package/src/Field/FieldLabel.tsx +2 -11
- package/src/Item/Item.doc.mjs +0 -5
- package/src/Item/Item.test.tsx +0 -88
- package/src/Item/Item.tsx +11 -57
- package/src/List/ListItem.doc.mjs +0 -12
- package/src/List/ListItem.tsx +0 -13
- package/src/MetadataList/MetadataList.test.tsx +0 -24
- package/src/MetadataList/MetadataList.tsx +2 -6
- package/src/MultiSelector/MultiSelector.test.tsx +0 -76
- package/src/MultiSelector/MultiSelector.tsx +2 -22
- package/src/RadioList/RadioList.test.tsx +4 -26
- package/src/RadioList/RadioListItem.tsx +1 -8
- package/src/SegmentedControl/SegmentedControl.test.tsx +0 -28
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -20
- package/src/Selector/Selector.test.tsx +0 -22
- package/src/Selector/Selector.tsx +1 -8
- package/src/Skeleton/Skeleton.test.tsx +0 -16
- package/src/Skeleton/Skeleton.tsx +1 -13
- package/src/Switch/Switch.test.tsx +0 -37
- package/src/Switch/Switch.tsx +3 -52
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -19
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -3
- package/src/Toast/ToastViewport.test.tsx +8 -218
- package/src/Toast/ToastViewport.tsx +12 -71
- package/src/ToggleButton/ToggleButton.test.tsx +0 -24
- package/src/ToggleButton/ToggleButton.tsx +1 -19
- package/src/Token/Token.test.tsx +0 -145
- package/src/Token/Token.tsx +33 -73
- package/src/Typeahead/BaseTypeahead.tsx +0 -12
- package/src/Typeahead/Typeahead.test.tsx +0 -62
- package/src/Typeahead/Typeahead.tsx +0 -6
- package/src/hooks/useKeyboardHint.test.tsx +0 -22
- package/src/hooks/useKeyboardHint.tsx +2 -6
- package/dist/Token/TokenLink.d.ts +0 -62
- package/dist/Token/TokenLink.d.ts.map +0 -1
- package/dist/Token/TokenLink.js +0 -76
- package/src/Token/TokenLink.tsx +0 -107
- package/src/__tests__/forcedColors.ts +0 -60
package/src/Token/Token.tsx
CHANGED
|
@@ -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.
|
|
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
|
-
{
|
|
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
|
-
<
|
|
424
|
-
ref={ref}
|
|
385
|
+
<LinkComponent
|
|
386
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
425
387
|
href={href as string}
|
|
426
|
-
|
|
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
|
-
{
|
|
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
|
|
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"}
|
package/dist/Token/TokenLink.js
DELETED
|
@@ -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';
|
package/src/Token/TokenLink.tsx
DELETED
|
@@ -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
|
-
}
|