@pharos-ds/core 1.0.5 → 1.0.7-canary.8f6397b

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @xds/core
2
2
 
3
+ # 1.0.7
4
+
5
+ #### New Features
6
+
7
+ - SideNavSection gains collapsible (true, or {defaultIsCollapsed, isCollapsed, onCollapsedChange}), mirroring SideNavItem: the title and subtitle become a native toggle button with a chevron, aria-expanded and aria-controls, endContent stays outside the button, and collapsed items are inert and aria-hidden behind the same grid-rows animation (instant under reduced motion); the section reflects data-collapsed. Ignored with isHeaderHidden or when the SideNav is collapsed to its icon rail; without the prop the DOM is unchanged
8
+
9
+ #### Contributors
10
+
11
+ Thanks to everyone who contributed to this release:
12
+
13
+ - @Caio-Silvestre
14
+
15
+ ---
16
+
17
+ # 1.0.6
18
+
19
+ ---
20
+
3
21
  # 1.0.5
4
22
 
5
23
  #### New Features
@@ -39,6 +39,26 @@ export interface SideNavSectionProps extends BaseProps<HTMLDivElement> {
39
39
  * @default false
40
40
  */
41
41
  isHeaderHidden?: boolean;
42
+ /**
43
+ * Lets the user collapse the section's items by clicking its title.
44
+ * The title area becomes a toggle button (`aria-expanded`) with a chevron;
45
+ * `endContent` stays outside the button.
46
+ *
47
+ * - `true` — collapsible with defaults (starts expanded)
48
+ * - Object — controlled/configured:
49
+ * - `defaultIsCollapsed` — start collapsed (default: false)
50
+ * - `isCollapsed` + `onCollapsedChange` — controlled mode
51
+ *
52
+ * Ignored when `isHeaderHidden` is set or the SideNav itself is collapsed
53
+ * to its icon rail (items stay visible so their icons remain reachable).
54
+ *
55
+ * @default false
56
+ */
57
+ collapsible?: boolean | {
58
+ defaultIsCollapsed?: boolean;
59
+ isCollapsed?: boolean;
60
+ onCollapsedChange?: (isCollapsed: boolean) => void;
61
+ };
42
62
  }
43
63
  /**
44
64
  * Section grouping for SideNav items.
@@ -54,7 +74,7 @@ export interface SideNavSectionProps extends BaseProps<HTMLDivElement> {
54
74
  * </SideNavSection>
55
75
  * ```
56
76
  */
