@astryxdesign/core 0.5.0-canary.f4d494c → 0.5.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 (71) hide show
  1. package/dist/Avatar/Avatar.d.ts +1 -25
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +9 -43
  4. package/dist/Avatar/index.d.ts +2 -2
  5. package/dist/Avatar/index.d.ts.map +1 -1
  6. package/dist/Avatar/index.js +1 -1
  7. package/dist/AvatarGroup/AvatarGroup.d.ts +2 -9
  8. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  9. package/dist/AvatarGroup/AvatarGroup.js +2 -5
  10. package/dist/AvatarGroup/AvatarGroupContext.d.ts +1 -2
  11. package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
  12. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  13. package/dist/AvatarGroup/AvatarGroupOverflow.js +6 -10
  14. package/dist/DateInput/DateInput.d.ts +0 -43
  15. package/dist/DateInput/DateInput.d.ts.map +1 -1
  16. package/dist/DateInput/DateInput.js +0 -19
  17. package/dist/DateInput/index.d.ts +1 -1
  18. package/dist/DateInput/index.d.ts.map +1 -1
  19. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  20. package/dist/HoverCard/HoverCard.js +3 -52
  21. package/dist/HoverCard/useHoverCard.d.ts +2 -11
  22. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  23. package/dist/HoverCard/useHoverCard.js +0 -2
  24. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  25. package/dist/Indicator/CheckboxIndicator.js +2 -6
  26. package/dist/Kbd/Kbd.d.ts.map +1 -1
  27. package/dist/Kbd/Kbd.js +26 -2
  28. package/dist/astryx.css +0 -21
  29. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  30. package/dist/hooks/useHotkeys.js +25 -3
  31. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  32. package/dist/theme/derivedVarRegistry.js +0 -4
  33. package/dist/theme/syntax/tokens.d.ts +1 -1
  34. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  35. package/dist/theme/syntax/tokens.js +2 -5
  36. package/package.json +3 -3
  37. package/src/Avatar/Avatar.doc.mjs +2 -12
  38. package/src/Avatar/Avatar.test.tsx +0 -80
  39. package/src/Avatar/Avatar.tsx +14 -64
  40. package/src/Avatar/index.ts +2 -2
  41. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -8
  42. package/src/AvatarGroup/AvatarGroup.test.tsx +0 -24
  43. package/src/AvatarGroup/AvatarGroup.tsx +4 -12
  44. package/src/AvatarGroup/AvatarGroupContext.ts +3 -5
  45. package/src/AvatarGroup/AvatarGroupOverflow.tsx +4 -10
  46. package/src/DateInput/DateInput.doc.mjs +0 -16
  47. package/src/DateInput/DateInput.tsx +0 -52
  48. package/src/DateInput/DateInputTouch.test.tsx +9 -32
  49. package/src/DateInput/index.ts +0 -1
  50. package/src/HoverCard/HoverCard.test.tsx +4 -84
  51. package/src/HoverCard/HoverCard.tsx +3 -65
  52. package/src/HoverCard/useHoverCard.tsx +2 -15
  53. package/src/Indicator/CheckboxIndicator.tsx +8 -14
  54. package/src/Indicator/Indicator.doc.mjs +0 -2
  55. package/src/Kbd/Kbd.test.tsx +17 -0
  56. package/src/Kbd/Kbd.tsx +26 -2
  57. package/src/hooks/useHotkeys.test.ts +18 -0
  58. package/src/hooks/useHotkeys.ts +25 -3
  59. package/src/theme/derivedVarRegistry.test.ts +0 -1
  60. package/src/theme/derivedVarRegistry.ts +0 -1
  61. package/src/theme/syntax/tokens.ts +2 -5
  62. package/dist/DateInput/NativeDateField.d.ts +0 -10
  63. package/dist/DateInput/NativeDateField.d.ts.map +0 -1
  64. package/dist/DateInput/NativeDateField.js +0 -477
  65. package/dist/utils/isApplePlatform.d.ts +0 -18
  66. package/dist/utils/isApplePlatform.d.ts.map +0 -1
  67. package/dist/utils/isApplePlatform.js +0 -37
  68. package/src/DateInput/NativeDateField.test.tsx +0 -565
  69. package/src/DateInput/NativeDateField.tsx +0 -623
  70. package/src/utils/isApplePlatform.test.ts +0 -61
  71. package/src/utils/isApplePlatform.ts +0 -37
