@astryxdesign/core 0.1.0 → 0.1.1-canary.129bf0e
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/README.md +68 -0
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -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/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -3
- package/dist/Layer/useLayer.d.ts +13 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +7 -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/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +13 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -0
- 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/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +31 -27
- 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/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +26 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +30 -7
- package/dist/Table/index.d.ts +3 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +376 -0
- package/dist/Table/types.d.ts +90 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.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/astryx.css +11 -0
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- 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 +7 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Button/Button.test.tsx +26 -11
- package/src/Button/Button.tsx +2 -0
- 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/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/FormLayout/FormLayout.doc.mjs +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +178 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +12 -10
- package/src/Icon/Icon.doc.mjs +4 -4
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Layer/useLayer.doc.mjs +7 -2
- package/src/Layer/useLayer.tsx +19 -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/Lightbox/Lightbox.doc.mjs +0 -2
- package/src/Link/Link.doc.mjs +3 -3
- package/src/Link/LinkProvider.doc.mjs +3 -3
- package/src/Markdown/Markdown.doc.mjs +6 -4
- package/src/Markdown/Markdown.test.tsx +17 -26
- package/src/Markdown/Markdown.tsx +16 -6
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- 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/Pagination/Pagination.test.tsx +137 -13
- package/src/Pagination/Pagination.tsx +33 -28
- package/src/Resizable/Resizable.doc.mjs +3 -3
- package/src/Resizable/useResizable.ts +1 -7
- package/src/Selector/Selector.doc.mjs +4 -0
- package/src/Selector/Selector.tsx +5 -6
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Table/BaseTable.tsx +50 -24
- package/src/Table/Table.doc.mjs +3 -3
- package/src/Table/Table.tsx +22 -1
- package/src/Table/index.ts +3 -0
- package/src/Table/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +414 -0
- package/src/Table/types.ts +96 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- 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/Toolbar/Toolbar.doc.mjs +1 -1
- 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
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":"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"}
|
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.129bf0e",
|
|
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",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"main": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
|
+
"unpkg": "./dist/astryx.umd.js",
|
|
31
|
+
"jsdelivr": "./dist/astryx.umd.js",
|
|
30
32
|
"sideEffects": [
|
|
31
33
|
"**/*.stylex.ts",
|
|
32
34
|
"**/*.stylex.js",
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"types": "./src/astryx.css.d.ts",
|
|
48
50
|
"default": "./dist/astryx.css"
|
|
49
51
|
},
|
|
52
|
+
"./astryx.umd.js": "./dist/astryx.umd.js",
|
|
50
53
|
"./tailwind-theme.css": {
|
|
51
54
|
"types": "./src/tailwind-theme.css.d.ts",
|
|
52
55
|
"default": "./src/tailwind-theme.css"
|
|
@@ -635,9 +638,10 @@
|
|
|
635
638
|
"@testing-library/react": "^16.3.2"
|
|
636
639
|
},
|
|
637
640
|
"scripts": {
|
|
638
|
-
"build": "rm -rf dist && pnpm build:esm && tsc --project tsconfig.build.json && pnpm build:css",
|
|
641
|
+
"build": "rm -rf dist && pnpm build:esm && tsc --project tsconfig.build.json && pnpm build:css && pnpm build:umd",
|
|
639
642
|
"build:esm": "babel src --out-dir dist --extensions '.ts,.tsx' --out-file-extension '.js' --ignore '**/*.test.ts,**/*.test.tsx,**/__tests__/**,**/*.perf.test.*,**/*.doc.mjs,**/*.d.ts,**/*.css,**/*.css.d.ts' --config-file ./babel.config.json",
|
|
640
643
|
"build:css": "node ../../scripts/build-css.mjs",
|
|
644
|
+
"build:umd": "node ../../scripts/build-umd.mjs",
|
|
641
645
|
"dev": "babel src --watch --out-dir dist --extensions '.ts,.tsx' --out-file-extension '.js' --ignore '**/*.test.*,**/*.doc.mjs,**/*.d.ts,**/*.css*' --config-file ./babel.config.json",
|
|
642
646
|
"test": "vitest run --root ../..",
|
|
643
647
|
"test:watch": "vitest --root ../..",
|
|
@@ -132,6 +132,7 @@ export function AvatarGroupOverflow({
|
|
|
132
132
|
xstyle,
|
|
133
133
|
className,
|
|
134
134
|
style,
|
|
135
|
+
...rest
|
|
135
136
|
}: AvatarGroupOverflowProps): ReactNode {
|
|
136
137
|
const group = useAvatarGroup();
|
|
137
138
|
const numericSize = group?.numericSize ?? 36;
|
|
@@ -147,6 +148,7 @@ export function AvatarGroupOverflow({
|
|
|
147
148
|
type="button"
|
|
148
149
|
onClick={onClick}
|
|
149
150
|
aria-label={label}
|
|
151
|
+
{...rest}
|
|
150
152
|
{...mergeProps(
|
|
151
153
|
themeProps('avatar-group-overflow'),
|
|
152
154
|
stylex.props(
|
|
@@ -170,6 +172,7 @@ export function AvatarGroupOverflow({
|
|
|
170
172
|
<span
|
|
171
173
|
ref={ref}
|
|
172
174
|
aria-label={label}
|
|
175
|
+
{...rest}
|
|
173
176
|
{...mergeProps(
|
|
174
177
|
themeProps('avatar-group-overflow'),
|
|
175
178
|
stylex.props(
|
|
@@ -76,11 +76,22 @@ describe('Banner', () => {
|
|
|
76
76
|
expect(screen.getByText('This is a description')).toBeInTheDocument();
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
it('renders title and description as <div> (never <p>) for composition safety', () => {
|
|
80
|
+
const {container} = render(
|
|
81
|
+
<Banner status="info" title="Title" description="Description" />,
|
|
82
|
+
);
|
|
83
|
+
// Block content can be nested inside Banner text slots without tripping
|
|
84
|
+
// the phrasing-content trap that <p> imposes, so neither slot is a <p>.
|
|
85
|
+
expect(container.querySelector('p')).toBeNull();
|
|
86
|
+
expect(screen.getByText('Title').tagName).toBe('DIV');
|
|
87
|
+
expect(screen.getByText('Description').tagName).toBe('DIV');
|
|
88
|
+
});
|
|
89
|
+
|
|
79
90
|
it('does not render description when not provided', () => {
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
expect(
|
|
91
|
+
render(<Banner status="info" title="Title Only" />);
|
|
92
|
+
// Title renders; no description text is present.
|
|
93
|
+
expect(screen.getByText('Title Only')).toBeInTheDocument();
|
|
94
|
+
expect(screen.queryByText('This is a description')).not.toBeInTheDocument();
|
|
84
95
|
});
|
|
85
96
|
|
|
86
97
|
it('renders dismiss button when isDismissable', () => {
|
|
@@ -158,9 +169,7 @@ describe('Banner', () => {
|
|
|
158
169
|
});
|
|
159
170
|
|
|
160
171
|
it('renders card container by default', () => {
|
|
161
|
-
const {container} = render(
|
|
162
|
-
<Banner status="info" title="Card Container" />,
|
|
163
|
-
);
|
|
172
|
+
const {container} = render(<Banner status="info" title="Card Container" />);
|
|
164
173
|
const root = container.firstElementChild;
|
|
165
174
|
expect(root).toBeInTheDocument();
|
|
166
175
|
});
|
package/src/Banner/Banner.tsx
CHANGED
|
@@ -16,6 +16,13 @@
|
|
|
16
16
|
* - Each visual area owns its own border-radius (no overflow:clip on the container)
|
|
17
17
|
* - When children are provided, a collapse/expand toggle button appears in the end area
|
|
18
18
|
*
|
|
19
|
+
* Title and description render as <div> (not <p>): they accept arbitrary
|
|
20
|
+
* ReactNode content, and <p> cannot legally contain block-level children
|
|
21
|
+
* (the HTML parser reparents them, desyncing SSR markup from the hydrated
|
|
22
|
+
* DOM). Using <div> keeps these slots composable with any content. Their
|
|
23
|
+
* StyleX styles set margin: 0 and explicit typography, so the rendered
|
|
24
|
+
* appearance is identical to the previous <p>.
|
|
25
|
+
*
|
|
19
26
|
* SYNC: When modified, update these files to stay in sync:
|
|
20
27
|
* - /packages/core/src/Banner/Banner.doc.mjs (props table, features, implementation notes)
|
|
21
28
|
* - /packages/core/src/Banner/Banner.test.tsx (tests for new/changed behavior)
|
|
@@ -441,9 +448,9 @@ export function Banner({
|
|
|
441
448
|
)}
|
|
442
449
|
</div>
|
|
443
450
|
<div {...stylex.props(styles.headerContent)}>
|
|
444
|
-
<
|
|
451
|
+
<div {...stylex.props(styles.title)}>{title}</div>
|
|
445
452
|
{description != null && (
|
|
446
|
-
<
|
|
453
|
+
<div {...stylex.props(styles.description)}>{description}</div>
|
|
447
454
|
)}
|
|
448
455
|
</div>
|
|
449
456
|
{showEndArea && (
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
13
|
+
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {Button} from './Button';
|
|
16
16
|
import {Badge} from '../Badge/Badge';
|
|
@@ -228,11 +228,7 @@ describe('Button', () => {
|
|
|
228
228
|
order.push('clickAction');
|
|
229
229
|
});
|
|
230
230
|
render(
|
|
231
|
-
<Button
|
|
232
|
-
label="Test"
|
|
233
|
-
onClick={handleClick}
|
|
234
|
-
clickAction={handleAction}
|
|
235
|
-
/>,
|
|
231
|
+
<Button label="Test" onClick={handleClick} clickAction={handleAction} />,
|
|
236
232
|
);
|
|
237
233
|
|
|
238
234
|
await user.click(screen.getByRole('button'));
|
|
@@ -246,11 +242,7 @@ describe('Button', () => {
|
|
|
246
242
|
const handleClick = vi.fn((e: React.MouseEvent) => e.preventDefault());
|
|
247
243
|
const handleAction = vi.fn();
|
|
248
244
|
render(
|
|
249
|
-
<Button
|
|
250
|
-
label="Test"
|
|
251
|
-
onClick={handleClick}
|
|
252
|
-
clickAction={handleAction}
|
|
253
|
-
/>,
|
|
245
|
+
<Button label="Test" onClick={handleClick} clickAction={handleAction} />,
|
|
254
246
|
);
|
|
255
247
|
|
|
256
248
|
await user.click(screen.getByRole('button'));
|
|
@@ -258,6 +250,29 @@ describe('Button', () => {
|
|
|
258
250
|
expect(handleAction).not.toHaveBeenCalled();
|
|
259
251
|
});
|
|
260
252
|
|
|
253
|
+
it('fires clickAction once on a fast double-click (no double-submit)', async () => {
|
|
254
|
+
let resolveAction: (() => void) | undefined;
|
|
255
|
+
const handleAction = vi.fn(
|
|
256
|
+
async () =>
|
|
257
|
+
new Promise<void>(resolve => {
|
|
258
|
+
resolveAction = resolve;
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
render(<Button label="Pay" clickAction={handleAction} />);
|
|
262
|
+
|
|
263
|
+
const button = screen.getByRole('button');
|
|
264
|
+
await act(async () => {
|
|
265
|
+
fireEvent.click(button);
|
|
266
|
+
fireEvent.click(button);
|
|
267
|
+
});
|
|
268
|
+
expect(handleAction).toHaveBeenCalledTimes(1);
|
|
269
|
+
|
|
270
|
+
await act(async () => {
|
|
271
|
+
resolveAction?.();
|
|
272
|
+
await Promise.resolve();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
261
276
|
// type/name/value/form props
|
|
262
277
|
it('defaults type to button', () => {
|
|
263
278
|
render(<Button label="Test" />);
|
package/src/Button/Button.tsx
CHANGED
|
@@ -513,6 +513,8 @@ export function Button({
|
|
|
513
513
|
const buttonGroup = useButtonGroup();
|
|
514
514
|
|
|
515
515
|
const [isPending, startTransition] = useTransition();
|
|
516
|
+
// clickAction is fire-once (submit/save/pay), so a same-tick double-click must
|
|
517
|
+
// dedupe — which neither isPending nor useOptimistic do. Hence the ref guard.
|
|
516
518
|
const actionInFlightRef = useRef(false);
|
|
517
519
|
const isLoadingState = isLoading || isPending;
|
|
518
520
|
const groupDisabled = buttonGroup?.isDisabled ?? false;
|
|
@@ -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[] = [];
|