@astryxdesign/core 0.7.2-canary.86e8392 → 0.7.2-canary.9da2d0b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/Drawer/Drawer.d.ts.map +1 -1
  2. package/dist/Drawer/Drawer.js +4 -4
  3. package/dist/Drawer/DrawerHeader.d.ts +2 -1
  4. package/dist/Drawer/DrawerHeader.d.ts.map +1 -1
  5. package/dist/Drawer/DrawerHeader.js +8 -7
  6. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  7. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  8. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  9. package/dist/TabList/TabMenu.d.ts +2 -2
  10. package/dist/TabList/TabMenu.d.ts.map +1 -1
  11. package/dist/TabList/TabMenu.js +6 -3
  12. package/dist/astryx.css +4 -0
  13. package/package.json +3 -3
  14. package/src/Drawer/Drawer.spec.md +8 -6
  15. package/src/Drawer/Drawer.tsx +7 -6
  16. package/src/Drawer/DrawerDirection.test.tsx +127 -0
  17. package/src/Drawer/DrawerHeader.test.tsx +13 -0
  18. package/src/Drawer/DrawerHeader.tsx +8 -8
  19. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
  20. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
  21. package/src/DropdownMenu/DropdownMenu.test.tsx +21 -12
  22. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  23. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  24. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
  25. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
  26. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
  27. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
  28. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
  29. package/src/Item/Item.spec.md +226 -0
  30. package/src/TabList/TabList.test.tsx +67 -0
  31. package/src/TabList/TabMenu.doc.mjs +4 -3
  32. package/src/TabList/TabMenu.tsx +7 -4
  33. package/src/TreeList/TreeList.spec.md +29 -17
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/Drawer/Drawer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,EAAgC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAe7C,OAAO,KAAK,EAAC,WAAW,EAAe,MAAM,WAAW,CAAC;AA+LzD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,iDAAiD;IACjD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAEvB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACb;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;KAC/B,CAAC;IAEF;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,YAAY,EACZ,IAAY,EACZ,KAAW,EACX,YAAY,EACZ,KAAK,EACL,QAAe,EACf,OAAgB,EAChB,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BA4Kb;yBA9Le,MAAM"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/Drawer/Drawer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,EAAgC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAe7C,OAAO,KAAK,EAAC,WAAW,EAAe,MAAM,WAAW,CAAC;AAgMzD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,iDAAiD;IACjD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAEvB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACb;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;KAC/B,CAAC;IAEF;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,YAAY,EACZ,IAAY,EACZ,KAAW,EACX,YAAY,EACZ,KAAK,EACL,QAAe,EACf,OAAgB,EAChB,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BA4Kb;yBA9Le,MAAM"}
@@ -123,11 +123,11 @@ const styles = {
123
123
  k2ei4v: "xgbv0en",
124
124
  kVhnKS: "x1t7ytsu",
125
125
  kGJrpR: "x1j92z86",
126
- k3aq6I: "xumwmo6 x1df3fe5",
126
+ k3aq6I: "xumwmo6 xbw113y",
127
127
  $$css: true
128
128
  },
129
129
  endOpen: {
130
- k3aq6I: "xbryuvx x1yqmsfc x1lymnkk",
130
+ k3aq6I: "xbryuvx x1yqmsfc xrxqmjf",
131
131
  $$css: true
132
132
  },