@@ -259,59 +259,7 @@ export function HoverCard({
259
259
  // Use combined ref for position + interaction
260
260
  hoverCard.ref(firstChild);
261
261
 
262
- if (label) {
263
- // When named, the hover card is a dialog. The trigger should advertise the
264
- // popup relationship with aria-haspopup/aria-expanded, not describe the
265
- // trigger with the dialog's content (aria-describedby is for plain-text
266
- // descriptions, not navigable regions). See #5049.
267
- //
268
- // Merge rather than overwrite: the trigger may already carry its own
269
- // popup semantics (e.g. a menu button wrapped in a labelled HoverCard),
270
- // so preserve the existing values and restore them on cleanup.
271
- const existingHaspopup = firstChild.getAttribute('aria-haspopup');
272
- const existingControls = firstChild.getAttribute('aria-controls');
273
- const existingExpanded = firstChild.getAttribute('aria-expanded');
274
-
275
- firstChild.setAttribute('aria-haspopup', 'dialog');
276
- // Only point aria-controls at the layer while it is open and in the DOM.
277
- // While closed, useLayer leaves only an inert <template> marker, so the
278
- // id would reference nothing. DateInput gates it the same way. The
279
- // trigger's own aria-controls (if any) is preserved either way.
280
- if (hoverCard.isOpen) {
281
- firstChild.setAttribute(
282
- 'aria-controls',
283
- mergeIds(existingControls, hoverCard.id) ?? '',
284
- );
285
- } else if (existingControls) {
286
- firstChild.setAttribute('aria-controls', existingControls);
287
- } else {
288
- firstChild.removeAttribute('aria-controls');
289
- }
290
- firstChild.setAttribute('aria-expanded', String(hoverCard.isOpen));
291
-
292
- return () => {
293
- hoverCard.ref(null);
294
- if (existingHaspopup) {
295
- firstChild.setAttribute('aria-haspopup', existingHaspopup);
296
- } else {
297
- firstChild.removeAttribute('aria-haspopup');
298
- }
299
- if (existingControls) {
300
- firstChild.setAttribute('aria-controls', existingControls);
301
- } else {
302
- firstChild.removeAttribute('aria-controls');
303
- }
304
- if (existingExpanded) {
305
- firstChild.setAttribute('aria-expanded', existingExpanded);
306
- } else {
307
- firstChild.removeAttribute('aria-expanded');
308
- }
309
- };
310
- }
311
-
312
- // Unnamed fallback: the popup remains role="group", which is not a dialog,
313
- // so keep the previous description relationship until a naming decision is
314
- // made for the no-label case (tracked in #5049).
262
+ // Set aria-describedby, merging with existing
315
263
  const existingDescribedBy = firstChild.getAttribute('aria-describedby');
316
264
  firstChild.setAttribute(
317
265
  'aria-describedby',
@@ -326,14 +274,7 @@ export function HoverCard({
326
274
  firstChild.removeAttribute('aria-describedby');
327
275
  }
328
276
  };
329
- }, [
330
- textOnly,
331
- label,
332
- hoverCard.ref,
333
- hoverCard.id,
334
- hoverCard.isOpen,
335
- hoverCard.describedBy,
336
- ]);
277
+ }, [textOnly, hoverCard.ref, hoverCard.describedBy]);
337
278
 
338
279
  // While closed, useLayer leaves only an inert <template> marker at this JSX
339
280
  // position. When the card needs to open, it uses that marker to keep the