57
- export declare function SideNavSection({ ref, title, subtitle, children, endContent, isHeaderHidden, xstyle, className, style, 'data-testid': testId, ...rest }: SideNavSectionProps): React.JSX.Element;
77
+ export declare function SideNavSection({ ref, title, subtitle, children, endContent, isHeaderHidden, collapsible, xstyle, className, style, 'data-testid': testId, ...rest }: SideNavSectionProps): React.JSX.Element;
58
78
  export declare namespace SideNavSection {
59
79
  var displayName: string;
60
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SideNavSection.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavSection.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AASnD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA+D5C,MAAM,WAAW,mBAAoB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACpE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,cAAsB,EACtB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,qBAyDrB;yBArEe,cAAc"}
1
+ {"version":3,"file":"SideNavSection.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavSection.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,EAKZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsJ5C,MAAM,WAAW,mBAAoB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACpE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EACR,OAAO,GACP;QACE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;KACpD,CAAC;CACP;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,cAAsB,EACtB,WAAW,EACX,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,qBA+IrB;yBA5Je,cAAc"}
@@ -17,12 +17,13 @@
17
17
  * - /apps/storybook/stories/SideNav.stories.tsx
18
18
  * - /packages/cli/templates/blocks/components/SideNav/ (showcase blocks)
19
19
  */
20
- import React, { useId } from 'react';
20
+ import React, { useCallback, useId, useMemo, useState } from 'react';
21
21
  import * as stylex from '@stylexjs/stylex';
22
22
  import "../theme/tokens.stylex.js";
23
- import { colorVars, spacingVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js";
23
+ import { colorVars, spacingVars, fontWeightVars, typeScaleVars, durationVars, easeVars, radiusVars } from "../theme/tokens.stylex.js";
24
24
  import { mergeProps } from "../utils/index.js";
25
25
  import { useSideNavCollapse } from "./SideNavCollapseContext.js";
26
+ import { getIcon } from "../Icon/globalIconRegistry.js";
26
27
  import { themeProps } from "../utils/themeProps.js";
27
28
  // =============================================================================
28
29
  // Styles
@@ -66,6 +67,7 @@ export function SideNavSection({
66
67
  children,
67
68
  endContent,
68
69
  isHeaderHidden = false,
70
+ collapsible,
69
71
  xstyle,
70
72
  className,
71
73
  style,
@@ -77,31 +79,79 @@ export function SideNavSection({
77
79
  } = useSideNavCollapse();
78
80
  const id = useId();
79
81
  const titleId = `${id}-title`;
80
- const headerContent = /*#__PURE__*/_jsxs(_Fragment, {
81
- children: [/*#__PURE__*/_jsxs("span", {
82
+ const itemsId = `${id}-items`;
83
+
84
+ // Section collapse state (mirrors SideNavItem's `collapsible`)
85
+ const collapsibleConfig = useMemo(() => typeof collapsible === 'object' ? collapsible : {}, [collapsible]);
86
+ const controlledCollapsed = collapsibleConfig.isCollapsed;
87
+ const isControlled = controlledCollapsed !== undefined;
88
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(collapsibleConfig.defaultIsCollapsed ?? false);
89
+ const isSectionCollapsed = isControlled ? controlledCollapsed : uncontrolledCollapsed;
90
+ const toggleSectionCollapse = useCallback(() => {
91
+ const next = !isSectionCollapsed;
92
+ if (!isControlled) {
93
+ setUncontrolledCollapsed(next);
94
+ }
95
+ collapsibleConfig.onCollapsedChange?.(next);
96
+ }, [isSectionCollapsed, isControlled, collapsibleConfig]);
97
+ const shouldHideHeader = isHeaderHidden || isCollapsed;
98
+ // Collapse only applies while the header is visible: a hidden header has
99
+ // no toggle, and the icon rail must keep every item reachable.
100
+ const isSectionCollapsible = collapsible != null && collapsible !== false && !shouldHideHeader;
101
+ const isItemsCollapsed = isSectionCollapsible && isSectionCollapsed;
102
+ const titleContent = /*#__PURE__*/_jsxs("span", {
103
+ ...{
104
+ className: "x78zum5 xdt5ytf x98rzlu xeuugli"
105
+ },
106
+ children: [/*#__PURE__*/_jsx("span", {
107
+ id: titleId,
82
108
  ...{
83
- className: "x78zum5 xdt5ytf x98rzlu xeuugli"
109
+ className: "x141an7d x2mo6ok x1ltkj2j xv1l7n4 xb3r6kr xlyipyv xuxw1ft"
84
110
  },
85
- children: [/*#__PURE__*/_jsx("span", {
86
- id: titleId,
87
- ...{
88
- className: "x141an7d x2mo6ok x1ltkj2j xv1l7n4 xb3r6kr xlyipyv xuxw1ft"
89
- },
90
- children: title
91
- }), subtitle && /*#__PURE__*/_jsx("span", {
92
- ...{
93
- className: "x141an7d x1ltkj2j xv1l7n4 xb3r6kr xlyipyv xuxw1ft"
94
- },
95
- children: subtitle
96
- })]
97
- }), endContent && /*#__PURE__*/_jsx("span", {
111
+ children: title
112
+ }), subtitle && /*#__PURE__*/_jsx("span", {
98
113
  ...{
99
- className: "x2lah0s x78zum5 x6s0dn4"
114
+ className: "x141an7d x1ltkj2j xv1l7n4 xb3r6kr xlyipyv xuxw1ft"
100
115
  },
101
- children: endContent
116
+ children: subtitle
102
117
  })]
103
118
  });
104
- const shouldHideHeader = isHeaderHidden || isCollapsed;
119
+ const endContentElement = endContent && /*#__PURE__*/_jsx("span", {
120
+ ...{
121
+ 0: {
122
+ className: "x2lah0s x78zum5 x6s0dn4"
123
+ },
124
+ 1: {
125
+ className: "x2lah0s x78zum5 x6s0dn4 x1djylfy"
126
+ }
127
+ }[!!isSectionCollapsible << 0],
128
+ children: endContent
129
+ });
130
+ const headerContent = isSectionCollapsible ? /*#__PURE__*/_jsxs(_Fragment, {
131
+ children: [/*#__PURE__*/_jsxs("button", {
132
+ type: "button",
133
+ onClick: toggleSectionCollapse,
134
+ "aria-expanded": !isSectionCollapsed,
135
+ "aria-controls": itemsId,
136
+ ...{
137
+ className: "x78zum5 x6s0dn4 x1txdalj x98rzlu xeuugli xh8yej3 xf314gf xu0wf1k x1ghz6dp xc342km xng3xce xh6dtrn xjbqb8w x1heor9g xjb2p0i x1qlqyl8 x1pd3egz x15bjb6t x1yc453h x1ypdohk x87ps6o xe9uy6x xyxi2l3 x1a2a7pz x17nn4n9 x1wfwxd8 x1bqaal"
138
+ },
139
+ children: [titleContent, /*#__PURE__*/_jsx("span", {
140
+ "aria-hidden": true,
141
+ ...{
142
+ 0: {
143
+ className: "x3nfvp2 x6s0dn4 xl56j7k x1mtvzj0 xvpftlf xv1l7n4 x11xpdln xuedmi6 x12w9bfk xlr8y92 x2lah0s"
144
+ },
145
+ 1: {
146
+ className: "x3nfvp2 x6s0dn4 xl56j7k x1mtvzj0 xvpftlf xv1l7n4 x11xpdln xuedmi6 x12w9bfk xlr8y92 x2lah0s x19jd1h0"
147
+ }
148
+ }[!!!isSectionCollapsed << 0],
149
+ children: getIcon('chevronDown')
150
+ })]
151
+ }), endContentElement]
152
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
153
+ children: [titleContent, endContentElement]
154
+ });
105
155
  const visuallyHiddenStyle = shouldHideHeader ? {
106
156
  position: 'absolute',
107
157
  width: 1,
@@ -115,19 +165,49 @@ export function SideNavSection({
115
165
  } : {};
116
166
  return /*#__PURE__*/_jsxs("div", {
117
167
  ref: ref,
118
- ...mergeProps(themeProps('side-nav-section'), stylex.props(styles.root, xstyle), className, style),
168
+ ...mergeProps(themeProps('side-nav-section', isSectionCollapsible ? {
169
+ collapsed: isSectionCollapsed ? 'collapsed' : null
170
+ } : undefined), stylex.props(styles.root, xstyle), className, style),
119
171
  ...rest,
120
172
  role: "group",
121
173
  "aria-labelledby": titleId,
122
174
  "data-testid": testId,
123
175
  children: [/*#__PURE__*/_jsx("div", {
124
176
  ...mergeProps({
125
- className: "x78zum5 x6s0dn4 x1txdalj xf314gf xu0wf1k xt0e3qv x87ps6o"
126
- }, {
177
+ 0: {
178
+ className: "x78zum5 x6s0dn4 x1txdalj xf314gf xu0wf1k xt0e3qv x87ps6o"
179
+ },
180
+ 1: {
181
+ className: "x78zum5 x6s0dn4 x1txdalj xt0e3qv x87ps6o xnjsko4 xt970qd"
182
+ }
183
+ }[!!isSectionCollapsible << 0], {
127
184
  style: shouldHideHeader ? visuallyHiddenStyle : undefined
128
185
  }),
129
186
  children: headerContent
130
- }), /*#__PURE__*/_jsx("div", {
187
+ }), isSectionCollapsible ? /*#__PURE__*/_jsx("div", {
188
+ id: itemsId,
189
+ "aria-hidden": isItemsCollapsed || undefined,
190
+ inert: isItemsCollapsed ? true : undefined,
191
+ ...{
192
+ 0: {
193
+ className: "xrvj5dj x1tu4anv x1qn9uv2 x80gvsz x12w9bfk xlr8y92"
194
+ },
195
+ 1: {
196
+ className: "xrvj5dj x1qn9uv2 x80gvsz x12w9bfk xlr8y92 xihq33y"
197
+ }
198
+ }[!!isItemsCollapsed << 0],
199
+ children: /*#__PURE__*/_jsx("div", {
200
+ ...{
201
+ className: "xb3r6kr x2lwn1j"
202
+ },
203
+ children: /*#__PURE__*/_jsx("div", {
204
+ ...{
205
+ className: "x78zum5 xdt5ytf x1lsbc85"
206
+ },
207
+ children: children
208
+ })
209
+ })
210
+ }) : /*#__PURE__*/_jsx("div", {
131
211
  ...{
132
212
  className: "x78zum5 xdt5ytf x1lsbc85"
133
213
  },
package/dist/pharos.css CHANGED
@@ -1094,6 +1094,7 @@
1094
1094
  .xof6bs:is(th:hover *):not(#\#):not(#\#):not(#\#){opacity:1}
1095
1095
  .xofkqq2:popover-open:not(#\#):not(#\#):not(#\#){opacity:1}
1096
1096
  .xzunyri:focus-visible:not(#\#):not(#\#):not(#\#){outline-color:var(--color-accent)}
1097
+ .x1bqaal:focus-visible:not(#\#):not(#\#):not(#\#){outline-offset:-2px}
1097
1098
  .x1506sbz.x1506sbz:where(.xy9u6lz:has(:focus-visible) *):not(#\#):not(#\#):not(#\#){outline-offset:2px}
1098
1099
  .x1ipu19n.x1ipu19n:where(.xjy6dfm:has(:focus-visible) *):not(#\#):not(#\#):not(#\#){outline-offset:2px}
1099
1100
  .x7s97pk:focus-visible:not(#\#):not(#\#):not(#\#){outline-offset:2px}