@astryxdesign/core 0.1.0 → 0.1.1-canary.080d887
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 +119 -1
- 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 +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +51 -13
- 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/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +1 -1
- 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/Slider/Slider.js +2 -2
- 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/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- package/dist/ToggleButton/ToggleButton.d.ts +10 -5
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +40 -17
- package/dist/astryx.css +20 -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.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +73 -5
- 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 +12 -4
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Kbd/Kbd.tsx +2 -9
- 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/Slider/Slider.doc.mjs +4 -0
- package/src/Slider/Slider.test.tsx +3 -16
- package/src/Slider/Slider.tsx +1 -1
- package/src/Table/BaseTable.tsx +50 -24
- package/src/Table/Table.doc.mjs +15 -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/Text/Heading.doc.mjs +13 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/ToggleButton/ToggleButton.doc.mjs +4 -4
- package/src/ToggleButton/ToggleButton.test.tsx +172 -9
- package/src/ToggleButton/ToggleButton.tsx +57 -31
- 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/src/Icon/Icon.doc.mjs
CHANGED
|
@@ -7,6 +7,14 @@ export const docs = {
|
|
|
7
7
|
displayName: 'Icon',
|
|
8
8
|
category: 'Content',
|
|
9
9
|
keywords: ["icon","svg","glyph","symbol","pictogram","graphic","vector"],
|
|
10
|
+
playground: {
|
|
11
|
+
// `icon` is required and its type can't be auto-generated, so the
|
|
12
|
+
// properties-tab preview showed "Missing: icon". Seed a valid semantic
|
|
13
|
+
// icon name so the interactive preview renders.
|
|
14
|
+
defaults: {
|
|
15
|
+
icon: 'search',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
10
18
|
props: [
|
|
11
19
|
{
|
|
12
20
|
name: 'icon',
|
|
@@ -17,7 +25,7 @@ export const docs = {
|
|
|
17
25
|
{
|
|
18
26
|
name: 'color',
|
|
19
27
|
type: "'primary' | 'secondary' | 'tertiary' | 'disabled' | 'accent' | 'success' | 'error' | 'warning' | 'inherit'",
|
|
20
|
-
description: 'Color variant mapped to
|
|
28
|
+
description: 'Color variant mapped to Astryx icon color tokens.',
|
|
21
29
|
default: "'inherit'",
|
|
22
30
|
},
|
|
23
31
|
{
|
|
@@ -62,7 +70,7 @@ export const docsZh = {
|
|
|
62
70
|
{
|
|
63
71
|
name: 'color',
|
|
64
72
|
type: "'primary' | 'secondary' | 'tertiary' | 'disabled' | 'accent' | 'success' | 'error' | 'warning' | 'inherit'",
|
|
65
|
-
description: '映射到
|
|
73
|
+
description: '映射到 Astryx 图标颜色令牌的颜色变体。',
|
|
66
74
|
default: "'inherit'",
|
|
67
75
|
},
|
|
68
76
|
{
|
|
@@ -96,7 +104,7 @@ export const docsZh = {
|
|
|
96
104
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
97
105
|
export const docsDense = {
|
|
98
106
|
description:
|
|
99
|
-
'Renders icons w/
|
|
107
|
+
'Renders icons w/ Astryx design system colors + sizes. Supports direct SVG icon components + semantic icon names that adapt to active theme.',
|
|
100
108
|
usage: {
|
|
101
109
|
description: 'Icons are small visual symbols that represent actions, objects, or concepts. They improve scannability and reinforce meaning alongside text. Supports both direct SVG components and semantic icon names that adapt to the active theme.',
|
|
102
110
|
bestPractices: [
|
|
@@ -113,7 +121,7 @@ export const docsDense = {
|
|
|
113
121
|
},
|
|
114
122
|
propDescriptions: {
|
|
115
123
|
icon: 'Semantic icon name or SVG component. Valid names: close, chevronDown, chevronLeft, chevronRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. For others, pass an SVG component.',
|
|
116
|
-
color: 'Color variant mapped to
|
|
124
|
+
color: 'Color variant mapped to Astryx icon color tokens.',
|
|
117
125
|
size: 'Icon size.',
|
|
118
126
|
},
|
|
119
127
|
};
|
package/src/Item/Item.doc.mjs
CHANGED
|
@@ -51,7 +51,7 @@ export const docs = {
|
|
|
51
51
|
],
|
|
52
52
|
usage: {
|
|
53
53
|
description:
|
|
54
|
-
'A single, flexible item primitive that unifies the "start content + label + description + end content" pattern across
|
|
54
|
+
'A single, flexible item primitive that unifies the "start content + label + description + end content" pattern across Astryx. Use it wherever you need a structured row: dropdown menus, selectors, contact lists, notifications, file browsers, and activity feeds.',
|
|
55
55
|
bestPractices: [
|
|
56
56
|
{guidance: true, description: 'Use named slots (startContent, label, description, endContent) for the common layout. These cover the 80% case.'},
|
|
57
57
|
{guidance: true, description: 'Use density="compact" for menus and dense lists, "balanced" for standard rows, and "spacious" for roomier layouts.'},
|
|
@@ -104,7 +104,7 @@ export const docsZh = {
|
|
|
104
104
|
],
|
|
105
105
|
usage: {
|
|
106
106
|
description:
|
|
107
|
-
'通用项目原语,统一
|
|
107
|
+
'通用项目原语,统一 Astryx 中 "起始内容 + 标签 + 描述 + 结束内容" 的布局模式。适用于下拉菜单、选择器、联系人列表、通知、文件浏览器和活动流等场景。',
|
|
108
108
|
bestPractices: [
|
|
109
109
|
{guidance: true, description: '使用命名插槽(startContent、label、description、endContent)处理常见布局。'},
|
|
110
110
|
{guidance: true, description: '菜单和密集列表使用 density="compact",标准行使用 "balanced",宽松布局使用 "spacious"。'},
|
package/src/Kbd/Kbd.tsx
CHANGED
|
@@ -45,7 +45,7 @@ const styles = stylex.create({
|
|
|
45
45
|
backgroundColor: colorVars['--color-neutral'],
|
|
46
46
|
borderBottomWidth: '2px',
|
|
47
47
|
borderBottomStyle: 'solid',
|
|
48
|
-
borderBottomColor: colorVars['--color-border'],
|
|
48
|
+
borderBottomColor: colorVars['--color-border-emphasized'],
|
|
49
49
|
color: colorVars['--color-text-secondary'],
|
|
50
50
|
fontFamily: typographyVars['--font-family-body'],
|
|
51
51
|
fontSize: typeScaleVars['--text-supporting-size'],
|
|
@@ -146,14 +146,7 @@ export interface KbdProps extends BaseProps<HTMLSpanElement> {
|
|
|
146
146
|
* <Kbd keys="mod+k" />
|
|
147
147
|
* ```
|
|
148
148
|
*/
|
|
149
|
-
export function Kbd({
|
|
150
|
-
keys,
|
|
151
|
-
ref,
|
|
152
|
-
xstyle,
|
|
153
|
-
className,
|
|
154
|
-
style,
|
|
155
|
-
...rest
|
|
156
|
-
}: KbdProps) {
|
|
149
|
+
export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
|
|
157
150
|
const isMac = useSyncExternalStore(
|
|
158
151
|
subscribeToPlatformChanges,
|
|
159
152
|
detectMac,
|
|
@@ -78,7 +78,7 @@ export const docs = {
|
|
|
78
78
|
name: 'render',
|
|
79
79
|
type: '(children: ReactNode, props: ContextRenderProps | FixedRenderProps) => ReactNode',
|
|
80
80
|
description:
|
|
81
|
-
'Render function for the popover element. Pass placement/alignment in context mode or x/y in fixed mode.',
|
|
81
|
+
'Render function for the popover element. Pass placement/alignment in context mode or x/y in fixed mode. In context mode, pass `as: "span"` to render an inline-safe layer (e.g. inside a paragraph). The layer renders inline in the React tree — the Popover API promotes it to the top layer when shown, so it escapes ancestor clipping and stacking without a portal.',
|
|
82
82
|
},
|
|
83
83
|
],
|
|
84
84
|
usage: {
|
|
@@ -95,6 +95,11 @@ export const docs = {
|
|
|
95
95
|
description:
|
|
96
96
|
'Build on higher-level components like Popover, HoverCard, and Tooltip for common overlay patterns.',
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
guidance: true,
|
|
100
|
+
description:
|
|
101
|
+
'Rely on the Popover API top layer to escape ancestor clipping and stacking — render the layer inline (no portal) so it inherits the trigger\u2019s theme cascade and keeps a natural focus order. Use `as: "span"` when the layer must be valid inside inline contexts like a paragraph.',
|
|
102
|
+
},
|
|
98
103
|
{
|
|
99
104
|
guidance: false,
|
|
100
105
|
description:
|
|
@@ -125,7 +130,7 @@ export const docsDense = {
|
|
|
125
130
|
hide: 'hide layer.',
|
|
126
131
|
isOpen: 'whether layer is open.',
|
|
127
132
|
id: 'unique ARIA id.',
|
|
128
|
-
render: 'renders popover element; pass placement/alignment or x/y.',
|
|
133
|
+
render: 'renders popover element; pass placement/alignment or x/y. Context mode accepts `as: "span"` for inline-safe layers. Renders inline; the Popover API top layer escapes clipping/stacking without a portal.',
|
|
129
134
|
},
|
|
130
135
|
usage: {
|
|
131
136
|
description:
|
package/src/Layer/useLayer.tsx
CHANGED
|
@@ -79,6 +79,19 @@ export interface ContextRenderProps {
|
|
|
79
79
|
* Merged after StyleX and anchor positioning styles.
|
|
80
80
|
*/
|
|
81
81
|
style?: React.CSSProperties;
|
|
82
|
+
/**
|
|
83
|
+
* HTML tag to render the popover container as.
|
|
84
|
+
*
|
|
85
|
+
* Defaults to `'div'`. Pass `'span'` when the layer must render inline-safe
|
|
86
|
+
* markup — e.g. a `HoverCard` wrapping inline text inside a `<p>`. A `<span>`
|
|
87
|
+
* is phrasing content, so it stays put in the DOM tree instead of being
|
|
88
|
+
* reparented out of a paragraph by the HTML parser, which keeps server and
|
|
89
|
+
* client markup identical. The Popover API and CSS anchor positioning work
|
|
90
|
+
* the same on either tag.
|
|
91
|
+
*
|
|
92
|
+
* @default 'div'
|
|
93
|
+
*/
|
|
94
|
+
as?: 'div' | 'span';
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
/**
|
|
@@ -369,6 +382,7 @@ export function useLayer(
|
|
|
369
382
|
xstyle,
|
|
370
383
|
className: extraClassName,
|
|
371
384
|
style: extraStyle,
|
|
385
|
+
as: Container = 'div',
|
|
372
386
|
} = props || {};
|
|
373
387
|
|
|
374
388
|
// CSS anchor positioning (dynamic, not in StyleX)
|
|
@@ -383,15 +397,18 @@ export function useLayer(
|
|
|
383
397
|
? `${extraClassName} ${stylexResult.className ?? ''}`
|
|
384
398
|
: stylexResult.className;
|
|
385
399
|
|
|
400
|
+
// Render as the requested tag. A `span` keeps the layer phrasing content
|
|
401
|
+
// so it is valid (and stays put on hydration) inside inline contexts like
|
|
402
|
+
// a `<p>`; `div` remains the default for block layers.
|
|
386
403
|
return (
|
|
387
|
-
<
|
|
404
|
+
<Container
|
|
388
405
|
ref={popoverRefCallback}
|
|
389
406
|
id={id}
|
|
390
407
|
popover={lightDismiss ? 'auto' : 'manual'}
|
|
391
408
|
className={combinedClassName}
|
|
392
409
|
style={{...stylexResult.style, ...anchorStyle, ...extraStyle}}>
|
|
393
410
|
{children}
|
|
394
|
-
</
|
|
411
|
+
</Container>
|
|
395
412
|
);
|
|
396
413
|
},
|
|
397
414
|
[anchorId, id, lightDismiss, popoverRefCallback],
|
|
@@ -29,7 +29,8 @@ export const docs = {
|
|
|
29
29
|
{
|
|
30
30
|
name: 'content',
|
|
31
31
|
type: 'ReactNode',
|
|
32
|
-
description:
|
|
32
|
+
description:
|
|
33
|
+
'Main content area (center). Children passed to `<Layout>` render here too — `<Layout>{main}</Layout>` is shorthand for `<Layout content={main} />`.',
|
|
33
34
|
slotElements: [
|
|
34
35
|
{
|
|
35
36
|
__element: 'LayoutContent',
|
package/src/Layout/Layout.tsx
CHANGED
|
@@ -157,6 +157,16 @@ export interface LayoutProps extends Omit<BaseProps, 'content'> {
|
|
|
157
157
|
* Inline styles to apply to the root element.
|
|
158
158
|
*/
|
|
159
159
|
style?: React.CSSProperties;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Children are a shorthand for the `content` slot:
|
|
163
|
+
* `<Layout>{main}</Layout>` is equivalent to `<Layout content={main} />`.
|
|
164
|
+
* The surrounding zones (`header`/`start`/`end`/`footer`) stay explicit
|
|
165
|
+
* props. If both `content` and `children` are provided, `content` wins.
|
|
166
|
+
* Accepting children keeps the natural `<Layout>…</Layout>` form from
|
|
167
|
+
* rendering a blank shell.
|
|
168
|
+
*/
|
|
169
|
+
children?: ReactNode;
|
|
160
170
|
}
|
|
161
171
|
|
|
162
172
|
/**
|
|
@@ -222,6 +232,7 @@ function AreaProvider({
|
|
|
222
232
|
* ```
|
|
223
233
|
*/
|
|
224
234
|
export function Layout({
|
|
235
|
+
children,
|
|
225
236
|
content,
|
|
226
237
|
contentWidth,
|
|
227
238
|
defaultHasDividers,
|
|
@@ -237,6 +248,9 @@ export function Layout({
|
|
|
237
248
|
style,
|
|
238
249
|
}: LayoutProps) {
|
|
239
250
|
const isFill = height === 'fill';
|
|
251
|
+
// Children are a shorthand for the content slot; an explicit `content` prop
|
|
252
|
+
// wins when both are provided.
|
|
253
|
+
const resolvedContent = content ?? children;
|
|
240
254
|
|
|
241
255
|
const dividerCtxValue = useMemo(
|
|
242
256
|
() => (defaultHasDividers != null ? {defaultHasDividers} : null),
|
|
@@ -287,7 +301,7 @@ export function Layout({
|
|
|
287
301
|
)}>
|
|
288
302
|
<AreaProvider area="start">{start}</AreaProvider>
|
|
289
303
|
<div {...stylex.props(...stackItem({size: 'fill'}))}>
|
|
290
|
-
<AreaProvider area="content">{
|
|
304
|
+
<AreaProvider area="content">{resolvedContent}</AreaProvider>
|
|
291
305
|
</div>
|
|
292
306
|
<AreaProvider area="end">{end}</AreaProvider>
|
|
293
307
|
</div>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file childrenAsContent.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react
|
|
6
|
+
* @output Verifies Layout renders children as a shorthand for the content slot
|
|
7
|
+
* (so the natural `<Layout>…</Layout>` form never renders a blank shell), with
|
|
8
|
+
* an explicit `content` prop taking precedence.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {describe, it, expect} from 'vitest';
|
|
12
|
+
import {render, screen} from '@testing-library/react';
|
|
13
|
+
import {Layout} from '../Layout';
|
|
14
|
+
import {LayoutContent} from '../LayoutContent';
|
|
15
|
+
|
|
16
|
+
describe('Layout children-as-content', () => {
|
|
17
|
+
it('renders nested children in the content slot', () => {
|
|
18
|
+
render(
|
|
19
|
+
<Layout>
|
|
20
|
+
<LayoutContent>
|
|
21
|
+
<span data-testid="body">Body</span>
|
|
22
|
+
</LayoutContent>
|
|
23
|
+
</Layout>,
|
|
24
|
+
);
|
|
25
|
+
expect(screen.getByTestId('body')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders bare children (no LayoutContent wrapper) too', () => {
|
|
29
|
+
render(
|
|
30
|
+
<Layout>
|
|
31
|
+
<span data-testid="bare">Bare</span>
|
|
32
|
+
</Layout>,
|
|
33
|
+
);
|
|
34
|
+
expect(screen.getByTestId('bare')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('lets an explicit content prop win over children', () => {
|
|
38
|
+
render(
|
|
39
|
+
<Layout content={<span data-testid="slot">Slot</span>}>
|
|
40
|
+
<span data-testid="child">Child</span>
|
|
41
|
+
</Layout>,
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByTestId('slot')).toBeInTheDocument();
|
|
44
|
+
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('still supports the canonical slot-only API', () => {
|
|
48
|
+
render(
|
|
49
|
+
<Layout
|
|
50
|
+
content={
|
|
51
|
+
<LayoutContent>
|
|
52
|
+
<span data-testid="canon">Canonical</span>
|
|
53
|
+
</LayoutContent>
|
|
54
|
+
}
|
|
55
|
+
/>,
|
|
56
|
+
);
|
|
57
|
+
expect(screen.getByTestId('canon')).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -30,7 +30,6 @@ export const docs = {
|
|
|
30
30
|
name: 'index',
|
|
31
31
|
type: 'number',
|
|
32
32
|
description: 'Current index in gallery mode (when media is an array).',
|
|
33
|
-
default: '0',
|
|
34
33
|
},
|
|
35
34
|
{
|
|
36
35
|
name: 'onIndexChange',
|
|
@@ -94,7 +93,6 @@ export const docsZh = {
|
|
|
94
93
|
name: 'index',
|
|
95
94
|
type: 'number',
|
|
96
95
|
description: '画廊模式中当前索引。',
|
|
97
|
-
default: '0',
|
|
98
96
|
},
|
|
99
97
|
{
|
|
100
98
|
name: 'onIndexChange',
|
package/src/Link/Link.doc.mjs
CHANGED
|
@@ -99,7 +99,7 @@ export const docs = {
|
|
|
99
99
|
isHiddenFromOverview: true,
|
|
100
100
|
displayName: 'Link Provider',
|
|
101
101
|
description:
|
|
102
|
-
'Provider that sets the default link component for all
|
|
102
|
+
'Provider that sets the default link component for all Astryx link-rendering components in the subtree. ' +
|
|
103
103
|
'Wrap your app root to replace native <a> elements with your framework router (Next.js Link, React Router Link, etc.).',
|
|
104
104
|
props: [
|
|
105
105
|
{
|
|
@@ -226,7 +226,7 @@ export const docsZh = {
|
|
|
226
226
|
isHiddenFromOverview: true,
|
|
227
227
|
displayName: 'Link Provider',
|
|
228
228
|
description:
|
|
229
|
-
'为子树中所有
|
|
229
|
+
'为子树中所有 Astryx 链接组件设置默认链接组件的 Provider。',
|
|
230
230
|
props: [
|
|
231
231
|
{
|
|
232
232
|
name: 'component',
|
|
@@ -310,7 +310,7 @@ export const docsDense = {
|
|
|
310
310
|
isHiddenFromOverview: true,
|
|
311
311
|
displayName: 'Link Provider',
|
|
312
312
|
description:
|
|
313
|
-
'Provider setting default link component for all
|
|
313
|
+
'Provider setting default link component for all Astryx links in subtree.',
|
|
314
314
|
propDescriptions: {
|
|
315
315
|
component: 'Component for all link elements',
|
|
316
316
|
children: 'Subtree',
|
|
@@ -10,7 +10,7 @@ export const docs = {
|
|
|
10
10
|
isHiddenFromOverview: true,
|
|
11
11
|
keywords: ['link', 'provider', 'router', 'nextjs', 'client-side-routing'],
|
|
12
12
|
usage: {
|
|
13
|
-
description: 'Wraps your app to replace the default <a> tag with a framework-specific link component (e.g. Next.js Link) for client-side routing across all
|
|
13
|
+
description: 'Wraps your app to replace the default <a> tag with a framework-specific link component (e.g. Next.js Link) for client-side routing across all Astryx components.',
|
|
14
14
|
},
|
|
15
15
|
props: [
|
|
16
16
|
{name: 'component', type: 'LinkComponentType', required: true, description: 'Link component to use for all link elements in the subtree (e.g. Next.js Link).'},
|
|
@@ -20,9 +20,9 @@ export const docs = {
|
|
|
20
20
|
|
|
21
21
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
22
22
|
export const docsDense = {
|
|
23
|
-
description: 'Wraps app to replace default <a> tag w/ framework-specific link component (e.g. Next.js Link) for client-side routing across all
|
|
23
|
+
description: 'Wraps app to replace default <a> tag w/ framework-specific link component (e.g. Next.js Link) for client-side routing across all Astryx components.',
|
|
24
24
|
usage: {
|
|
25
|
-
description: 'Wraps app to replace default <a> tag w/ framework-specific link component (e.g. Next.js Link) for client-side routing across all
|
|
25
|
+
description: 'Wraps app to replace default <a> tag w/ framework-specific link component (e.g. Next.js Link) for client-side routing across all Astryx components.',
|
|
26
26
|
},
|
|
27
27
|
propDescriptions: {
|
|
28
28
|
component: 'link component for all link elements in subtree (e.g. Next.js Link)',
|
|
@@ -75,6 +75,7 @@ export const docs = {
|
|
|
75
75
|
type: 'number | string',
|
|
76
76
|
description:
|
|
77
77
|
'Max width for prose content (paragraphs, headings, lists, blockquotes). Tables and code blocks are unconstrained and can expand to the full container width. Use for readable line lengths in wide layouts.',
|
|
78
|
+
default: '680',
|
|
78
79
|
},
|
|
79
80
|
{
|
|
80
81
|
name: 'contentAlign',
|
|
@@ -131,7 +132,7 @@ export const docs = {
|
|
|
131
132
|
},
|
|
132
133
|
usage: {
|
|
133
134
|
description:
|
|
134
|
-
'Renders a markdown string as
|
|
135
|
+
'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
|
|
135
136
|
bestPractices: [
|
|
136
137
|
{ guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
|
|
137
138
|
{ guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
|
|
@@ -245,6 +246,7 @@ export const docsZh = {
|
|
|
245
246
|
type: 'number | string',
|
|
246
247
|
description:
|
|
247
248
|
'正文内容的最大宽度(段落、标题、列表、引用块)。表格和代码块不受限制,可扩展到完整容器宽度。用于在宽布局中保持可读行长。',
|
|
249
|
+
default: '680',
|
|
248
250
|
},
|
|
249
251
|
{
|
|
250
252
|
name: 'contentAlign',
|
|
@@ -294,7 +296,7 @@ export const docsZh = {
|
|
|
294
296
|
},
|
|
295
297
|
usage: {
|
|
296
298
|
description:
|
|
297
|
-
'Renders a markdown string as
|
|
299
|
+
'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
|
|
298
300
|
bestPractices: [
|
|
299
301
|
{ guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
|
|
300
302
|
{ guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
|
|
@@ -306,10 +308,10 @@ export const docsZh = {
|
|
|
306
308
|
|
|
307
309
|
export const docsDense = {
|
|
308
310
|
description:
|
|
309
|
-
'Renders markdown string as
|
|
311
|
+
'Renders markdown string as Astryx-styled components. Use for user-generated content, AI responses, docs. Headings, lists, tables, code, citations w/ consistent styling.',
|
|
310
312
|
usage: {
|
|
311
313
|
description:
|
|
312
|
-
'Renders a markdown string as
|
|
314
|
+
'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
|
|
313
315
|
bestPractices: [
|
|
314
316
|
{ guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
|
|
315
317
|
{ guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
|
|
@@ -22,9 +22,16 @@ describe('Markdown', () => {
|
|
|
22
22
|
expect(screen.getByText('Heading 2').tagName).toBe('H2');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('renders paragraphs', () => {
|
|
25
|
+
it('renders paragraphs as block <div> (never <p>) for composition safety', () => {
|
|
26
26
|
render(<Markdown>{'Hello world'}</Markdown>);
|
|
27
|
-
|
|
27
|
+
// Markdown paragraphs render as <div> so block-level inline content
|
|
28
|
+
// (images, custom inline components) never trips the phrasing-content
|
|
29
|
+
// trap that a <p> would impose. role="paragraph" re-exposes the paragraph
|
|
30
|
+
// role to assistive tech without the <p> hazard. Consumers who want a real
|
|
31
|
+
// <p> element can pass `components={{paragraph: 'p'}}`.
|
|
32
|
+
const para = screen.getByText('Hello world');
|
|
33
|
+
expect(para.tagName).toBe('DIV');
|
|
34
|
+
expect(para).toHaveAttribute('role', 'paragraph');
|
|
28
35
|
});
|
|
29
36
|
|
|
30
37
|
it('renders inline display without block wrappers', () => {
|
|
@@ -201,9 +208,7 @@ describe('Markdown', () => {
|
|
|
201
208
|
});
|
|
202
209
|
|
|
203
210
|
it('shows streaming cursor when isStreaming is true', () => {
|
|
204
|
-
const {container} = render(
|
|
205
|
-
<Markdown isStreaming>{'Hello'}</Markdown>,
|
|
206
|
-
);
|
|
211
|
+
const {container} = render(<Markdown isStreaming>{'Hello'}</Markdown>);
|
|
207
212
|
// Streaming mode parses incrementally but no cursor element
|
|
208
213
|
expect(container.querySelector('[role="document"]')).toBeInTheDocument();
|
|
209
214
|
});
|
|
@@ -243,9 +248,7 @@ describe('Markdown', () => {
|
|
|
243
248
|
|
|
244
249
|
it('sanitizes data: URLs in images', () => {
|
|
245
250
|
const {container} = render(
|
|
246
|
-
<Markdown>
|
|
247
|
-
{'</script>)'}
|
|
248
|
-
</Markdown>,
|
|
251
|
+
<Markdown>{'</script>)'}</Markdown>,
|
|
249
252
|
);
|
|
250
253
|
const img = container.querySelector('img');
|
|
251
254
|
expect(img).toBeNull();
|
|
@@ -435,9 +438,7 @@ describe('inlinePlugins', () => {
|
|
|
435
438
|
},
|
|
436
439
|
};
|
|
437
440
|
const {container} = render(
|
|
438
|
-
<Markdown inlinePlugins={[plugin]}>
|
|
439
|
-
{'See TAG:important here'}
|
|
440
|
-
</Markdown>,
|
|
441
|
+
<Markdown inlinePlugins={[plugin]}>{'See TAG:important here'}</Markdown>,
|
|
441
442
|
);
|
|
442
443
|
const tag = container.querySelector('[data-testid="tag-match"]');
|
|
443
444
|
expect(tag).toBeInTheDocument();
|
|
@@ -446,9 +447,7 @@ describe('inlinePlugins', () => {
|
|
|
446
447
|
|
|
447
448
|
it('renders identically when no inlinePlugins are provided', () => {
|
|
448
449
|
const withPlugins = render(
|
|
449
|
-
<Markdown inlinePlugins={[]}>
|
|
450
|
-
{'Hello **world** and `code`'}
|
|
451
|
-
</Markdown>,
|
|
450
|
+
<Markdown inlinePlugins={[]}>{'Hello **world** and `code`'}</Markdown>,
|
|
452
451
|
);
|
|
453
452
|
const withoutPlugins = render(
|
|
454
453
|
<Markdown>{'Hello **world** and `code`'}</Markdown>,
|
|
@@ -481,9 +480,7 @@ describe('inlinePlugins', () => {
|
|
|
481
480
|
|
|
482
481
|
it('renders bare https URLs as links when autolink="gfm"', () => {
|
|
483
482
|
const {container} = render(
|
|
484
|
-
<Markdown autolink="gfm">
|
|
485
|
-
{'see https://example.com here'}
|
|
486
|
-
</Markdown>,
|
|
483
|
+
<Markdown autolink="gfm">{'see https://example.com here'}</Markdown>,
|
|
487
484
|
);
|
|
488
485
|
const link = container.querySelector('a');
|
|
489
486
|
expect(link).not.toBeNull();
|
|
@@ -503,9 +500,7 @@ describe('inlinePlugins', () => {
|
|
|
503
500
|
|
|
504
501
|
it('renders bare emails with mailto: href', () => {
|
|
505
502
|
const {container} = render(
|
|
506
|
-
<Markdown autolink="gfm">
|
|
507
|
-
{'ping user@example.com please'}
|
|
508
|
-
</Markdown>,
|
|
503
|
+
<Markdown autolink="gfm">{'ping user@example.com please'}</Markdown>,
|
|
509
504
|
);
|
|
510
505
|
const link = container.querySelector('a');
|
|
511
506
|
expect(link).not.toBeNull();
|
|
@@ -515,9 +510,7 @@ describe('inlinePlugins', () => {
|
|
|
515
510
|
|
|
516
511
|
it('does not autolink URLs inside code spans', () => {
|
|
517
512
|
const {container} = render(
|
|
518
|
-
<Markdown autolink="gfm">
|
|
519
|
-
{'try `https://example.com` here'}
|
|
520
|
-
</Markdown>,
|
|
513
|
+
<Markdown autolink="gfm">{'try `https://example.com` here'}</Markdown>,
|
|
521
514
|
);
|
|
522
515
|
expect(container.querySelector('a')).toBeNull();
|
|
523
516
|
expect(container.querySelector('code')).not.toBeNull();
|
|
@@ -525,9 +518,7 @@ describe('inlinePlugins', () => {
|
|
|
525
518
|
|
|
526
519
|
it('does not autolink URLs inside code blocks', () => {
|
|
527
520
|
const {container} = render(
|
|
528
|
-
<Markdown autolink="gfm">
|
|
529
|
-
{'```\nhttps://example.com\n```'}
|
|
530
|
-
</Markdown>,
|
|
521
|
+
<Markdown autolink="gfm">{'```\nhttps://example.com\n```'}</Markdown>,
|
|
531
522
|
);
|
|
532
523
|
expect(container.querySelector('a')).toBeNull();
|
|
533
524
|
expect(container.querySelector('pre')).not.toBeNull();
|
|
@@ -1113,9 +1113,19 @@ function renderBlock(
|
|
|
1113
1113
|
if (ParagraphComp) {
|
|
1114
1114
|
return <ParagraphComp key={index}>{paraChildren}</ParagraphComp>;
|
|
1115
1115
|
}
|
|
1116
|
+
// Markdown paragraphs render as <div>, not <p>: inline content can
|
|
1117
|
+
// include block-level nodes (images, custom inline components), and a
|
|
1118
|
+
// <p> would reparent them, desyncing SSR markup from the hydrated DOM.
|
|
1119
|
+
// Block spacing comes from token-based StyleX margins, so the rendered
|
|
1120
|
+
// appearance is unchanged. role="paragraph" re-exposes the paragraph
|
|
1121
|
+
// role in the accessibility tree (a pure ARIA hint — it does not trigger
|
|
1122
|
+
// the parser's block-child reparenting) so prose semantics are preserved
|
|
1123
|
+
// without the <p> composition hazard. Consumers who want a real <p>
|
|
1124
|
+
// element can still pass components={{paragraph: 'p'}}.
|
|
1116
1125
|
return (
|
|
1117
|
-
<
|
|
1126
|
+
<div
|
|
1118
1127
|
key={index}
|
|
1128
|
+
role="paragraph"
|
|
1119
1129
|
{...stylex.props(
|
|
1120
1130
|
spacing,
|
|
1121
1131
|
contentWidthValue != null
|
|
@@ -1128,7 +1138,7 @@ function renderBlock(
|
|
|
1128
1138
|
isLast && styles.noMarginBlockEnd,
|
|
1129
1139
|
)}>
|
|
1130
1140
|
{paraChildren}
|
|
1131
|
-
</
|
|
1141
|
+
</div>
|
|
1132
1142
|
);
|
|
1133
1143
|
}
|
|
1134
1144
|
case 'codeblock': {
|
|
@@ -1487,7 +1497,7 @@ function renderBlock(
|
|
|
1487
1497
|
const safeSrc = sanitizeUrl(node.src);
|
|
1488
1498
|
if (safeSrc == null) {
|
|
1489
1499
|
return (
|
|
1490
|
-
<
|
|
1500
|
+
<div
|
|
1491
1501
|
key={index}
|
|
1492
1502
|
{...stylex.props(
|
|
1493
1503
|
spacing,
|
|
@@ -1495,11 +1505,11 @@ function renderBlock(
|
|
|
1495
1505
|
isLast && styles.noMarginBlockEnd,
|
|
1496
1506
|
)}>
|
|
1497
1507
|
[{node.alt}]
|
|
1498
|
-
</
|
|
1508
|
+
</div>
|
|
1499
1509
|
);
|
|
1500
1510
|
}
|
|
1501
1511
|
return (
|
|
1502
|
-
<
|
|
1512
|
+
<div
|
|
1503
1513
|
key={index}
|
|
1504
1514
|
{...stylex.props(
|
|
1505
1515
|
spacing,
|
|
@@ -1507,7 +1517,7 @@ function renderBlock(
|
|
|
1507
1517
|
isLast && styles.noMarginBlockEnd,
|
|
1508
1518
|
)}>
|
|
1509
1519
|
<img src={safeSrc} alt={node.alt} {...stylex.props(styles.image)} />
|
|
1510
|
-
</
|
|
1520
|
+
</div>
|
|
1511
1521
|
);
|
|
1512
1522
|
}
|
|
1513
1523
|
}
|
|
@@ -43,14 +43,14 @@ export const docs = {
|
|
|
43
43
|
type: 'number',
|
|
44
44
|
description:
|
|
45
45
|
'Drawer width in pixels. Capped at 85vw to prevent overflow on small screens.',
|
|
46
|
-
default: '
|
|
46
|
+
default: '320',
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
name: 'side',
|
|
50
|
-
type: "'start' | 'end'",
|
|
50
|
+
type: "'start' | 'end' | 'auto'",
|
|
51
51
|
description:
|
|
52
|
-
'Which side the drawer slides from. Start is left in LTR, right in RTL.',
|
|
53
|
-
default: "'
|
|
52
|
+
'Which side the drawer slides from. Start is left in LTR, right in RTL. Auto picks a side based on the trigger position.',
|
|
53
|
+
default: "'auto'",
|
|
54
54
|
},
|
|
55
55
|
],
|
|
56
56
|
},
|
|
@@ -124,14 +124,14 @@ export const docsZh = {
|
|
|
124
124
|
type: 'number',
|
|
125
125
|
description:
|
|
126
126
|
'抽屉宽度(像素)。上限为 85vw 以防止在小屏幕上溢出。',
|
|
127
|
-
default: '
|
|
127
|
+
default: '320',
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
name: 'side',
|
|
131
|
-
type: "'start' | 'end'",
|
|
131
|
+
type: "'start' | 'end' | 'auto'",
|
|
132
132
|
description:
|
|
133
|
-
'抽屉滑出的方向。在 LTR 布局中 start 为左侧,在 RTL 布局中为右侧。',
|
|
134
|
-
default: "'
|
|
133
|
+
'抽屉滑出的方向。在 LTR 布局中 start 为左侧,在 RTL 布局中为右侧。auto 根据触发元素的位置自动选择方向。',
|
|
134
|
+
default: "'auto'",
|
|
135
135
|
},
|
|
136
136
|
],
|
|
137
137
|
theming: {
|
|
@@ -363,8 +363,21 @@ export function MobileNav({
|
|
|
363
363
|
return () => {
|
|
364
364
|
if (closeTimeoutRef.current) {
|
|
365
365
|
clearTimeout(closeTimeoutRef.current);
|
|
366
|
+
closeTimeoutRef.current = null;
|
|
366
367
|
}
|
|
367
368
|
document.documentElement.style.overflow = '';
|
|
369
|
+
// Close the native dialog on teardown if it's still open. Inside AppShell
|
|
370
|
+
// the drawer is mounted in an <Activity> that switches to mode="hidden"
|
|
371
|
+
// when the drawer closes; React then runs this cleanup (with a stale
|
|
372
|
+
// isOpen) instead of re-running the effect with isOpen=false, so the
|
|
373
|
+
// close branch above never fires. If we leave the <dialog> `open` here,
|
|
374
|
+
// showModal() is skipped on the next open (the dialog is already open in
|
|
375
|
+
// the hidden tree) and the drawer can never be re-opened. Closing it
|
|
376
|
+
// unconditionally on teardown keeps the native dialog state in sync so a
|
|
377
|
+
// subsequent open cleanly calls showModal() again.
|
|
378
|
+
if (dialog.open) {
|
|
379
|
+
dialog.close();
|
|
380
|
+
}
|
|
368
381
|
};
|
|
369
382
|
}, [isOpen, side]);
|
|
370
383
|
|