133
133
  start: {
@@ -136,11 +136,11 @@ const styles = {
136
136
  ke9TFa: "xw8tdv1",
137
137
  k8ry5P: "x18b5jzi",
138
138
  kBCPoo: "x1gejf6u",
139
- k3aq6I: "x5i6ehr xttggg",
139
+ k3aq6I: "x5i6ehr x1eqdj8u",
140
140
  $$css: true
141
141
  },
142
142
  startOpen: {
143
- k3aq6I: "xbryuvx x6mt36l x14gflnl",
143
+ k3aq6I: "xbryuvx x6mt36l x1r0dqvm",
144
144
  $$css: true
145
145
  },
146
146
  scrim: {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file DrawerHeader.tsx
3
- * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, mergeProps, themeProps
3
+ * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, isRenderable, mergeProps, themeProps
4
4
  * @output Exports DrawerHeader component and DrawerHeaderProps
5
5
  * @position Drawer header; composed in a Layout header slot inside Drawer, tested by DrawerHeader.test.tsx
6
6
  *
@@ -18,6 +18,7 @@
18
18
  * - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
19
19
  * - /packages/cli/assets/templates/blocks/components/DrawerHeader/ (showcase blocks)
20
20
  * - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
21
+ * - /apps/storybook/stories/DrawerHeader.stories.tsx (component audit fixture)
21
22
  */
22
23
  import type { ReactNode } from 'react';
23
24
  import type { BaseProps } from '../BaseProps';
@@ -1 +1 @@
1
- {"version":3,"file":"DrawerHeader.d.ts","sourceRoot":"","sources":["../../src/Drawer/DrawerHeader.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0C5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAClE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE5C;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IAExD;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,iBAAiB,+BAqFnB;yBAlGe,YAAY"}
1
+ {"version":3,"file":"DrawerHeader.d.ts","sourceRoot":"","sources":["../../src/Drawer/DrawerHeader.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0C5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAClE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE5C;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IAExD;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,iBAAiB,+BAoFnB;yBAjGe,YAAY"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DrawerHeader.tsx
7
- * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, mergeProps, themeProps
7
+ * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, isRenderable, mergeProps, themeProps
8
8
  * @output Exports DrawerHeader component and DrawerHeaderProps
9
9
  * @position Drawer header; composed in a Layout header slot inside Drawer, tested by DrawerHeader.test.tsx
10
10
  *
@@ -22,6 +22,7 @@
22
22
  * - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
23
23
  * - /packages/cli/assets/templates/blocks/components/DrawerHeader/ (showcase blocks)
24
24
  * - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
25
+ * - /apps/storybook/stories/DrawerHeader.stories.tsx (component audit fixture)
25
26
  */
26
27
  import * as stylex from '@stylexjs/stylex';
27
28
  import "../theme/tokens.stylex.js";
@@ -32,7 +33,7 @@ import { Icon } from "../Icon/index.js";
32
33
  import { LayoutHeader } from "../Layout/index.js";
33
34
  import { Text } from "../Text/index.js";
34
35
  import { useTranslator } from "../i18n/index.js";
35
- import { mergeProps, themeProps } from "../utils/index.js";
36
+ import { isRenderable, mergeProps, themeProps } from "../utils/index.js";
36
37
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
37
38
  /**
38
39
  * Header for a Drawer: a title, optional subtitle and start/end content, and a
@@ -63,9 +64,9 @@ export function DrawerHeader({
63
64
  ...rest
64
65
  }) {
65
66
  const t = useTranslator();
66
- // A node subtitle may be `0`: render it inside Text instead of letting a
67
- // truthiness check leak a bare text node, and skip only empty values.
68
- const hasSubtitle = subtitle != null && typeof subtitle !== 'boolean' && subtitle !== '';
67
+ const hasSubtitle = isRenderable(subtitle);
68
+ const hasStartContent = isRenderable(startContent);
69
+ const hasEndContent = isRenderable(endContent);
69
70
  const shouldCompensateEndBlock = endContentEdgeCompensation == null ? onOpenChange != null : endContentEdgeCompensation === 'block' || endContentEdgeCompensation === 'all';
70
71
  const shouldCompensateEndInline = endContentEdgeCompensation == null ? onOpenChange != null : endContentEdgeCompensation === 'inline' || endContentEdgeCompensation === 'all';
71
72
  return /*#__PURE__*/_jsx(LayoutHeader, {
@@ -79,7 +80,7 @@ export function DrawerHeader({
79
80
  ...mergeProps(themeProps('drawer-header'), {
80
81
  className: "x78zum5 x1cy8zhl x1qughib xjcht0a"
81
82
  }),
82
- children: [startContent && /*#__PURE__*/_jsx("div", {
83
+ children: [hasStartContent && /*#__PURE__*/_jsx("div", {
83
84
  ...mergeProps(themeProps('drawer-header-start-content'), {
84
85
  className: "x78zum5 x6s0dn4 x1txdalj x2lah0s"
85
86
  }),
@@ -97,7 +98,7 @@ export function DrawerHeader({
97
98
  color: "secondary",
98
99
  children: subtitle
99
100
  })]
100
- }), (endContent || onOpenChange) && /*#__PURE__*/_jsxs("div", {
101
+ }), (hasEndContent || onOpenChange != null) && /*#__PURE__*/_jsxs("div", {
101
102
  ...mergeProps(themeProps('drawer-header-end-content'), {
102
103
  0: {
103
104
  className: "x78zum5 x6s0dn4 x1txdalj x2lah0s"
@@ -1,5 +1,5 @@
1
1
  import type { BaseProps } from '../BaseProps';
2
- export interface DropdownMenuDividerProps extends Pick<BaseProps, 'xstyle' | 'className' | 'style'> {
2
+ export interface DropdownMenuDividerProps extends Omit<BaseProps<HTMLDivElement>, 'role' | 'aria-orientation'> {
3
3
  /** Ref forwarded to the separator element. */
4
4
  ref?: React.Ref<HTMLDivElement>;
5
5
  }
@@ -7,7 +7,9 @@ export interface DropdownMenuDividerProps extends Pick<BaseProps, 'xstyle' | 'cl
7
7
  * A horizontal rule separating groups of menu rows.
8
8
  *
9
9
  * Renders `role="separator"`, so it is never a stop in the menu's arrow-key
10
- * order. Equivalent to `{type: 'divider'}` in the `items` data API.
10
+ * order. Equivalent to `{type: 'divider'}` in the `items` data API. Host
11
+ * attributes (`id`, `data-*`, `aria-*`) and DOM handlers reach the separator;
12
+ * its role and orientation stay component-owned.
11
13
  *
12
14
  * @example
13
15
  * ```
@@ -18,7 +20,7 @@ export interface DropdownMenuDividerProps extends Pick<BaseProps, 'xstyle' | 'cl
18
20
  * </DropdownMenu>
19
21
  * ```
20
22
  */
21
- export declare function DropdownMenuDivider({ xstyle, className, style, ref, }: DropdownMenuDividerProps): import("react").JSX.Element;
23
+ export declare function DropdownMenuDivider({ xstyle, className, style, ref, ...rest }: DropdownMenuDividerProps): import("react").JSX.Element;
22
24
  export declare namespace DropdownMenuDivider {
23
25
  var displayName: string;
24
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuDivider.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuDivider.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC,8CAA8C;IAC9C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE,wBAAwB,+BAW1B;yBAhBe,mBAAmB"}
1
+ {"version":3,"file":"DropdownMenuDivider.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuDivider.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,CAAC,cAAc,CAAC,EACzB,MAAM,GAAG,kBAAkB,CAC5B;IACC,8CAA8C;IAC9C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,wBAAwB,+BAY1B;yBAlBe,mBAAmB"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DropdownMenuDivider.tsx
7
- * @input Uses Divider, spacing tokens, themeProps
7
+ * @input Uses Divider, spacing tokens, themeProps; forwards host props to Divider
8
8
  * @output Exports DropdownMenuDivider component and DropdownMenuDividerProps
9
9
  * @position Sub-component; used inside DropdownMenu
10
10
  *
@@ -39,7 +39,9 @@ const THEME_CLASS_NAME = themeProps('dropdown-menu-divider').className;
39
39
  * A horizontal rule separating groups of menu rows.
40
40
  *
41
41
  * Renders `role="separator"`, so it is never a stop in the menu's arrow-key
42
- * order. Equivalent to `{type: 'divider'}` in the `items` data API.
42
+ * order. Equivalent to `{type: 'divider'}` in the `items` data API. Host
43
+ * attributes (`id`, `data-*`, `aria-*`) and DOM handlers reach the separator;
44
+ * its role and orientation stay component-owned.
43
45
  *
44
46
  * @example
45
47
  * ```
@@ -54,9 +56,11 @@ export function DropdownMenuDivider({
54
56
  xstyle,
55
57
  className,
56
58
  style,
57
- ref
59
+ ref,
60
+ ...rest
58
61
  }) {
59
62
  return /*#__PURE__*/_jsx(Divider, {
63
+ ...rest,
60
64
  ref: ref,
61
65
  xstyle: [styles.divider, xstyle],
62
66
  className: className ? `${THEME_CLASS_NAME} ${className}` : THEME_CLASS_NAME,
@@ -22,7 +22,7 @@ export interface TabMenuOption {
22
22
  */
23
23
  icon?: ReactNode | IconType;
24
24
  }
25
- export interface TabMenuProps extends Pick<BaseProps<HTMLButtonElement>, 'xstyle' | 'className' | 'style'> {
25
+ export interface TabMenuProps extends Omit<BaseProps<HTMLButtonElement>, 'role' | 'tabIndex' | 'aria-haspopup' | 'aria-expanded' | 'aria-controls'> {
26
26
  ref?: React.Ref<HTMLButtonElement>;
27
27
  /**
28
28
  * Label for the trigger button and dropdown heading.
@@ -50,7 +50,7 @@ export interface TabMenuProps extends Pick<BaseProps<HTMLButtonElement>, 'xstyle
50
50
  * </TabList>
51
51
  * ```
52
52
  */
53
- export declare function TabMenu({ ref, label, options, xstyle, className, style, }: TabMenuProps): React.JSX.Element;
53
+ export declare function TabMenu({ ref, label, options, xstyle, className, style, onClick, ...rest }: TabMenuProps): React.JSX.Element;
54
54
  export declare namespace TabMenu {
55
55
  var displayName: string;
56
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TabMenu.d.ts","sourceRoot":"","sources":["../../src/TabList/TabMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAA6B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAGxE,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAoBtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,SAAS,CAAC,iBAAiB,CAAC,EAC5B,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AA+KD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,EACtB,GAAG,EACH,KAAK,EACL,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,GACN,EAAE,YAAY,qBA4Ld;yBAnMe,OAAO"}
1
+ {"version":3,"file":"TabMenu.d.ts","sourceRoot":"","sources":["../../src/TabList/TabMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAA6B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAGxE,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAoBtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,SAAS,CAAC,iBAAiB,CAAC,EAC5B,MAAM,GAAG,UAAU,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAC1E;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AA+KD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,EACtB,GAAG,EACH,KAAK,EACL,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EACP,GAAG,IAAI,EACR,EAAE,YAAY,qBA6Ld;yBAtMe,OAAO"}
@@ -28,7 +28,7 @@ import { useListFocus } from "../hooks/useListFocus.js";
28
28
  import { useTabListContext } from "./TabListContext.js";
29
29
  import "./tab.markers.stylex.js";
30
30
  import { tabScope } from "./tab.markers.stylex.js";
31
- import { mergeProps } from "../utils/index.js";
31
+ import { composeEventHandlers, mergeProps } from "../utils/index.js";
32
32
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
33
33
  import { themeProps } from "../utils/themeProps.js";
34
34
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -166,7 +166,9 @@ export function TabMenu({
166
166
  options,
167
167
  xstyle,
168
168
  className,
169
- style
169
+ style,
170
+ onClick,
171
+ ...rest
170
172
  }) {
171
173
  const tabListCtx = useTabListContext();
172
174
  const menuId = useId();
@@ -231,6 +233,7 @@ export function TabMenu({
231
233
  const setButtonRef = useMergedRefs(popover.triggerRef, buttonRef, ref);
232
234
  return /*#__PURE__*/_jsxs(_Fragment, {
233
235
  children: [/*#__PURE__*/_jsxs("button", {
236
+ ...rest,
234
237
  ref: setButtonRef,
235
238
  type: "button",
236
239
  "aria-haspopup": "menu",
@@ -238,7 +241,7 @@ export function TabMenu({
238
241
  "aria-controls": menuId,
239
242
  "data-tab-menu": "",
240
243
  tabIndex: hasSelectedOption ? 0 : -1,
241
- onClick: handleToggle,
244
+ onClick: composeEventHandlers(handleToggle, onClick),
242
245
  ...mergeProps(themeProps('tab-menu'), focusOutlineProps.focusVisible(styles.trigger, sizeStyles[size], hasSelectedOption && styles.triggerSelected, tabScope, xstyle), className, style),
243
246
  children: [/*#__PURE__*/_jsx("span", {
244
247
  "aria-hidden": "true",
package/dist/astryx.css CHANGED
@@ -1535,6 +1535,8 @@
1535
1535
  .xxdn8bs:has(> div > .astryx-layout-content):not(#\#):not(#\#):not(#\#){padding-inline-end:max(0px,calc((100% - var(--layout-content-width)) / 2))}
1536
1536
  .xmnmr5y:has(.astryx-nav-icon):not(#\#):not(#\#):not(#\#){padding-inline-start:0}
1537
1537
  .x1vvd0s7:has(> div > .astryx-layout-content):not(#\#):not(#\#):not(#\#){padding-inline-start:max(0px,calc((100% - var(--layout-content-width)) / 2))}
1538
+ .xbw113y:dir(rtl):not(#\#):not(#\#):not(#\#){transform:translateX(-100%)}
1539
+ .x1eqdj8u:dir(rtl):not(#\#):not(#\#):not(#\#){transform:translateX(100%)}
1538
1540
  .x1xrp5p4:first-child:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--radius-element)}
1539
1541
  .xyf0ibl:first-child:not(#\#):not(#\#):not(#\#){border-inline-start-style:none}
1540
1542
  .x1pjv70x:first-child:not(#\#):not(#\#):not(#\#){border-inline-start-width:0}
@@ -2079,6 +2081,8 @@
2079
2081
  .xubczrd.xubczrd:where(:not(:disabled,[aria-disabled="true"]))[data-astryx-press="fading"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-image:var(--_press-paint-image)}
2080
2082
  @starting-style{.x1lymnkk.x1lymnkk:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){transform:translateX(-100%)}}
2081
2083
  @starting-style{.x14gflnl.x14gflnl:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){transform:translateX(100%)}}
2084
+ @starting-style{.xrxqmjf.xrxqmjf:dir(rtl):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){transform:translateX(-100%)}}
2085
+ @starting-style{.x1r0dqvm.x1r0dqvm:dir(rtl):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){transform:translateX(100%)}}
2082
2086
  @media (forced-colors: active){.x1pjcgc3.x1pjcgc3[data-astryx-press="fading"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-color:Highlight}}
2083
2087
  @media (forced-colors: active){.x1r4mieh.x1r4mieh[data-astryx-press="on"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-color:Highlight}}
2084
2088
  .x1k48kgn:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{border-radius:inherit}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.2-canary.86e8392",
3
+ "version": "0.7.2-canary.9da2d0b",
4
4
  "displayName": "Astryx 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",
@@ -792,8 +792,8 @@
792
792
  "react-dom": ">=19.0.0"
793
793
  },
794
794
  "devDependencies": {
795
- "@astryxdesign/a11y-spec": "0.7.2-canary.86e8392",
796
- "@astryxdesign/cli": "0.7.2-canary.86e8392",
795
+ "@astryxdesign/a11y-spec": "0.7.2-canary.9da2d0b",
796
+ "@astryxdesign/cli": "0.7.2-canary.9da2d0b",
797
797
  "@babel/cli": "^7.29.7",
798
798
  "@babel/core": "^7.29.7",
799
799
  "@babel/preset-react": "^7.29.7",
@@ -239,11 +239,13 @@ Consumer prop syntax and examples remain in `Drawer.doc.mjs`.
239
239
  - **AR5 — Close affordance.** Drawer provides no close control of its own. When
240
240
  rendered, the `DrawerHeader` close action keeps its translated accessible name
241
241
  and Button-owned keyboard/focus behavior in both presentations.
242
- - **AR6 — Direction and motion.** Logical inset placement follows computed
243
- direction; slide-direction mirroring currently requires a `[dir="rtl"]`
244
- ancestor. Self-applied `dir="rtl"` and CSS-only direction do not trigger
245
- transform mirroring. Motion reduces under `prefers-reduced-motion` without
246
- changing the final state.
242
+ - **AR6 — Direction and motion.** Logical inset placement and slide-direction
243
+ mirroring follow DOM directionality from the nearest effective `dir` attribute,
244
+ including an explicit local override or an inherited direction. As required by
245
+ `design:bidirectionality`, a region whose direction differs from its ancestor
246
+ describes that direction in DOM `dir`; a CSS-only `direction` declaration is
247
+ not a supported substitute. Motion reduces under `prefers-reduced-motion`
248
+ without changing the final state.
247
249
 
248
250
  ## Design relationships
249
251
 
@@ -299,7 +301,7 @@ decide future target qualification.
299
301
  | FR15 | `Drawer.test.tsx` purpose suite | `info`, `form`, `required`; modal and non-modal; a sibling behind a required drawer | A `form` drawer closes on a scrim click, a `required` drawer closes or lets Escape reach the drawer behind it, or its role semantics drift. | `audit:Drawer/behavior` |
300
302
  | FR12 | source, `Drawer.doc.mjs`, `DrawerHeader.doc.mjs`, the `DrawerHeader` target test, and current target discovery | start/end root Panel and inherited container context | Current root target/axis or padding reset changes, or the record claims an unreachable child target or decides future admission. | `audit:Drawer/theming` |
301
303
  | FR16 | `Drawer.test.tsx` container padding suites; `defineTheme.test.ts` drawer mapping; browser evidence | default, `padding` prop, theme `padding`, lone padded Section child, Layout composition | The published inset differs from the applied padding, the default drawer changes geometry, or the block-end safe area is lost or double-subtracted. | `audit:Drawer/theming` |
302
- | AR6 | side tests and source inspection plus Storybook ancestor-RTL audit | settled `end` placement under ancestor RTL; transform mirroring and reduced motion source-inspected | Audited settled placement or source-inspected ancestor mirroring/reduced-motion behavior changes without corresponding evidence. | `audit:Drawer/accessibility` |
304
+ | AR6 | `DrawerDirection.test.tsx` DOM-direction and closed/entry parity suites plus exact-head Storybook RTL evidence | start/end; self-applied, inherited, and nested opposite `dir`; closed and opening transforms | Any effective DOM direction or local override moves the panel from a different edge than its logical placement, or closed and opening transforms diverge. | `audit:Drawer/accessibility` |
303
305
 
304
306
  ## Decision log
305
307
 
@@ -153,8 +153,9 @@ const styles = stylex.create({
153
153
  rendered: {
154
154
  display: 'flex',
155
155
  },
156
- // start/end transforms flip under RTL so the panel always slides in from
157
- // the edge it is anchored to.
156
+ // DOM directionality comes from the nearest effective `dir` attribute. A
157
+ // local override and an inherited region therefore agree with the logical
158
+ // edge the panel is anchored to.
158
159
  end: {
159
160
  insetInlineEnd: 0,
160
161
  insetInlineStart: 'auto',
@@ -163,7 +164,7 @@ const styles = stylex.create({
163
164
  borderInlineStartColor: colorVars['--color-border'],
164
165
  transform: {
165
166
  default: 'translateX(100%)',
166
- ':is([dir="rtl"] *)': 'translateX(-100%)',
167
+ ':dir(rtl)': 'translateX(-100%)',
167
168
  },
168
169
  },
169
170
  endOpen: {
@@ -171,7 +172,7 @@ const styles = stylex.create({
171
172
  default: 'translateX(0)',
172
173
  '@starting-style': {
173
174
  default: 'translateX(100%)',
174
- ':is([dir="rtl"] *)': 'translateX(-100%)',
175
+ ':dir(rtl)': 'translateX(-100%)',
175
176
  },
176
177
  },
177
178
  },
@@ -183,7 +184,7 @@ const styles = stylex.create({
183
184
  borderInlineEndColor: colorVars['--color-border'],
184
185
  transform: {
185
186
  default: 'translateX(-100%)',
186
- ':is([dir="rtl"] *)': 'translateX(100%)',
187
+ ':dir(rtl)': 'translateX(100%)',
187
188
  },
188
189
  },
189
190
  startOpen: {
@@ -191,7 +192,7 @@ const styles = stylex.create({
191
192
  default: 'translateX(0)',
192
193
  '@starting-style': {
193
194
  default: 'translateX(-100%)',
194
- ':is([dir="rtl"] *)': 'translateX(100%)',
195
+ ':dir(rtl)': 'translateX(100%)',
195
196
  },
196
197
  },
197
198
  },
@@ -0,0 +1,127 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file DrawerDirection.test.tsx
5
+ * @input Drawer closed transforms and the matching @starting-style declarations
6
+ * @output Regression coverage for resolved DOM LTR/RTL motion on both logical sides
7
+ * @position Focused direction contract; complements Drawer.test.tsx behavior coverage
8
+ */
9
+
10
+ import {readFileSync} from 'node:fs';
11
+ import {join} from 'node:path';
12
+ import * as ts from 'typescript';
13
+ import {describe, expect, it} from 'vitest';
14
+ import {render, screen} from '@testing-library/react';
15
+ import {Drawer} from './Drawer';
16
+
17
+ function closedTransform(): string {
18
+ return getComputedStyle(screen.getByRole('dialog', {hidden: true})).transform;
19
+ }
20
+
21
+ describe.each([
22
+ {side: 'start' as const, ltr: 'translateX(-100%)', rtl: 'translateX(100%)'},
23
+ {side: 'end' as const, ltr: 'translateX(100%)', rtl: 'translateX(-100%)'},
24
+ ])('Drawer direction on side=$side', ({side, ltr, rtl}) => {
25
+ it('uses a self-applied RTL direction', () => {
26
+ render(
27
+ <Drawer
28
+ isOpen={false}
29
+ onOpenChange={() => {}}
30
+ label="Details"
31
+ side={side}
32
+ dir="rtl">
33
+ Content
34
+ </Drawer>,
35
+ );
36
+ expect(closedTransform()).toBe(rtl);
37
+ });
38
+
39
+ it('honors a nearer LTR override inside an RTL page', () => {
40
+ render(
41
+ <div dir="rtl">
42
+ <Drawer
43
+ isOpen={false}
44
+ onOpenChange={() => {}}
45
+ label="Details"
46
+ side={side}
47
+ dir="ltr">
48
+ Content
49
+ </Drawer>
50
+ </div>,
51
+ );
52
+ expect(closedTransform()).toBe(ltr);
53
+ });
54
+ });
55
+
56
+ const SOURCE = readFileSync(join(__dirname, 'Drawer.tsx'), 'utf8');
57
+
58
+ function styleDefinition(name: string): ts.ObjectLiteralExpression {
59
+ const file = ts.createSourceFile(
60
+ 'Drawer.tsx',
61
+ SOURCE,
62
+ ts.ScriptTarget.Latest,
63
+ true,
64
+ ts.ScriptKind.TSX,
65
+ );
66
+ let found: ts.ObjectLiteralExpression | undefined;
67
+ const visit = (node: ts.Node) => {
68
+ if (
69
+ ts.isPropertyAssignment(node) &&
70
+ ts.isIdentifier(node.name) &&
71
+ node.name.text === name &&
72
+ ts.isObjectLiteralExpression(node.initializer)
73
+ ) {
74
+ found = node.initializer;
75
+ }
76
+ ts.forEachChild(node, visit);
77
+ };
78
+ visit(file);
79
+ if (found == null) {
80
+ throw new Error(`No style named "${name}" in Drawer.tsx`);
81
+ }
82
+ return found;
83
+ }
84
+
85
+ function property(style: ts.ObjectLiteralExpression, key: string): string {
86
+ const entry = style.properties.find(
87
+ candidate =>
88
+ ts.isPropertyAssignment(candidate) &&
89
+ (ts.isIdentifier(candidate.name) || ts.isStringLiteral(candidate.name)) &&
90
+ candidate.name.text === key,
91
+ );
92
+ if (entry == null) {
93
+ throw new Error(`No "${key}" in ${style.getText().slice(0, 40)}…`);
94
+ }
95
+ return (entry as ts.PropertyAssignment).initializer.getText();
96
+ }
97
+
98
+ describe.each([
99
+ {
100
+ side: 'start',
101
+ closed: 'start',
102
+ open: 'startOpen',
103
+ ltr: 'translateX(-100%)',
104
+ rtl: 'translateX(100%)',
105
+ },
106
+ {
107
+ side: 'end',
108
+ closed: 'end',
109
+ open: 'endOpen',
110
+ ltr: 'translateX(100%)',
111
+ rtl: 'translateX(-100%)',
112
+ },
113
+ ])('Drawer $side entry declaration', ({closed, open, ltr, rtl}) => {
114
+ it('parks closed on the computed logical edge', () => {
115
+ const transform = property(styleDefinition(closed), 'transform');
116
+ expect(transform).toContain(`default: '${ltr}'`);
117
+ expect(transform).toContain(`':dir(rtl)': '${rtl}'`);
118
+ });
119
+
120
+ it('starts opening from the same computed logical edge', () => {
121
+ const transform = property(styleDefinition(open), 'transform');
122
+ expect(transform).toContain("default: 'translateX(0)'");
123
+ expect(transform).toContain('@starting-style');
124
+ expect(transform).toContain(`default: '${ltr}'`);
125
+ expect(transform).toContain(`':dir(rtl)': '${rtl}'`);
126
+ });
127
+ });
@@ -50,6 +50,19 @@ describe('DrawerHeader', () => {
50
50
  expect(names).toEqual(['Back', 'Edit', 'Close']);
51
51
  });
52
52
 
53
+ it('renders 0 values inside their start and end content slots', () => {
54
+ const {container} = render(
55
+ <DrawerHeader title="Details" startContent={0} endContent={0} />,
56
+ );
57
+
58
+ expect(
59
+ container.querySelector('.astryx-drawer-header-start-content'),
60
+ ).toHaveTextContent('0');
61
+ expect(
62
+ container.querySelector('.astryx-drawer-header-end-content'),
63
+ ).toHaveTextContent('0');
64
+ });
65
+
53
66
  it('renders a 0 subtitle and skips an empty one', () => {
54
67
  const {rerender, container} = render(
55
68
  <DrawerHeader title="Details" subtitle={0} />,
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DrawerHeader.tsx
7
- * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, mergeProps, themeProps
7
+ * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, isRenderable, mergeProps, themeProps
8
8
  * @output Exports DrawerHeader component and DrawerHeaderProps
9
9
  * @position Drawer header; composed in a Layout header slot inside Drawer, tested by DrawerHeader.test.tsx
10
10
  *
@@ -22,6 +22,7 @@
22
22
  * - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
23
23
  * - /packages/cli/assets/templates/blocks/components/DrawerHeader/ (showcase blocks)
24
24
  * - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
25
+ * - /apps/storybook/stories/DrawerHeader.stories.tsx (component audit fixture)
25
26
  */
26
27
 
27
28
  import type {ReactNode} from 'react';
@@ -34,7 +35,7 @@ import {Icon} from '../Icon';
34
35
  import {LayoutHeader} from '../Layout';
35
36
  import {Text} from '../Text';
36
37
  import {useTranslator} from '../i18n';
37
- import {mergeProps, themeProps} from '../utils';
38
+ import {isRenderable, mergeProps, themeProps} from '../utils';
38
39
 
39
40
  const styles = stylex.create({
40
41
  container: {
@@ -147,10 +148,9 @@ export function DrawerHeader({
147
148
  ...rest
148
149
  }: DrawerHeaderProps) {
149
150
  const t = useTranslator();
150
- // A node subtitle may be `0`: render it inside Text instead of letting a
151
- // truthiness check leak a bare text node, and skip only empty values.
152
- const hasSubtitle =
153
- subtitle != null && typeof subtitle !== 'boolean' && subtitle !== '';
151
+ const hasSubtitle = isRenderable(subtitle);
152
+ const hasStartContent = isRenderable(startContent);
153
+ const hasEndContent = isRenderable(endContent);
154
154
  const shouldCompensateEndBlock =
155
155
  endContentEdgeCompensation == null
156
156
  ? onOpenChange != null
@@ -175,7 +175,7 @@ export function DrawerHeader({
175
175
  themeProps('drawer-header'),
176
176
  stylex.props(styles.container),
177
177
  )}>
178
- {startContent && (
178
+ {hasStartContent && (
179
179
  <div
180
180
  {...mergeProps(
181
181
  themeProps('drawer-header-start-content'),
@@ -196,7 +196,7 @@ export function DrawerHeader({
196
196
  </Text>
197
197
  )}
198
198
  </div>
199
- {(endContent || onOpenChange) && (
199
+ {(hasEndContent || onOpenChange != null) && (
200
200
  <div
201
201
  {...mergeProps(
202
202
  themeProps('drawer-header-end-content'),