@astryxdesign/core 0.1.0 → 0.1.1-canary.a514b99
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/CHANGELOG.md +66 -0
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +5 -1
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/DropdownMenu/{renderXDSDropdownItems.d.ts → renderDropdownItems.d.ts} +3 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -0
- package/dist/DropdownMenu/{renderXDSDropdownItems.js → renderDropdownItems.js} +2 -2
- package/dist/Layout/Layout.d.ts +10 -1
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +5 -1
- package/dist/Outline/Outline.d.ts +3 -2
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +23 -4
- package/dist/Outline/useScrollSpy.d.ts +14 -1
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +161 -50
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +1 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- package/dist/ToggleButton/ToggleButton.d.ts +10 -3
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +64 -18
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +1 -1
- package/dist/theme/tokens.d.ts +1 -1
- package/dist/theme/tokens.js +4 -4
- package/dist/theme/useTheme.d.ts +2 -2
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +15 -2
- package/package.json +2 -2
- package/src/Chat/ChatLayoutScrollButton.tsx +7 -1
- package/src/Collapsible/useCollapsible.doc.mjs +2 -2
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +68 -20
- package/src/Divider/Divider.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/DropdownMenu/{renderXDSDropdownItems.tsx → renderDropdownItems.tsx} +2 -2
- package/src/FormLayout/FormLayout.doc.mjs +3 -3
- package/src/Icon/Icon.doc.mjs +4 -4
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Layout/Layout.doc.mjs +2 -1
- package/src/Layout/Layout.tsx +15 -1
- package/src/Layout/__tests__/childrenAsContent.test.tsx +59 -0
- package/src/Link/Link.doc.mjs +3 -3
- package/src/Link/LinkProvider.doc.mjs +3 -3
- package/src/Markdown/Markdown.doc.mjs +4 -4
- package/src/Outline/Outline.doc.mjs +1 -1
- package/src/Outline/Outline.test.tsx +76 -38
- package/src/Outline/Outline.tsx +23 -4
- package/src/Outline/useScrollSpy.ts +196 -63
- package/src/Resizable/Resizable.doc.mjs +2 -2
- package/src/Resizable/useResizable.ts +1 -7
- package/src/Selector/Selector.tsx +5 -6
- package/src/Table/Table.doc.mjs +3 -3
- package/src/ToggleButton/ToggleButton.doc.mjs +2 -2
- package/src/ToggleButton/ToggleButton.test.tsx +148 -6
- package/src/ToggleButton/ToggleButton.tsx +83 -20
- package/src/hooks/useEntryAnimation.doc.mjs +3 -3
- package/src/hooks/useMediaQuery.doc.mjs +2 -2
- package/src/hooks/useStreamingText.doc.mjs +3 -3
- package/src/theme/Theme.doc.mjs +2 -2
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +1 -1
- package/src/theme/index.ts +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +1 -1
- package/src/theme/tokens.ts +4 -4
- package/src/theme/useTheme.ts +2 -2
- package/src/utils/dateParser.test.ts +26 -0
- package/src/utils/dateParser.ts +16 -2
- package/dist/DropdownMenu/renderXDSDropdownItems.d.ts.map +0 -1
|
@@ -486,7 +486,7 @@ export function Selector(props) {
|
|
|
486
486
|
onKeyDown: onKeyDown,
|
|
487
487
|
tabIndex: -1,
|
|
488
488
|
...{
|
|
489
|
-
className: "x78zum5 x6s0dn4 x1qughib x1txdalj x1iyjqo2 xs83m0k x1r8uery xeuugli x1717udv x1ghz6dp xc342km xng3xce xjbqb8w xjb2p0i x1qlqyl8 x15bjb6t x1heor9g x1ypdohk x1a2a7pz
|
|
489
|
+
className: "x78zum5 x6s0dn4 x1qughib x1txdalj x1iyjqo2 xs83m0k x1r8uery xeuugli x1717udv x1ghz6dp xc342km xng3xce xjbqb8w xjb2p0i x1qlqyl8 x15bjb6t x1heor9g x1ypdohk x1a2a7pz"
|
|
490
490
|
},
|
|
491
491
|
children: /*#__PURE__*/_jsx("span", {
|
|
492
492
|
...{
|
|
@@ -38,8 +38,15 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
|
|
|
38
38
|
*/
|
|
39
39
|
onPressedChange?: (isPressed: boolean) => void;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
* The button shows a loading spinner while the
|
|
41
|
+
* Action handler for API- or navigation-backed toggles, run inside a
|
|
42
|
+
* transition. The button shows a loading spinner while the action is
|
|
43
|
+
* pending — whether it returns a promise or synchronously triggers a
|
|
44
|
+
* suspending update (e.g. a router navigation that suspends on data).
|
|
45
|
+
*
|
|
46
|
+
* Because it runs in a transition, the toggle is *interruptible*: clicking
|
|
47
|
+
* again while an action is pending starts a new transition with the next
|
|
48
|
+
* optimistic state, so the action reflects the latest intent rather than
|
|
49
|
+
* being dropped.
|
|
43
50
|
*
|
|
44
51
|
* @example
|
|
45
52
|
* ```
|
|
@@ -53,7 +60,7 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
|
|
|
53
60
|
* />
|
|
54
61
|
* ```
|
|
55
62
|
*/
|
|
56
|
-
pressedChangeAction?: (isPressed: boolean) => Promise<void>;
|
|
63
|
+
pressedChangeAction?: (isPressed: boolean) => void | Promise<void>;
|
|
57
64
|
/**
|
|
58
65
|
* The size of the toggle button.
|
|
59
66
|
* When used inside ToggleButtonGroup, defaults to the group's size.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToggleButton.d.ts","sourceRoot":"","sources":["../../src/ToggleButton/ToggleButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"ToggleButton.d.ts","sourceRoot":"","sources":["../../src/ToggleButton/ToggleButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,EAAS,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAElD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAyE5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAE/C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,KAAK,EACL,SAAS,EAAE,aAAa,EACxB,eAAe,EAAE,mBAAmB,EACpC,mBAAmB,EACnB,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,cAAsB,EAClC,SAAiB,EACjB,IAAI,EACJ,UAAkB,EAClB,WAAW,EACX,QAAQ,EACR,OAAO,EACP,KAAK,EACL,MAAM,EACN,SAAS,EAAE,UAAU,EACrB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,GAAG,SAAS,CAyG/B;yBA5He,YAAY"}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - /apps/storybook/stories/ToggleButton.stories.tsx
|
|
20
20
|
* - /packages/cli/templates/blocks/components/ToggleButton/ (showcase blocks)
|
|
21
21
|
*/
|
|
22
|
-
import React, { useCallback } from 'react';
|
|
22
|
+
import React, { useCallback, useEffect, useOptimistic, useState, useTransition } from 'react';
|
|
23
23
|
import * as stylex from '@stylexjs/stylex';
|
|
24
24
|
import "../theme/tokens.stylex.js";
|
|
25
25
|
import { colorVars, fontWeightVars } from "../theme/tokens.stylex.js";
|
|
@@ -27,6 +27,38 @@ import { Button } from "../Button/index.js";
|
|
|
27
27
|
import { useToggleButtonGroup } from "./ToggleButtonGroup.js";
|
|
28
28
|
import { themeProps } from "../utils/themeProps.js";
|
|
29
29
|
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Constants & helpers
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The spinner only appears once the action has been pending for this long.
|
|
36
|
+
* A fast action shows the optimistic pressed state immediately with no spinner
|
|
37
|
+
* flash, and rapid re-clicks can interrupt the in-flight action before the
|
|
38
|
+
* button locks behind the spinner.
|
|
39
|
+
*/
|
|
40
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
41
|
+
const PENDING_SPINNER_DELAY_MS = 150;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns `true` only once `active` has stayed `true` for `delayMs`.
|
|
45
|
+
* Used to debounce the loading spinner so the optimistic state shows first.
|
|
46
|
+
*/
|
|
47
|
+
function useDelayed(active, delayMs) {
|
|
48
|
+
const [delayed, setDelayed] = useState(false);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!active) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const timer = setTimeout(() => setDelayed(true), delayMs);
|
|
54
|
+
return () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
setDelayed(false);
|
|
57
|
+
};
|
|
58
|
+
}, [active, delayMs]);
|
|
59
|
+
return active && delayed;
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
// =============================================================================
|
|
31
63
|
// Styles
|
|
32
64
|
// =============================================================================
|
|
@@ -36,7 +68,6 @@ import { themeProps } from "../utils/themeProps.js";
|
|
|
36
68
|
* A hidden span renders the same text at semibold weight to reserve
|
|
37
69
|
* the wider width, preventing layout shift when toggling.
|
|
38
70
|
*/
|
|
39
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
40
71
|
const pressedStyles = {
|
|
41
72
|
background: {
|
|
42
73
|
kWkggS: "xi89dp7",
|
|
@@ -93,32 +124,47 @@ export function ToggleButton({
|
|
|
93
124
|
style,
|
|
94
125
|
...props
|
|
95
126
|
}) {
|
|
96
|
-
// Read group context if inside a group
|
|
97
127
|
const group = useToggleButtonGroup();
|
|
98
|
-
|
|
99
|
-
// Resolve state from group or props
|
|
100
|
-
const isPressed = group && value != null ? group.selectedValues.has(value) : isPressedProp ?? false;
|
|
128
|
+
const committedPressed = group && value != null ? group.selectedValues.has(value) : isPressedProp ?? false;
|
|
101
129
|
const size = sizeProp ?? group?.size ?? 'md';
|
|
102
130
|
const isDisabled = group?.isDisabled ?? isDisabledProp;
|
|
131
|
+
|
|
132
|
+
// Track the pressed state optimistically. While an action is pending, the
|
|
133
|
+
// button reflects the intended (optimistic) state immediately, and a click
|
|
134
|
+
// mid-flight derives its next state from this value — so rapid toggles read
|
|
135
|
+
// true -> false -> true rather than stalling on the last committed value.
|
|
136
|
+
const [optimisticPressed, setOptimisticPressed] = useOptimistic(committedPressed);
|
|
137
|
+
const isPressed = optimisticPressed;
|
|
103
138
|
const resolvedIcon = isPressed && pressedIcon ? pressedIcon : icon;
|
|
139
|
+
|
|
140
|
+
// Run the toggle inside a transition. The action is interruptible: clicking
|
|
141
|
+
// again while it is pending starts a fresh transition with the next
|
|
142
|
+
// optimistic state instead of being dropped, so there is no re-entry guard.
|
|
143
|
+
// Both onPressedChange and pressedChangeAction run inside the transition,
|
|
144
|
+
// which means a synchronous-but-suspending handler (e.g. a router navigation
|
|
145
|
+
// that suspends on data) also drives the pending state — not just promises.
|
|
146
|
+
const [isPending, startTransition] = useTransition();
|
|
147
|
+
// Debounce the spinner so a fast action shows the optimistic state without a
|
|
148
|
+
// spinner flash, and rapid re-clicks can interrupt before the button locks.
|
|
149
|
+
const showSpinner = useDelayed(isPending, PENDING_SPINNER_DELAY_MS);
|
|
150
|
+
const isLoadingState = isLoading || showSpinner;
|
|
104
151
|
const handleClick = useCallback(() => {
|
|
105
|
-
if (isDisabled
|
|
152
|
+
if (isDisabled) {
|
|
106
153
|
return;
|
|
107
154
|
}
|
|
108
155
|
if (group && value != null) {
|
|
109
|
-
//
|
|
156
|
+
// Group mode delegates selection to the group; no async-action path.
|
|
110
157
|
group.toggle(value);
|
|
111
|
-
|
|
112
|
-
// Standalone toggle
|
|
113
|
-
const newState = !isPressed;
|
|
114
|
-
onPressedChangeProp(newState);
|
|
115
|
-
if (pressedChangeAction) {
|
|
116
|
-
void pressedChangeAction(newState);
|
|
117
|
-
}
|
|
158
|
+
return;
|
|
118
159
|
}
|
|
119
|
-
|
|
160
|
+
const newState = !optimisticPressed;
|
|
161
|
+
startTransition(async () => {
|
|
162
|
+
setOptimisticPressed(newState);
|
|
163
|
+
onPressedChangeProp?.(newState);
|
|
164
|
+
await pressedChangeAction?.(newState);
|
|
165
|
+
});
|
|
166
|
+
}, [isDisabled, group, value, optimisticPressed, onPressedChangeProp, pressedChangeAction, setOptimisticPressed]);
|
|
120
167
|
|
|
121
|
-
// Label with font weight shift and width reservation
|
|
122
168
|
// isIconOnly prop is the source of truth for icon-only rendering.
|
|
123
169
|
const labelContent = children != null ? /*#__PURE__*/_jsxs("span", {
|
|
124
170
|
...{
|
|
@@ -165,7 +211,7 @@ export function ToggleButton({
|
|
|
165
211
|
variant: "ghost",
|
|
166
212
|
size: size,
|
|
167
213
|
isDisabled: isDisabled,
|
|
168
|
-
isLoading:
|
|
214
|
+
isLoading: isLoadingState,
|
|
169
215
|
isIconOnly: isIconOnly,
|
|
170
216
|
"aria-pressed": isPressed,
|
|
171
217
|
icon: resolvedIcon,
|
package/dist/theme/Theme.js
CHANGED
|
@@ -115,7 +115,7 @@ function useThemeStyleInjection(theme) {
|
|
|
115
115
|
// One-time perf hint per theme
|
|
116
116
|
if (!warnedThemes.has(theme.name)) {
|
|
117
117
|
warnedThemes.add(theme.name);
|
|
118
|
-
console.warn(`[
|
|
118
|
+
console.warn(`[Astryx] Theme "${theme.name}" is using runtime style injection. ` + `For better performance, use the pre-built theme:\n\n` + ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` + ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` + `For custom themes, run \`npx astryx theme build <file>\` to generate ` + `the built artifacts.`);
|
|
119
119
|
}
|
|
120
120
|
const {
|
|
121
121
|
prose,
|
|
@@ -296,7 +296,7 @@ export interface DefinedTheme {
|
|
|
296
296
|
__onLight?: ResolvedOnMedia;
|
|
297
297
|
}
|
|
298
298
|
/** All Astryx token defaults as a flat map. Useful for resolving full token sets. */
|
|
299
|
-
export declare const
|
|
299
|
+
export declare const tokenDefaults: Record<string, string>;
|
|
300
300
|
/**
|
|
301
301
|
* Create an Astryx theme.
|
|
302
302
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineTheme.d.ts","sourceRoot":"","sources":["../../src/theme/defineTheme.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"defineTheme.d.ts","sourceRoot":"","sources":["../../src/theme/defineTheme.ts"],"names":[],"mappings":"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"}
|
|
@@ -115,7 +115,7 @@ import { domainTokenDefaults } from "./domainTokens/index.js";
|
|
|
115
115
|
// =============================================================================
|
|
116
116
|
|
|
117
117
|
/** All Astryx token defaults as a flat map. Useful for resolving full token sets. */
|
|
118
|
-
export const
|
|
118
|
+
export const tokenDefaults = {
|
|
119
119
|
...colorDefaults,
|
|
120
120
|
...spacingDefaults,
|
|
121
121
|
...sizeDefaults,
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
export { Theme } from './Theme';
|
|
14
14
|
export { MediaTheme } from './MediaTheme';
|
|
15
15
|
export type { MediaThemeProps } from './MediaTheme';
|
|
16
|
-
export { defineTheme, generateThemeCSS, generateThemeCSSFlat, generateOnMediaCSS, generateThemeRules, generateThemeRulesSplit, type ThemeCSSOutput, type ThemeRulesSplit, isDefinedTheme,
|
|
16
|
+
export { defineTheme, generateThemeCSS, generateThemeCSSFlat, generateOnMediaCSS, generateThemeRules, generateThemeRulesSplit, type ThemeCSSOutput, type ThemeRulesSplit, isDefinedTheme, tokenDefaults, } from './defineTheme';
|
|
17
17
|
export type { DefineThemeInput, DefinedTheme, CoreTokenName, TokenName, TokenValue, ComponentStyleMap, StyleOverrides, } from './defineTheme';
|
|
18
18
|
export type { SyntaxTokenName, DomainTokenName, DataTokenName, } from './domainTokens';
|
|
19
19
|
export { syntaxTokenDefaults, domainTokenDefaults, dataTokenDefaults, } from './domainTokens';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AACxC,YAAY,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AACxC,YAAY,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,SAAS,EACT,UAAU,EACV,iBAAiB,EACjB,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,eAAe,EACf,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAC,iBAAiB,EAAC,MAAM,UAAU,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAC,WAAW,EAAE,cAAc,EAAC,MAAM,UAAU,CAAC;AACrD,YAAY,EAAC,oBAAoB,EAAC,MAAM,UAAU,CAAC;AAEnD,OAAO,EAAC,eAAe,EAAE,2BAA2B,EAAC,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EAAC,eAAe,EAAE,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAExE,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,YAAY,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAE3E,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,QAAQ,EACR,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAY,EACZ,cAAc,EACd,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAC,QAAQ,EAAE,YAAY,EAAC,MAAM,YAAY,CAAC;AAClD,YAAY,EAAC,cAAc,EAAE,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,QAAQ,EACR,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,UAAU,GACX,MAAM,SAAS,CAAC"}
|
package/dist/theme/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export { Theme } from "./Theme.js";
|
|
18
18
|
export { MediaTheme } from "./MediaTheme.js";
|
|
19
|
-
export { defineTheme, generateThemeCSS, generateThemeCSSFlat, generateOnMediaCSS, generateThemeRules, generateThemeRulesSplit, isDefinedTheme,
|
|
19
|
+
export { defineTheme, generateThemeCSS, generateThemeCSSFlat, generateOnMediaCSS, generateThemeRules, generateThemeRulesSplit, isDefinedTheme, tokenDefaults } from "./defineTheme.js";
|
|
20
20
|
export { syntaxTokenDefaults, domainTokenDefaults, dataTokenDefaults } from "./domainTokens/index.js";
|
|
21
21
|
|
|
22
22
|
// Syntax theme API
|
|
@@ -97,7 +97,7 @@ export function resolveSyntaxTokenForMode(value, mode) {
|
|
|
97
97
|
export function defineSyntaxTheme(input) {
|
|
98
98
|
const missing = ALL_SYNTAX_KEYS.filter(key => !(key in input.tokens));
|
|
99
99
|
if (missing.length > 0) {
|
|
100
|
-
console.warn('[
|
|
100
|
+
console.warn('[Astryx] defineSyntaxTheme("' + input.name + '"): missing tokens: ' + missing.join(', ') + '. All 14 syntax tokens are required.');
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// Resolve tuples to light-dark() CSS strings
|
package/dist/theme/tokens.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare const tokenVars: Record<TokenName, string>;
|
|
|
48
48
|
/**
|
|
49
49
|
* Resolve all Astryx token values for a theme and effective color mode.
|
|
50
50
|
*
|
|
51
|
-
* The result starts with `
|
|
51
|
+
* The result starts with `tokenDefaults`, applies `theme.tokens`, then
|
|
52
52
|
* reapplies `theme.__inputTokens` when available so explicit tuple overrides
|
|
53
53
|
* retain their original light/dark sides instead of relying on CSS parsing.
|
|
54
54
|
* This mirrors the token resolution used by `useTheme()` but does not need
|
package/dist/theme/tokens.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - /packages/core/src/theme/index.ts
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { tokenDefaults } from "./defineTheme.js";
|
|
19
19
|
|
|
20
20
|
/** Resolved color mode used when choosing the side of light/dark token values. */
|
|
21
21
|
|
|
@@ -45,7 +45,7 @@ export function tokenVar(name) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** Flat map of every known Astryx token name to its `var(--token-name)` reference. */
|
|
48
|
-
export const tokenVars = Object.fromEntries(Object.keys(
|
|
48
|
+
export const tokenVars = Object.fromEntries(Object.keys(tokenDefaults).map(name => [name, tokenVar(name)]));
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Split the arguments of a CSS function body on the first top-level comma.
|
|
@@ -118,7 +118,7 @@ function resolveXDSTokenValue(value, mode) {
|
|
|
118
118
|
/**
|
|
119
119
|
* Resolve all Astryx token values for a theme and effective color mode.
|
|
120
120
|
*
|
|
121
|
-
* The result starts with `
|
|
121
|
+
* The result starts with `tokenDefaults`, applies `theme.tokens`, then
|
|
122
122
|
* reapplies `theme.__inputTokens` when available so explicit tuple overrides
|
|
123
123
|
* retain their original light/dark sides instead of relying on CSS parsing.
|
|
124
124
|
* This mirrors the token resolution used by `useTheme()` but does not need
|
|
@@ -131,7 +131,7 @@ export function resolveThemeTokens(theme, options) {
|
|
|
131
131
|
mode
|
|
132
132
|
} = options;
|
|
133
133
|
const resolved = {};
|
|
134
|
-
for (const [key, value] of Object.entries(
|
|
134
|
+
for (const [key, value] of Object.entries(tokenDefaults)) {
|
|
135
135
|
resolved[key] = resolveXDSTokenValue(value, mode);
|
|
136
136
|
}
|
|
137
137
|
if (theme == null) {
|
package/dist/theme/useTheme.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface UseThemeReturn {
|
|
|
30
30
|
* For tokens with [light, dark] tuples, returns the value matching
|
|
31
31
|
* the current mode. For single-value tokens, returns the value as-is.
|
|
32
32
|
*
|
|
33
|
-
* Falls back to
|
|
33
|
+
* Falls back to tokenDefaults if the token isn't overridden by the theme.
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
36
|
* ```
|
|
@@ -42,7 +42,7 @@ export interface UseThemeReturn {
|
|
|
42
42
|
/**
|
|
43
43
|
* All tokens resolved for the current color mode.
|
|
44
44
|
*
|
|
45
|
-
* Merges
|
|
45
|
+
* Merges tokenDefaults with the theme's overrides, resolving
|
|
46
46
|
* light-dark() values based on the effective color mode.
|
|
47
47
|
*
|
|
48
48
|
* Memoized — stable reference unless theme or mode changes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dateParser.d.ts","sourceRoot":"","sources":["../../src/utils/dateParser.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAqC,MAAM,aAAa,CAAC;AAE/E,OAAO,EACL,gBAAgB,IAAI,QAAQ,EAC5B,cAAc,IAAI,SAAS,GAC5B,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAK1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"dateParser.d.ts","sourceRoot":"","sources":["../../src/utils/dateParser.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAqC,MAAM,aAAa,CAAC;AAE/E,OAAO,EACL,gBAAgB,IAAI,QAAQ,EAC5B,cAAc,IAAI,SAAS,GAC5B,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAK1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAsG9D"}
|
package/dist/utils/dateParser.js
CHANGED
|
@@ -112,10 +112,23 @@ export function parseDateInput(input) {
|
|
|
112
112
|
return parseNumericDate(+first, +second, currentYear);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// 6. Fall back to native Date parsing for other formats
|
|
115
|
+
// 6. Fall back to native Date parsing for other formats.
|
|
116
|
+
//
|
|
117
|
+
// Skip bare numeric input (e.g. "0", "1", "01", "2026"). These are
|
|
118
|
+
// in-progress values a user is still typing, not complete dates. Native
|
|
119
|
+
// `Date` parsing coerces them into arbitrary dates ("0" -> year 2000 in V8,
|
|
120
|
+
// year 0 in some engines), which is both surprising and — when the year
|
|
121
|
+
// resolves to 0 — produces an out-of-range date that throws downstream.
|
|
122
|
+
// Treat them as not-yet-a-valid-date instead.
|
|
123
|
+
if (/^\d+$/.test(trimmed)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
116
126
|
const parsed = new Date(trimmed);
|
|
117
127
|
if (!isNaN(parsed.getTime())) {
|
|
118
|
-
|
|
128
|
+
const fromDate = plainDateFromDate(parsed);
|
|
129
|
+
// Validate the result so we never return an out-of-range date (e.g. a
|
|
130
|
+
// year of 0), which would throw when later re-parsed.
|
|
131
|
+
return tryCreatePlainDate(fromDate.year, fromDate.month, fromDate.day);
|
|
119
132
|
}
|
|
120
133
|
return null;
|
|
121
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-canary.a514b99",
|
|
4
4
|
"displayName": "XDS Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "https://github.com/facebook/astryx/issues"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
|
-
"
|
|
18
|
+
"astryx",
|
|
19
19
|
"design-system",
|
|
20
20
|
"react",
|
|
21
21
|
"components",
|
|
@@ -90,6 +90,12 @@ const styles = stylex.create({
|
|
|
90
90
|
whiteSpace: 'nowrap',
|
|
91
91
|
paddingInline: spacingVars['--spacing-2'],
|
|
92
92
|
},
|
|
93
|
+
// When a label is shown, the icon sits on the leading edge and the text on
|
|
94
|
+
// the trailing edge. Symmetric padding leaves the text cramped against the
|
|
95
|
+
// pill's rounded edge, so give the trailing side extra breathing room.
|
|
96
|
+
buttonWithLabel: {
|
|
97
|
+
paddingInlineEnd: spacingVars['--spacing-3'],
|
|
98
|
+
},
|
|
93
99
|
});
|
|
94
100
|
|
|
95
101
|
// =============================================================================
|
|
@@ -130,7 +136,7 @@ export function ChatLayoutScrollButton({
|
|
|
130
136
|
variant="ghost"
|
|
131
137
|
size="md"
|
|
132
138
|
onClick={onClick}
|
|
133
|
-
xstyle={styles.button}>
|
|
139
|
+
xstyle={[styles.button, label ? styles.buttonWithLabel : null]}>
|
|
134
140
|
{label ?? undefined}
|
|
135
141
|
</Button>
|
|
136
142
|
</div>
|
|
@@ -38,7 +38,7 @@ export const docs = {
|
|
|
38
38
|
usage: {
|
|
39
39
|
description: 'Reusable hook that encapsulates the collapsible state machine. Supports three modes: group-controlled (inside CollapsibleGroup), controlled (isOpen + onOpenChange), and uncontrolled (self-managed with defaultIsOpen). Used internally by Card and Section.',
|
|
40
40
|
bestPractices: [
|
|
41
|
-
{guidance: true, description: 'Use the hook directly when building custom collapsible components that need
|
|
41
|
+
{guidance: true, description: 'Use the hook directly when building custom collapsible components that need Astryx collapsible behavior without Collapsible wrapper.'},
|
|
42
42
|
{guidance: true, description: 'For accordion behavior, wrap items in CollapsibleGroup and pass unique value props.'},
|
|
43
43
|
{guidance: false, description: 'Implement your own open/close state when useCollapsible already provides it; the hook handles group coordination automatically.'},
|
|
44
44
|
],
|
|
@@ -64,7 +64,7 @@ export const docsDense = {
|
|
|
64
64
|
usage: {
|
|
65
65
|
description: 'Encapsulates collapsible state machine. 3 modes: group-controlled (inside CollapsibleGroup), controlled (isOpen + onOpenChange), uncontrolled (self-managed w/ defaultIsOpen). Used internally by Card + Section.',
|
|
66
66
|
bestPractices: [
|
|
67
|
-
{guidance: true, description: 'Use directly when building custom collapsible components needing
|
|
67
|
+
{guidance: true, description: 'Use directly when building custom collapsible components needing Astryx collapsible behavior w/o Collapsible wrapper.'},
|
|
68
68
|
{guidance: true, description: 'For accordion behavior, wrap items in CollapsibleGroup + pass unique value props.'},
|
|
69
69
|
{guidance: false, description: 'Implement your own open/close state when useCollapsible already provides it; hook handles group coordination automatically.'},
|
|
70
70
|
],
|
|
@@ -36,7 +36,7 @@ import React, {
|
|
|
36
36
|
import type {ReactNode} from 'react';
|
|
37
37
|
import * as stylex from '@stylexjs/stylex';
|
|
38
38
|
import {useLayer} from '../Layer/useLayer';
|
|
39
|
-
import {
|
|
39
|
+
import {renderDropdownItems} from '../DropdownMenu/renderDropdownItems';
|
|
40
40
|
import {
|
|
41
41
|
DropdownMenuContext,
|
|
42
42
|
type DropdownMenuContextValue,
|
|
@@ -278,7 +278,7 @@ export function ContextMenu({
|
|
|
278
278
|
);
|
|
279
279
|
|
|
280
280
|
const resolvedMenuContent =
|
|
281
|
-
props.items !== undefined ?
|
|
281
|
+
props.items !== undefined ? renderDropdownItems(items) : menuContent;
|
|
282
282
|
|
|
283
283
|
return (
|
|
284
284
|
<>
|
|
@@ -21,19 +21,13 @@ describe('DateInput', () => {
|
|
|
21
21
|
|
|
22
22
|
it('renders with placeholder', () => {
|
|
23
23
|
render(
|
|
24
|
-
<DateInput
|
|
25
|
-
label="Date"
|
|
26
|
-
onChange={() => {}}
|
|
27
|
-
placeholder="Pick a date"
|
|
28
|
-
/>,
|
|
24
|
+
<DateInput label="Date" onChange={() => {}} placeholder="Pick a date" />,
|
|
29
25
|
);
|
|
30
26
|
expect(screen.getByPlaceholderText('Pick a date')).toBeInTheDocument();
|
|
31
27
|
});
|
|
32
28
|
|
|
33
29
|
it('displays formatted date when value is provided', () => {
|
|
34
|
-
render(
|
|
35
|
-
<DateInput label="Date" value="2026-01-25" onChange={() => {}} />,
|
|
36
|
-
);
|
|
30
|
+
render(<DateInput label="Date" value="2026-01-25" onChange={() => {}} />);
|
|
37
31
|
expect(screen.getByDisplayValue('January 25, 2026')).toBeInTheDocument();
|
|
38
32
|
});
|
|
39
33
|
|
|
@@ -118,9 +112,7 @@ describe('DateInput', () => {
|
|
|
118
112
|
|
|
119
113
|
it('reverts to previous value on blur when input is invalid', async () => {
|
|
120
114
|
const onChange = vi.fn();
|
|
121
|
-
render(
|
|
122
|
-
<DateInput label="Date" value="2026-01-25" onChange={onChange} />,
|
|
123
|
-
);
|
|
115
|
+
render(<DateInput label="Date" value="2026-01-25" onChange={onChange} />);
|
|
124
116
|
|
|
125
117
|
const input = screen.getByRole('combobox');
|
|
126
118
|
fireEvent.change(input, {target: {value: 'not a date'}});
|
|
@@ -299,9 +291,7 @@ describe('DateInput', () => {
|
|
|
299
291
|
// --- P1: Tab order: calendar button first, then input ---
|
|
300
292
|
|
|
301
293
|
it('renders calendar button before input in DOM order', () => {
|
|
302
|
-
const {container} = render(
|
|
303
|
-
<DateInput label="Date" onChange={() => {}} />,
|
|
304
|
-
);
|
|
294
|
+
const {container} = render(<DateInput label="Date" onChange={() => {}} />);
|
|
305
295
|
const input = container.querySelector('input');
|
|
306
296
|
const button = container.querySelector('button');
|
|
307
297
|
// Calendar button should come before input in the DOM
|
|
@@ -389,9 +379,7 @@ describe('DateInput', () => {
|
|
|
389
379
|
|
|
390
380
|
it('calls onChange with undefined when input is cleared and blurred', () => {
|
|
391
381
|
const onChange = vi.fn();
|
|
392
|
-
render(
|
|
393
|
-
<DateInput label="Date" value="2026-01-25" onChange={onChange} />,
|
|
394
|
-
);
|
|
382
|
+
render(<DateInput label="Date" value="2026-01-25" onChange={onChange} />);
|
|
395
383
|
|
|
396
384
|
const input = screen.getByRole('combobox');
|
|
397
385
|
fireEvent.change(input, {target: {value: ''}});
|
|
@@ -476,9 +464,7 @@ describe('DateInput', () => {
|
|
|
476
464
|
});
|
|
477
465
|
|
|
478
466
|
it('does not show clear button when hasClear is false', () => {
|
|
479
|
-
render(
|
|
480
|
-
<DateInput label="Date" value="2026-01-15" onChange={() => {}} />,
|
|
481
|
-
);
|
|
467
|
+
render(<DateInput label="Date" value="2026-01-15" onChange={() => {}} />);
|
|
482
468
|
expect(
|
|
483
469
|
screen.queryByRole('button', {name: 'Clear Date'}),
|
|
484
470
|
).not.toBeInTheDocument();
|
|
@@ -514,6 +500,68 @@ describe('DateInput', () => {
|
|
|
514
500
|
});
|
|
515
501
|
});
|
|
516
502
|
|
|
503
|
+
// --- Regression: in-progress / leading-zero input must not crash ---
|
|
504
|
+
|
|
505
|
+
describe('incomplete typed input', () => {
|
|
506
|
+
it('does not crash or fire onChange when first digit typed is 0', () => {
|
|
507
|
+
const onChange = vi.fn();
|
|
508
|
+
render(<DateInput label="Date" onChange={onChange} />);
|
|
509
|
+
|
|
510
|
+
const input = screen.getByRole('combobox');
|
|
511
|
+
// Typing a leading "0" (e.g. starting "01" for January) must be treated
|
|
512
|
+
// as incomplete input, not coerced into an (invalid) date that crashes.
|
|
513
|
+
expect(() =>
|
|
514
|
+
fireEvent.change(input, {target: {value: '0'}}),
|
|
515
|
+
).not.toThrow();
|
|
516
|
+
|
|
517
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
518
|
+
expect(input).toHaveValue('0');
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('does not crash or fire onChange when first digit typed is 1', () => {
|
|
522
|
+
const onChange = vi.fn();
|
|
523
|
+
render(<DateInput label="Date" onChange={onChange} />);
|
|
524
|
+
|
|
525
|
+
const input = screen.getByRole('combobox');
|
|
526
|
+
expect(() =>
|
|
527
|
+
fireEvent.change(input, {target: {value: '1'}}),
|
|
528
|
+
).not.toThrow();
|
|
529
|
+
|
|
530
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
531
|
+
expect(input).toHaveValue('1');
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('does not crash while progressively typing a numeric date', () => {
|
|
535
|
+
const onChange = vi.fn();
|
|
536
|
+
render(<DateInput label="Date" onChange={onChange} />);
|
|
537
|
+
|
|
538
|
+
const input = screen.getByRole('combobox');
|
|
539
|
+
// Simulate keystroke-by-keystroke entry of "01/15/2026". The leading
|
|
540
|
+
// single-digit keystrokes must not crash (the original bug).
|
|
541
|
+
for (const partial of ['0', '01', '01/', '01/1', '01/15', '01/15/']) {
|
|
542
|
+
expect(() =>
|
|
543
|
+
fireEvent.change(input, {target: {value: partial}}),
|
|
544
|
+
).not.toThrow();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Completing the date commits it without error.
|
|
548
|
+
expect(() =>
|
|
549
|
+
fireEvent.change(input, {target: {value: '01/15/2026'}}),
|
|
550
|
+
).not.toThrow();
|
|
551
|
+
expect(onChange).toHaveBeenCalledWith('2026-01-15');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('does not crash on blur after typing an incomplete value', () => {
|
|
555
|
+
const onChange = vi.fn();
|
|
556
|
+
render(<DateInput label="Date" onChange={onChange} />);
|
|
557
|
+
|
|
558
|
+
const input = screen.getByRole('combobox');
|
|
559
|
+
fireEvent.change(input, {target: {value: '0'}});
|
|
560
|
+
expect(() => fireEvent.blur(input)).not.toThrow();
|
|
561
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
517
565
|
describe('external value changes', () => {
|
|
518
566
|
it('clears pending input when value changes externally', () => {
|
|
519
567
|
const onChange = vi.fn();
|
|
@@ -108,7 +108,7 @@ export const docsZh = {
|
|
|
108
108
|
|
|
109
109
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
110
110
|
export const docsDense = {
|
|
111
|
-
description: 'visual separator w/ optional label, using
|
|
111
|
+
description: 'visual separator w/ optional label, using Astryx design tokens',
|
|
112
112
|
usage: {
|
|
113
113
|
description: 'A visual separator that divides content into distinct sections. Use to create clear boundaries between groups of related content, or to demarcate interactive regions within a layout.',
|
|
114
114
|
bestPractices: [
|
|
@@ -37,7 +37,7 @@ import {Button, type ButtonProps} from '../Button';
|
|
|
37
37
|
import {Icon} from '../Icon';
|
|
38
38
|
import type {IconType} from '../Icon';
|
|
39
39
|
|
|
40
|
-
import {
|
|
40
|
+
import {renderDropdownItems} from './renderDropdownItems';
|
|
41
41
|
import {
|
|
42
42
|
DropdownMenuContext,
|
|
43
43
|
type DropdownMenuContextValue,
|
|
@@ -366,7 +366,7 @@ export function DropdownMenu({
|
|
|
366
366
|
|
|
367
367
|
// Resolve menu content: data-driven items become components
|
|
368
368
|
const menuContent =
|
|
369
|
-
props.items !== undefined ?
|
|
369
|
+
props.items !== undefined ? renderDropdownItems(items) : children;
|
|
370
370
|
|
|
371
371
|
return (
|
|
372
372
|
<>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file
|
|
4
|
+
* @file renderDropdownItems.tsx
|
|
5
5
|
* @output Converts data-driven menu items into DropdownMenuItem components
|
|
6
6
|
* @position Utility; used by DropdownMenu to unify data-driven and compound paths
|
|
7
7
|
*/
|
|
@@ -49,7 +49,7 @@ function getSectionKey(section: DropdownMenuSection, index: number): string {
|
|
|
49
49
|
* Converts data-driven items into DropdownMenuItem components,
|
|
50
50
|
* so both modes share the same rendering and keyboard navigation path.
|
|
51
51
|
*/
|
|
52
|
-
export function
|
|
52
|
+
export function renderDropdownItems(
|
|
53
53
|
items: DropdownMenuOption[],
|
|
54
54
|
): ReactNode {
|
|
55
55
|
const elements: ReactNode[] = [];
|