@@ -352,10 +293,7 @@ export function HoverCard({
352
293
  <span
353
294
  ref={hoverCard.ref}
354
295
  tabIndex={0}
355
- aria-haspopup={label ? 'dialog' : undefined}
356
- aria-controls={label && hoverCard.isOpen ? hoverCard.id : undefined}
357
- aria-expanded={label ? hoverCard.isOpen : undefined}
358
- aria-describedby={label ? undefined : hoverCard.describedBy}
296
+ aria-describedby={hoverCard.describedBy}
359
297
  {...stylex.props(
360
298
  styles.wrapperInline,
361
299
  showHoverIndication && styles.hoverIndication,
@@ -190,22 +190,11 @@ export interface HoverCardReturn {
190
190
  anchorId: string;
191
191
 
192
192
  /**
193
- * Unique ID for the hover card container.
194
- * Useful for `aria-controls` or `aria-owns` on the trigger.
195
- */
196
- id: string;
197
-
198
- /**
199
- * Deprecated alias of `id`. Kept for backwards compatibility; prefer `id`.
193
+ * ID for aria-describedby on the trigger element.
194
+ * Caller should compose with other IDs using mergeIds utility.
200
195
  */
201
196
  describedBy: string;
202
197
 
203
- /**
204
- * Whether the hover card is currently open.
205
- * Useful for driving `aria-expanded` on the trigger.
206
- */
207
- isOpen: boolean;
208
-
209
198
  /**
210
199
  * Render function for hover card content.
211
200
  * Returns anchor-positioned popover element.
@@ -692,9 +681,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
692
681
  positionRef: layer.ref,
693
682
  interactionRef,
694
683
  anchorId: layer.anchorId,
695
- id: layer.id,
696
684
  describedBy: layer.id,
697
- isOpen: layer.isOpen,
698
685
  renderHoverCard,
699
686
  show: layer.show,
700
687
  hide: layer.hide,
@@ -227,13 +227,10 @@ export function CheckboxIndicator({
227
227
  <>
228
228
  <svg
229
229
  viewBox="0 0 10 10"
230
- {...mergeProps(
231
- themeProps('checkbox-indicator-check', {size}),
232
- stylex.props(
233
- styles.checkmark,
234
- checkmarkSizeStyles[size],
235
- isChecked && styles.checkmarkVisible,
236
- ),
230
+ {...stylex.props(
231
+ styles.checkmark,
232
+ checkmarkSizeStyles[size],
233
+ isChecked && styles.checkmarkVisible,
237
234
  )}>
238
235
  <path
239
236
  d="M8.5 2.5L4 7.5L1.5 5"
@@ -245,13 +242,10 @@ export function CheckboxIndicator({
245
242
  />
246
243
  </svg>
247
244
  <span
248
- {...mergeProps(
249
- themeProps('checkbox-indicator-dash', {size}),
250
- stylex.props(
251
- styles.indeterminateMark,
252
- indeterminateSizeStyles[size],
253
- isIndeterminate && styles.indeterminateMarkVisible,
254
- ),
245
+ {...stylex.props(
246
+ styles.indeterminateMark,
247
+ indeterminateSizeStyles[size],
248
+ isIndeterminate && styles.indeterminateMarkVisible,
255
249
  )}
256
250
  />
257
251
  </>
@@ -132,8 +132,6 @@ export const docs = {
132
132
  visualProps: ['size'],
133
133
  states: ['checked', 'disabled'],
134
134
  },
135
- {className: 'astryx-checkbox-indicator-check', visualProps: ['size']},
136
- {className: 'astryx-checkbox-indicator-dash', visualProps: ['size']},
137
135
  {
138
136
  className: 'astryx-radio-indicator',
139
137
  visualProps: ['size'],
@@ -23,6 +23,7 @@ describe('Kbd', () => {
23
23
  value: originalPlatform,
24
24
  configurable: true,
25
25
  });
26
+ delete (navigator as {userAgentData?: unknown}).userAgentData;
26
27
  });
27
28
 
28
29
  it('renders a single key', () => {
@@ -54,6 +55,22 @@ describe('Kbd', () => {
54
55
  expect(screen.getByText('\u2318')).toBeInTheDocument(); // \u2318
55
56
  });
56
57
 
58
+ it('reads a blank userAgentData.platform as unknown, not as non-Mac', () => {
59
+ // Builds that rewrite their client-hints identity expose the key with an
60
+ // empty value; navigator.platform is the only surface left that answers.
61
+ Object.defineProperty(navigator, 'userAgentData', {
62
+ value: {platform: ''},
63
+ configurable: true,
64
+ });
65
+ Object.defineProperty(navigator, 'platform', {
66
+ value: 'MacIntel',
67
+ configurable: true,
68
+ });
69
+
70
+ render(<Kbd keys="mod" />);
71
+ expect(screen.getByText('\u2318')).toBeInTheDocument();
72
+ });
73
+
57
74
  it('maps modifier keys to symbols', () => {
58
75
  render(<Kbd keys="ctrl+alt+shift+k" />);
59
76
  expect(screen.getByText('\u2303')).toBeInTheDocument(); // \u2303
package/src/Kbd/Kbd.tsx CHANGED
@@ -16,7 +16,6 @@
16
16
  import React, {useSyncExternalStore} from 'react';
17
17
  import * as stylex from '@stylexjs/stylex';
18
18
  import {mergeProps} from '../utils';
19
- import {isApplePlatform} from '../utils/isApplePlatform';
20
19
  import type {BaseProps} from '../BaseProps';
21
20
  import {themeProps} from '../utils/themeProps';
22
21
  import {
@@ -123,6 +122,31 @@ function getServerPlatformSnapshot(): boolean {
123
122
  return false;
124
123
  }
125
124
 
125
+ /**
126
+ * Detects whether the current platform is macOS/iOS.
127
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
128
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
129
+ * supported) when it is absent or blank.
130
+ */
131
+ function detectMac(): boolean {
132
+ if (typeof navigator === 'undefined') {
133
+ return false;
134
+ }
135
+ // Prefer User-Agent Client Hints API (not deprecated)
136
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
137
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
138
+ const uaPlatform = (uaData as {platform?: unknown}).platform;
139
+ // A blank platform is no answer, not a negative one. Builds that rewrite
140
+ // their client-hints identity ship '', so fall through rather than
141
+ // reading it as "not Apple".
142
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
143
+ return /mac/i.test(uaPlatform);
144
+ }
145
+ }
146
+ // Fallback: navigator.platform (deprecated but still shipped everywhere)
147
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
148
+ }
149
+
126
150
  export interface KbdProps extends BaseProps<HTMLSpanElement> {
127
151
  ref?: React.Ref<HTMLSpanElement>;
128
152
  /**
@@ -159,7 +183,7 @@ export interface KbdProps extends BaseProps<HTMLSpanElement> {
159
183
  export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
160
184
  const isMac = useSyncExternalStore(
161
185
  subscribeToPlatformChanges,
162
- isApplePlatform,
186
+ detectMac,
163
187
  getServerPlatformSnapshot,
164
188
  );
165
189
 
@@ -67,6 +67,24 @@ describe('useHotkeys', () => {
67
67
  expect(onPress).toHaveBeenCalledTimes(1);
68
68
  });
69
69
 
70
+ it('reads a blank userAgentData.platform as unknown, not as non-Apple', () => {
71
+ // Builds that rewrite their client-hints identity expose the key with an
72
+ // empty value; navigator.platform is the only surface left that answers.
73
+ vi.stubGlobal('navigator', {
74
+ userAgentData: {platform: ''},
75
+ platform: 'MacIntel',
76
+ });
77
+ const onPress = vi.fn();
78
+ renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
79
+
80
+ press('k', {ctrlKey: true});
81
+ expect(onPress).not.toHaveBeenCalled();
82
+
83
+ const event = press('k', {metaKey: true});
84
+ expect(onPress).toHaveBeenCalledTimes(1);
85
+ expect(onPress).toHaveBeenCalledWith(event);
86
+ });
87
+
70
88
  it('does not fire a bare key when modifiers are held', () => {
71
89
  stubApplePlatform();
72
90
  const onPress = vi.fn();
@@ -18,8 +18,7 @@
18
18
  * unless a hotkey opts in via `allowInInputs`.
19
19
  *
20
20
  * Platform-aware: `mod` maps to metaKey (⌘) on Apple platforms and
21
- * ctrlKey elsewhere, through the same isApplePlatform util Kbd reads, so
22
- * displayed and handled shortcuts cannot disagree.
21
+ * ctrlKey elsewhere — mirrors the detection used by Kbd.
23
22
  *
24
23
  * SYNC: When modified, update:
25
24
  * - /packages/core/src/hooks/index.ts
@@ -27,7 +26,6 @@
27
26
  */
28
27
 
29
28
  import {useEffect, useRef} from 'react';
30
- import {isApplePlatform} from '../utils/isApplePlatform';
31
29
 
32
30
  /**
33
31
  * A single keyboard shortcut registration.
@@ -73,6 +71,30 @@ const KEY_ALIASES: Record<string, string> = {
73
71
  plus: '+',
74
72
  };
75
73
 
74
+ /**
75
+ * Detects whether the current platform is macOS/iOS.
76
+ * Prefers the User-Agent Client Hints API when it names a platform (modern
77
+ * Chrome/Edge), falls back to navigator.platform (deprecated but universally
78
+ * supported) when it is absent or blank.
79
+ * Mirrors the detection used by Kbd so displayed and handled shortcuts agree.
80
+ */
81
+ function isApplePlatform(): boolean {
82
+ if (typeof navigator === 'undefined') {
83
+ return false;
84
+ }
85
+ const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
86
+ if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
87
+ const uaPlatform = (uaData as {platform?: unknown}).platform;
88
+ // A blank platform is no answer, not a negative one. Builds that rewrite
89
+ // their client-hints identity ship '', so fall through rather than
90
+ // reading it as "not Apple".
91
+ if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
92
+ return /mac/i.test(uaPlatform);
93
+ }
94
+ }
95
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
96
+ }
97
+
76
98
  /**
77
99
  * Whether the event target is a typing surface where global shortcuts
78
100
  * should stay silent by default.
@@ -213,7 +213,6 @@ function discoverComponents(): ComponentInfo[] {
213
213
  // ---------------------------------------------------------------------------
214
214
 
215
215
  const DIR_TO_REGISTRY_KEY: Record<string, string> = {
216
- Avatar: 'avatar',
217
216
  Banner: 'banner',
218
217
  Button: 'button',
219
218
  Card: 'card',
@@ -43,7 +43,6 @@ export interface DerivedVarEntry {
43
43
  * entries share the same property.
44
44
  */
45
45
  export const derivedVarRegistry: Record<string, DerivedVarEntry[]> = {
46
- avatar: [{property: 'borderRadius', vars: ['--_avatar-radius']}],
47
46
  banner: [{property: 'borderRadius', vars: ['--_banner-radius']}],
48
47
  button: [{property: 'borderRadius', vars: ['--_button-radius']}],
49
48
  card: [
@@ -44,11 +44,8 @@ export const syntaxTokenDefaults = {
44
44
  '--color-syntax-attribute': 'var(--color-text-teal)',
45
45
  // property -> cyan text (object properties)
46
46
  '--color-syntax-property': 'var(--color-text-cyan)',
47
- // punctuation -> secondary text (brackets, semicolons). Not disabled text:
48
- // disabled grey is calibrated to fail AA on purpose (WCAG exempts inactive
49
- // controls), and punctuation in a code sample is normal, always-active
50
- // text — see #5386.
51
- '--color-syntax-punctuation': 'var(--color-text-secondary)',
47
+ // punctuation -> disabled text (brackets, semicolons)
48
+ '--color-syntax-punctuation': 'var(--color-text-disabled)',
52
49
  // background -> muted surface
53
50
  '--color-syntax-background': 'var(--color-background-muted)',
54
51
  } as const;
@@ -1,10 +0,0 @@
1
- import type { DateInputProps } from './DateInput';
2
- /**
3
- * The OS-picker surface. Takes `DateInput`'s props verbatim; see
4
- * {@link DateInput} for when it is chosen over the other two.
5
- */
6
- export declare function NativeDateField({ label, isLabelHidden, description, isOptional, isRequired, isDisabled, disabledMessage, value, onChange, isLoading, min, max, dateConstraints, placeholder: placeholderFromProps, size: sizeProp, status, statusVariant, labelTooltip, hasClear, numberOfMonths: _numberOfMonths, weekStartsOn: _weekStartsOn, format, width, xstyle, className, style, ref, ...rest }: DateInputProps): import("react").JSX.Element;
7
- export declare namespace NativeDateField {
8
- var displayName: string;
9
- }
10
- //# sourceMappingURL=NativeDateField.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"NativeDateField.d.ts","sourceRoot":"","sources":["../../src/DateInput/NativeDateField.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAwLhD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,KAAK,EACL,QAAQ,EACR,SAAiB,EACjB,GAAG,EACH,GAAG,EACH,eAAe,EACf,WAAW,EAAE,oBAAoB,EACjC,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,aAA0B,EAC1B,YAAY,EACZ,QAAgB,EAGhB,cAAc,EAAE,eAAe,EAC/B,YAAY,EAAE,aAAa,EAC3B,MAAoB,EACpB,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,cAAc,+BAmXhB;yBAlZe,eAAe"}