braid-design-system 34.9.1 → 34.10.0

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 (40) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/lib/components/Accordion/Accordion.cjs +61 -13
  3. package/dist/lib/components/Accordion/Accordion.d.cts +56 -2
  4. package/dist/lib/components/Accordion/Accordion.d.mts +56 -2
  5. package/dist/lib/components/Accordion/Accordion.mjs +62 -14
  6. package/dist/lib/components/Accordion/AccordionContext.d.cts +7 -0
  7. package/dist/lib/components/Accordion/AccordionContext.d.mts +7 -0
  8. package/dist/lib/components/Accordion/AccordionItem.cjs +86 -12
  9. package/dist/lib/components/Accordion/AccordionItem.css.cjs +15 -0
  10. package/dist/lib/components/Accordion/AccordionItem.css.mjs +13 -1
  11. package/dist/lib/components/Accordion/AccordionItem.d.cts +5 -0
  12. package/dist/lib/components/Accordion/AccordionItem.d.mts +5 -0
  13. package/dist/lib/components/Accordion/AccordionItem.mjs +88 -14
  14. package/dist/lib/components/Accordion/AccordionItem.playroom.cjs +6 -2
  15. package/dist/lib/components/Accordion/AccordionItem.playroom.d.cts +1 -1
  16. package/dist/lib/components/Accordion/AccordionItem.playroom.d.mts +1 -1
  17. package/dist/lib/components/Accordion/AccordionItem.playroom.mjs +6 -2
  18. package/dist/lib/components/private/Modal/Modal.cjs +0 -10
  19. package/dist/lib/components/private/Modal/Modal.mjs +0 -10
  20. package/dist/lib/components/useToast/Toast.cjs +8 -3
  21. package/dist/lib/components/useToast/Toast.mjs +8 -3
  22. package/dist/lib/components/useToast/ToastTypes.d.cts +3 -1
  23. package/dist/lib/components/useToast/ToastTypes.d.mts +3 -1
  24. package/dist/lib/components/useToast/useFlipList.cjs +3 -3
  25. package/dist/lib/components/useToast/useFlipList.mjs +3 -3
  26. package/dist/lib/components/useToast/useTimeout.cjs +1 -1
  27. package/dist/lib/components/useToast/useTimeout.mjs +1 -1
  28. package/dist/lib/css/atoms/sprinkles.css.d.cts +1 -1
  29. package/dist/lib/css/atoms/sprinkles.css.d.mts +1 -1
  30. package/dist/lib/playroom/snippets/Accordion.cjs +8 -12
  31. package/dist/lib/playroom/snippets/Accordion.mjs +8 -12
  32. package/dist/playroom/FrameComponent.mjs +1 -2
  33. package/dist/playroom/scope.mjs +1 -2
  34. package/dist/playroom/snippets.mjs +1 -2
  35. package/dist/test.mjs +1 -2
  36. package/dist/themes/docs.mjs +1 -2
  37. package/dist/themes/seekBusiness.mjs +1 -2
  38. package/dist/themes/seekJobs.mjs +1 -2
  39. package/dist/themes/wireframe.mjs +1 -2
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # braid-design-system
2
2
 
3
+ ## 34.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **Accordion, AccordionItem:** Add height animation and shared open state ([#2144](https://github.com/seek-oss/braid-design-system/pull/2144))
8
+
9
+ Items animate their height when opening and closing, unless reduced motion is set.
10
+
11
+ Set `multiple` to `false` so only one item can be open, or use `value`, `defaultValue`, and `onChange` on `Accordion` to choose which items are open.
12
+
13
+ **EXAMPLE USAGE:**
14
+
15
+ ```jsx
16
+ <Accordion multiple={false} defaultValue="one">
17
+ <AccordionItem value="one" label="One">
18
+ ...
19
+ </AccordionItem>
20
+ <AccordionItem value="two" label="Two">...</AccordionItem>
21
+ </Accordion>
22
+ ```
23
+ - **useToast:** Add `duration` option ([#2181](https://github.com/seek-oss/braid-design-system/pull/2181))
24
+
25
+ Toasts accept `short` (6 seconds), `standard` (10 seconds), or `indefinite`. The default is `standard`, matching the previous dismiss time.
26
+
27
+ **EXAMPLE USAGE:**
28
+
29
+ ```jsx
30
+ showToast({
31
+ tone: 'positive',
32
+ message: 'Saved',
33
+ duration: 'short',
34
+ });
35
+ ```
36
+
37
+ ### Patch Changes
38
+
39
+ - **Dialog, Drawer:** Fix focus in stacked modals ([#2184](https://github.com/seek-oss/braid-design-system/pull/2184))
40
+
41
+ Fixes an issue where, with multiple modals open, focus could move to an underlying modal after switching away from and back to the browser window, preventing interaction with fields in the top-most modal.
42
+
3
43
  ## 34.9.1
4
44
 
5
45
  ### Patch Changes
@@ -16,6 +16,13 @@ const validSpaceValues = [
16
16
  "xlarge"
17
17
  ];
18
18
  const defaultSize = "large";
19
+ const normalizeAccordionValue = (input, multiple) => {
20
+ if (input == null || input === "") return [];
21
+ const list = typeof input === "string" ? [input] : [...input];
22
+ (0, assert.default)(list.every((item) => typeof item === "string" && item.length > 0), "Accordion 'value' and 'defaultValue' must be a non-empty string or an array of non-empty strings.");
23
+ (0, assert.default)(multiple || list.length <= 1, "When 'multiple' is false, Accordion 'value' and 'defaultValue' must be a string or a single-item array.");
24
+ return list;
25
+ };
19
26
  const defaultSpaceForSize = {
20
27
  divided: {
21
28
  xsmall: "medium",
@@ -30,26 +37,67 @@ const defaultSpaceForSize = {
30
37
  large: "large"
31
38
  }
32
39
  };
33
- const Accordion = ({ children, size = defaultSize, tone, weight, space: spaceProp, dividers = true, data, ...restProps }) => {
34
- (0, assert.default)(spaceProp === void 0 || Object.values(require_sprinkles_css.normalizeResponsiveValue(spaceProp)).every((value) => value === void 0 || validSpaceValues.includes(value)), `To ensure adequate space for touch targets, 'space' prop values must be one of the following: ${validSpaceValues.map((x) => `"${x}"`).join(", ")}`);
40
+ const assertUniqueItemValues = (children) => {
41
+ const seen = /* @__PURE__ */ new Set();
42
+ for (const child of require_flattenChildren.default(children)) {
43
+ if (!(0, react.isValidElement)(child)) continue;
44
+ const itemValue = child.props.value;
45
+ if (typeof itemValue !== "string") continue;
46
+ (0, assert.default)(!seen.has(itemValue), `AccordionItem value "${itemValue}" is used more than once. Each AccordionItem value must be unique.`);
47
+ seen.add(itemValue);
48
+ }
49
+ };
50
+ const Accordion = (props) => {
51
+ const { children, size = defaultSize, tone, weight, multiple = true, value, defaultValue, space: spaceProp, dividers = true, data, ...restProps } = props;
52
+ (0, assert.default)(spaceProp === void 0 || Object.values(require_sprinkles_css.normalizeResponsiveValue(spaceProp)).every((spaceValue) => spaceValue === void 0 || validSpaceValues.includes(spaceValue)), `To ensure adequate space for touch targets, 'space' prop values must be one of the following: ${validSpaceValues.map((x) => `"${x}"`).join(", ")}`);
35
53
  (0, assert.default)(tone === void 0 || require_AccordionContext.validTones.includes(tone), `The 'tone' prop should be one of the following: ${require_AccordionContext.validTones.map((x) => `"${x}"`).join(", ")}`);
36
- if (process.env.NODE_ENV !== "production")
37
- /**
38
- * Validate that consumers are not passing `data-*`props,
39
- * which will not work and are not validated by TypeScript.
40
- */
41
- require_buildDataAttributes.default({
42
- data,
43
- validateRestProps: restProps
44
- });
54
+ (0, assert.default)(value === void 0 || defaultValue === void 0, "Accordion 'defaultValue' cannot be set when 'value' is set. Use 'value' to control the open items, or 'defaultValue' for the initial state.");
55
+ (0, assert.default)(value === void 0 || typeof props.onChange === "function", "Accordion 'onChange' must be set when 'value' is set.");
56
+ const managed = multiple === false || value !== void 0 || defaultValue !== void 0 || props.onChange !== void 0;
57
+ if (process.env.NODE_ENV !== "production") {
58
+ /**
59
+ * Validate that consumers are not passing `data-*`props,
60
+ * which will not work and are not validated by TypeScript.
61
+ */
62
+ require_buildDataAttributes.default({
63
+ data,
64
+ validateRestProps: restProps
65
+ });
66
+ assertUniqueItemValues(children);
67
+ }
68
+ const normalizedValue = (0, react.useMemo)(() => value === void 0 ? void 0 : normalizeAccordionValue(value, multiple), [multiple, value]);
69
+ const [uncontrolledValue, setUncontrolledValue] = (0, react.useState)(() => normalizeAccordionValue(defaultValue, multiple));
70
+ const openValues = normalizedValue ?? uncontrolledValue;
71
+ const openValuesRef = (0, react.useRef)(openValues);
72
+ const propsRef = (0, react.useRef)(props);
73
+ openValuesRef.current = openValues;
74
+ propsRef.current = props;
75
+ const toggleValue = (0, react.useCallback)((itemValue) => {
76
+ const current = openValuesRef.current;
77
+ const isOpen = current.includes(itemValue);
78
+ let next;
79
+ if (!multiple) next = isOpen ? [] : [itemValue];
80
+ else if (isOpen) next = current.filter((openValue) => openValue !== itemValue);
81
+ else next = [...current, itemValue];
82
+ if (normalizedValue === void 0) setUncontrolledValue(next);
83
+ const p = propsRef.current;
84
+ if (p.multiple === false) p.onChange?.(next[0] ?? "");
85
+ else p.onChange?.(next);
86
+ }, [multiple, normalizedValue]);
45
87
  const contextValue = (0, react.useMemo)(() => ({
46
88
  size,
47
89
  tone,
48
- weight
90
+ weight,
91
+ managed,
92
+ openValues,
93
+ toggleValue
49
94
  }), [
50
95
  size,
51
96
  tone,
52
- weight
97
+ weight,
98
+ managed,
99
+ openValues,
100
+ toggleValue
53
101
  ]);
54
102
  const space = spaceProp ?? defaultSpaceForSize[dividers ? "divided" : "undivided"][size];
55
103
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AccordionContext.AccordionContext.Provider, {
@@ -5,7 +5,61 @@ import { AccordionContextValue } from "./AccordionContext.cjs";
5
5
  import { FC } from "react";
6
6
  //#region src/lib/components/Accordion/Accordion.d.ts
7
7
  declare const validSpaceValues: readonly ["medium", "large", "xlarge"];
8
- interface AccordionProps {
8
+ /** Open item value, or `""` when none are open. */
9
+ type AccordionSingleValue = string | '';
10
+ type ControlledStateSingle = {
11
+ /**
12
+ * Allow more than one item to be open. Defaults to true.
13
+ * Note: Will default to false next major release.
14
+ */
15
+ multiple: false;
16
+ /**
17
+ * Value of the open AccordionItem, or `""` when none are open.
18
+ */
19
+ value: AccordionSingleValue;
20
+ onChange: (value: AccordionSingleValue) => void;
21
+ defaultValue?: never;
22
+ };
23
+ type ControlledStateMultiple = {
24
+ /**
25
+ * Allow more than one item to be open. Defaults to true.
26
+ * Note: Will default to false next major release.
27
+ */
28
+ multiple?: true;
29
+ /**
30
+ * Values of the open AccordionItems. Pass an empty array when none are open.
31
+ */
32
+ value: string[];
33
+ onChange: (value: string[]) => void;
34
+ defaultValue?: never;
35
+ };
36
+ type UncontrolledStateSingle = {
37
+ /**
38
+ * Allow more than one item to be open. Defaults to true.
39
+ * Note: Will default to false next major release.
40
+ */
41
+ multiple: false;
42
+ /**
43
+ * Value of the AccordionItem to expand by default.
44
+ */
45
+ defaultValue?: AccordionSingleValue;
46
+ value?: never;
47
+ onChange?: never;
48
+ };
49
+ type UncontrolledStateMultiple = {
50
+ /**
51
+ * Allow more than one item to be open. Defaults to true.
52
+ * Note: Will default to false next major release.
53
+ */
54
+ multiple?: true;
55
+ /**
56
+ * Values of the AccordionItems to expand by default.
57
+ */
58
+ defaultValue?: string[];
59
+ value?: never;
60
+ onChange?: never;
61
+ };
62
+ type AccordionProps = {
9
63
  children: ReactNodeNoStrings;
10
64
  dividers?: boolean;
11
65
  size?: AccordionContextValue['size'];
@@ -14,7 +68,7 @@ interface AccordionProps {
14
68
  /** @deprecated The spacing is now derived from the `size` prop and will be removed in a future release. */
15
69
  space?: RequiredResponsiveValue<(typeof validSpaceValues)[number]>;
16
70
  data?: DataAttributeMap;
17
- }
71
+ } & (ControlledStateSingle | ControlledStateMultiple | UncontrolledStateSingle | UncontrolledStateMultiple);
18
72
  declare const Accordion: FC<AccordionProps>;
19
73
  //#endregion
20
74
  export { Accordion, AccordionProps };
@@ -5,7 +5,61 @@ import { AccordionContextValue } from "./AccordionContext.mjs";
5
5
  import { FC } from "react";
6
6
  //#region src/lib/components/Accordion/Accordion.d.ts
7
7
  declare const validSpaceValues: readonly ["medium", "large", "xlarge"];
8
- interface AccordionProps {
8
+ /** Open item value, or `""` when none are open. */
9
+ type AccordionSingleValue = string | '';
10
+ type ControlledStateSingle = {
11
+ /**
12
+ * Allow more than one item to be open. Defaults to true.
13
+ * Note: Will default to false next major release.
14
+ */
15
+ multiple: false;
16
+ /**
17
+ * Value of the open AccordionItem, or `""` when none are open.
18
+ */
19
+ value: AccordionSingleValue;
20
+ onChange: (value: AccordionSingleValue) => void;
21
+ defaultValue?: never;
22
+ };
23
+ type ControlledStateMultiple = {
24
+ /**
25
+ * Allow more than one item to be open. Defaults to true.
26
+ * Note: Will default to false next major release.
27
+ */
28
+ multiple?: true;
29
+ /**
30
+ * Values of the open AccordionItems. Pass an empty array when none are open.
31
+ */
32
+ value: string[];
33
+ onChange: (value: string[]) => void;
34
+ defaultValue?: never;
35
+ };
36
+ type UncontrolledStateSingle = {
37
+ /**
38
+ * Allow more than one item to be open. Defaults to true.
39
+ * Note: Will default to false next major release.
40
+ */
41
+ multiple: false;
42
+ /**
43
+ * Value of the AccordionItem to expand by default.
44
+ */
45
+ defaultValue?: AccordionSingleValue;
46
+ value?: never;
47
+ onChange?: never;
48
+ };
49
+ type UncontrolledStateMultiple = {
50
+ /**
51
+ * Allow more than one item to be open. Defaults to true.
52
+ * Note: Will default to false next major release.
53
+ */
54
+ multiple?: true;
55
+ /**
56
+ * Values of the AccordionItems to expand by default.
57
+ */
58
+ defaultValue?: string[];
59
+ value?: never;
60
+ onChange?: never;
61
+ };
62
+ type AccordionProps = {
9
63
  children: ReactNodeNoStrings;
10
64
  dividers?: boolean;
11
65
  size?: AccordionContextValue['size'];
@@ -14,7 +68,7 @@ interface AccordionProps {
14
68
  /** @deprecated The spacing is now derived from the `size` prop and will be removed in a future release. */
15
69
  space?: RequiredResponsiveValue<(typeof validSpaceValues)[number]>;
16
70
  data?: DataAttributeMap;
17
- }
71
+ } & (ControlledStateSingle | ControlledStateMultiple | UncontrolledStateSingle | UncontrolledStateMultiple);
18
72
  declare const Accordion: FC<AccordionProps>;
19
73
  //#endregion
20
74
  export { Accordion, AccordionProps };
@@ -5,7 +5,7 @@ import { Divider } from "../Divider/Divider.mjs";
5
5
  import { Stack } from "../Stack/Stack.mjs";
6
6
  import { AccordionContext, validTones } from "./AccordionContext.mjs";
7
7
  import assert from "assert";
8
- import { Children, useMemo } from "react";
8
+ import { Children, isValidElement, useCallback, useMemo, useRef, useState } from "react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/lib/components/Accordion/Accordion.tsx
11
11
  const validSpaceValues = [
@@ -14,6 +14,13 @@ const validSpaceValues = [
14
14
  "xlarge"
15
15
  ];
16
16
  const defaultSize = "large";
17
+ const normalizeAccordionValue = (input, multiple) => {
18
+ if (input == null || input === "") return [];
19
+ const list = typeof input === "string" ? [input] : [...input];
20
+ assert(list.every((item) => typeof item === "string" && item.length > 0), "Accordion 'value' and 'defaultValue' must be a non-empty string or an array of non-empty strings.");
21
+ assert(multiple || list.length <= 1, "When 'multiple' is false, Accordion 'value' and 'defaultValue' must be a string or a single-item array.");
22
+ return list;
23
+ };
17
24
  const defaultSpaceForSize = {
18
25
  divided: {
19
26
  xsmall: "medium",
@@ -28,26 +35,67 @@ const defaultSpaceForSize = {
28
35
  large: "large"
29
36
  }
30
37
  };
31
- const Accordion = ({ children, size = defaultSize, tone, weight, space: spaceProp, dividers = true, data, ...restProps }) => {
32
- assert(spaceProp === void 0 || Object.values(normalizeResponsiveValue(spaceProp)).every((value) => value === void 0 || validSpaceValues.includes(value)), `To ensure adequate space for touch targets, 'space' prop values must be one of the following: ${validSpaceValues.map((x) => `"${x}"`).join(", ")}`);
38
+ const assertUniqueItemValues = (children) => {
39
+ const seen = /* @__PURE__ */ new Set();
40
+ for (const child of flattenChildren(children)) {
41
+ if (!isValidElement(child)) continue;
42
+ const itemValue = child.props.value;
43
+ if (typeof itemValue !== "string") continue;
44
+ assert(!seen.has(itemValue), `AccordionItem value "${itemValue}" is used more than once. Each AccordionItem value must be unique.`);
45
+ seen.add(itemValue);
46
+ }
47
+ };
48
+ const Accordion = (props) => {
49
+ const { children, size = defaultSize, tone, weight, multiple = true, value, defaultValue, space: spaceProp, dividers = true, data, ...restProps } = props;
50
+ assert(spaceProp === void 0 || Object.values(normalizeResponsiveValue(spaceProp)).every((spaceValue) => spaceValue === void 0 || validSpaceValues.includes(spaceValue)), `To ensure adequate space for touch targets, 'space' prop values must be one of the following: ${validSpaceValues.map((x) => `"${x}"`).join(", ")}`);
33
51
  assert(tone === void 0 || validTones.includes(tone), `The 'tone' prop should be one of the following: ${validTones.map((x) => `"${x}"`).join(", ")}`);
34
- if (process.env.NODE_ENV !== "production")
35
- /**
36
- * Validate that consumers are not passing `data-*`props,
37
- * which will not work and are not validated by TypeScript.
38
- */
39
- buildDataAttributes_default({
40
- data,
41
- validateRestProps: restProps
42
- });
52
+ assert(value === void 0 || defaultValue === void 0, "Accordion 'defaultValue' cannot be set when 'value' is set. Use 'value' to control the open items, or 'defaultValue' for the initial state.");
53
+ assert(value === void 0 || typeof props.onChange === "function", "Accordion 'onChange' must be set when 'value' is set.");
54
+ const managed = multiple === false || value !== void 0 || defaultValue !== void 0 || props.onChange !== void 0;
55
+ if (process.env.NODE_ENV !== "production") {
56
+ /**
57
+ * Validate that consumers are not passing `data-*`props,
58
+ * which will not work and are not validated by TypeScript.
59
+ */
60
+ buildDataAttributes_default({
61
+ data,
62
+ validateRestProps: restProps
63
+ });
64
+ assertUniqueItemValues(children);
65
+ }
66
+ const normalizedValue = useMemo(() => value === void 0 ? void 0 : normalizeAccordionValue(value, multiple), [multiple, value]);
67
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeAccordionValue(defaultValue, multiple));
68
+ const openValues = normalizedValue ?? uncontrolledValue;
69
+ const openValuesRef = useRef(openValues);
70
+ const propsRef = useRef(props);
71
+ openValuesRef.current = openValues;
72
+ propsRef.current = props;
73
+ const toggleValue = useCallback((itemValue) => {
74
+ const current = openValuesRef.current;
75
+ const isOpen = current.includes(itemValue);
76
+ let next;
77
+ if (!multiple) next = isOpen ? [] : [itemValue];
78
+ else if (isOpen) next = current.filter((openValue) => openValue !== itemValue);
79
+ else next = [...current, itemValue];
80
+ if (normalizedValue === void 0) setUncontrolledValue(next);
81
+ const p = propsRef.current;
82
+ if (p.multiple === false) p.onChange?.(next[0] ?? "");
83
+ else p.onChange?.(next);
84
+ }, [multiple, normalizedValue]);
43
85
  const contextValue = useMemo(() => ({
44
86
  size,
45
87
  tone,
46
- weight
88
+ weight,
89
+ managed,
90
+ openValues,
91
+ toggleValue
47
92
  }), [
48
93
  size,
49
94
  tone,
50
- weight
95
+ weight,
96
+ managed,
97
+ openValues,
98
+ toggleValue
51
99
  ]);
52
100
  const space = spaceProp ?? defaultSpaceForSize[dividers ? "divided" : "undivided"][size];
53
101
  return /* @__PURE__ */ jsx(AccordionContext.Provider, {
@@ -5,6 +5,13 @@ interface AccordionContextValue {
5
5
  size?: TextProps['size'];
6
6
  tone?: (typeof validTones)[number];
7
7
  weight?: TextProps['weight'];
8
+ /**
9
+ * True when Accordion owns the open items via `multiple={false}`,
10
+ * `value`, `defaultValue`, or `onChange`.
11
+ */
12
+ managed: boolean;
13
+ openValues: readonly string[];
14
+ toggleValue: (value: string) => void;
8
15
  }
9
16
  //#endregion
10
17
  export { AccordionContextValue, validTones };
@@ -5,6 +5,13 @@ interface AccordionContextValue {
5
5
  size?: TextProps['size'];
6
6
  tone?: (typeof validTones)[number];
7
7
  weight?: TextProps['weight'];
8
+ /**
9
+ * True when Accordion owns the open items via `multiple={false}`,
10
+ * `value`, `defaultValue`, or `onChange`.
11
+ */
12
+ managed: boolean;
13
+ openValues: readonly string[];
14
+ toggleValue: (value: string) => void;
8
15
  }
9
16
  //#endregion
10
17
  export { AccordionContextValue, validTones };
@@ -1,9 +1,9 @@
1
1
  const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
2
2
  const require_buildDataAttributes = require("../private/buildDataAttributes.cjs");
3
3
  const require_Box = require("../Box/Box.cjs");
4
- const require_Stack = require("../Stack/Stack.cjs");
5
4
  const require_AccordionContext = require("./AccordionContext.cjs");
6
5
  require("./Accordion.cjs");
6
+ const require_useFallbackId = require("../../hooks/useFallbackId.cjs");
7
7
  const require_useDisclosure = require("../Disclosure/useDisclosure.cjs");
8
8
  const require_Spread = require("../Spread/Spread.cjs");
9
9
  const require_Text = require("../Text/Text.cjs");
@@ -21,12 +21,21 @@ const itemSpaceForSize = {
21
21
  standard: "medium",
22
22
  large: "medium"
23
23
  };
24
- const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneProp, weight: weightProp, icon, data, ...restProps }) => {
24
+ const minDurationMs = 200;
25
+ const maxDurationMs = 500;
26
+ const pixelsPerSecond = 320;
27
+ const durationMsForHeight = (height) => Math.round(Math.min(maxDurationMs, Math.max(minDurationMs, height / pixelsPerSecond * 1e3)));
28
+ const prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
29
+ const isHeightTransition = (event) => event.propertyName === "height" && event.target === event.currentTarget;
30
+ const AccordionItem = ({ id, value, label, children, badge, size: sizeProp, tone: toneProp, weight: weightProp, icon, data, expanded: expandedProp, onToggle, ...restProps }) => {
25
31
  const accordionContext = (0, react.useContext)(require_AccordionContext.AccordionContext);
32
+ const managed = Boolean(accordionContext?.managed);
26
33
  (0, assert.default)(!(accordionContext && sizeProp), "Size cannot be set on AccordionItem when inside Accordion. Size should be set on Accordion instead.");
27
34
  (0, assert.default)(!(accordionContext && toneProp), "Tone cannot be set on AccordionItem when inside Accordion. Tone should be set on Accordion instead.");
28
35
  (0, assert.default)(!(accordionContext && weightProp), "Weight cannot be set on AccordionItem when inside Accordion. Weight should be set on Accordion instead.");
29
- (0, assert.default)(toneProp === void 0 || require_AccordionContext.validTones.includes(toneProp), `The 'tone' prop should be one of the following: ${require_AccordionContext.validTones.map((x) => `"${x}"`).join(", ")}`);
36
+ (0, assert.default)(toneProp === void 0 || require_AccordionContext.validTones.includes(toneProp), `The 'tone' prop should be one of the following: ${require_AccordionContext.validTones.map((tone) => `"${tone}"`).join(", ")}`);
37
+ (0, assert.default)(!managed || typeof value === "string" && value.length > 0, "AccordionItem 'value' must be a non-empty string when Accordion controls which items are open.");
38
+ (0, assert.default)(!(managed && expandedProp !== void 0), "expanded cannot be set on AccordionItem when Accordion controls which items are open. Use 'value' and 'onChange' on Accordion.");
30
39
  (0, assert.default)(!badge || badge.type.__isBadge__, `AccordionItem badge prop can only be an instance of Badge. e.g. <AccordionItem badge={<Badge>New</Badge>}>`);
31
40
  (0, assert.default)(!badge || badge.props.bleedY === void 0, "Badge elements cannot set the 'bleedY' prop when passed to an AccordionItem component");
32
41
  (0, assert.default)(!icon || icon.props.size === void 0 && icon.props.tone === void 0, "Icons cannot set the 'size' or 'tone' prop when passed to an AccordionItem component");
@@ -34,14 +43,64 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
34
43
  const tone = accordionContext?.tone ?? toneProp ?? "neutral";
35
44
  const weight = accordionContext?.weight ?? weightProp ?? "medium";
36
45
  const itemSpace = itemSpaceForSize[size] ?? "none";
46
+ const contentRef = (0, react.useRef)(null);
47
+ const contentSizeRef = (0, react.useRef)(null);
37
48
  (0, assert.default)(typeof label === "undefined" || typeof label === "string", "Label must be a string");
49
+ const resolvedId = require_useFallbackId.useFallbackId(id);
50
+ let disclosureState = { onToggle };
51
+ if (managed && accordionContext && value) disclosureState = {
52
+ expanded: accordionContext.openValues.includes(value),
53
+ onToggle: (nextExpanded) => {
54
+ accordionContext.toggleValue(value);
55
+ onToggle?.(nextExpanded);
56
+ }
57
+ };
58
+ else if (expandedProp !== void 0) disclosureState = {
59
+ expanded: expandedProp,
60
+ onToggle
61
+ };
38
62
  const { expanded, buttonProps, contentProps } = require_useDisclosure.useDisclosure({
39
- id,
40
- ...restProps.expanded !== void 0 ? {
41
- onToggle: restProps.onToggle,
42
- expanded: restProps.expanded
43
- } : { onToggle: restProps.onToggle }
63
+ id: resolvedId,
64
+ ...disclosureState
44
65
  });
66
+ const [trackedExpanded, setTrackedExpanded] = (0, react.useState)(expanded);
67
+ const [animatedHeight, setAnimatedHeight] = (0, react.useState)(null);
68
+ const [animationDurationMs, setAnimationDurationMs] = (0, react.useState)(minDurationMs);
69
+ const isAnimating = animatedHeight !== null;
70
+ if (expanded !== trackedExpanded) {
71
+ setTrackedExpanded(expanded);
72
+ if (prefersReducedMotion()) setAnimatedHeight(null);
73
+ else {
74
+ const height = contentSizeRef.current?.scrollHeight ?? 0;
75
+ setAnimatedHeight(height);
76
+ setAnimationDurationMs(durationMsForHeight(height));
77
+ }
78
+ }
79
+ const finishAnimation = (0, react.useCallback)(() => {
80
+ setAnimatedHeight(null);
81
+ }, []);
82
+ const finishHeightTransition = (0, react.useCallback)((event) => {
83
+ if (isHeightTransition(event)) finishAnimation();
84
+ }, [finishAnimation]);
85
+ (0, react.useLayoutEffect)(() => {
86
+ contentRef.current?.toggleAttribute("inert", !expanded);
87
+ }, [expanded]);
88
+ (0, react.useLayoutEffect)(() => {
89
+ if (!isAnimating) return;
90
+ if (!expanded) {
91
+ contentRef.current?.getBoundingClientRect();
92
+ setAnimatedHeight(0);
93
+ }
94
+ const timeoutId = window.setTimeout(finishAnimation, animationDurationMs + 50);
95
+ return () => {
96
+ window.clearTimeout(timeoutId);
97
+ };
98
+ }, [
99
+ animationDurationMs,
100
+ expanded,
101
+ finishAnimation,
102
+ isAnimating
103
+ ]);
45
104
  if (process.env.NODE_ENV !== "production")
46
105
  /**
47
106
  * Validate that consumers are not passing `data-*`props,
@@ -51,8 +110,7 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
51
110
  data,
52
111
  validateRestProps: restProps
53
112
  });
54
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_Stack.Stack, {
55
- space: itemSpace,
113
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_Box.Box, {
56
114
  data,
57
115
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Box.Box, {
58
116
  position: "relative",
@@ -91,9 +149,25 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
91
149
  })
92
150
  })
93
151
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Box.Box, {
94
- display: expanded ? "block" : "none",
152
+ className: {
153
+ [require_AccordionItem_css.content]: true,
154
+ [require_AccordionItem_css.contentOpen]: expanded && !isAnimating,
155
+ [require_AccordionItem_css.contentClosed]: !expanded && !isAnimating
156
+ },
157
+ style: isAnimating ? {
158
+ height: animatedHeight,
159
+ transitionDuration: `${animationDurationMs}ms`
160
+ } : void 0,
161
+ onTransitionEnd: finishHeightTransition,
162
+ onTransitionCancel: finishHeightTransition,
163
+ "aria-hidden": expanded ? void 0 : true,
164
+ ref: contentRef,
95
165
  ...contentProps,
96
- children
166
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Box.Box, {
167
+ ref: contentSizeRef,
168
+ paddingTop: itemSpace,
169
+ children
170
+ })
97
171
  })]
98
172
  });
99
173
  };
@@ -7,7 +7,22 @@ let _vanilla_extract_css = require("@vanilla-extract/css");
7
7
  (0, _vanilla_extract_css_fileScope.setFileScope)("src/lib/components/Accordion/AccordionItem.css.ts", "braid-design-system");
8
8
  const button = (0, _vanilla_extract_css.style)([{}, require_virtualTouchable_css.virtualTouchable], "button");
9
9
  const focusRing = (0, _vanilla_extract_css.style)([{ outlineOffset: require_vars_css.vars.space.xxsmall }, require_atoms.atoms({ borderRadius: "small" })], "focusRing");
10
+ const content = (0, _vanilla_extract_css.style)([require_atoms.atoms({ overflow: "hidden" }), {
11
+ transition: "height 200ms ease",
12
+ "@media": { "screen and (prefers-reduced-motion: reduce)": { transition: "none" } }
13
+ }], "content");
14
+ const contentOpen = (0, _vanilla_extract_css.style)([require_atoms.atoms({ overflow: "visible" }), {
15
+ height: "auto",
16
+ transition: "none"
17
+ }], "contentOpen");
18
+ const contentClosed = (0, _vanilla_extract_css.style)({
19
+ height: 0,
20
+ visibility: "hidden"
21
+ }, "contentClosed");
10
22
  (0, _vanilla_extract_css_fileScope.endFileScope)();
11
23
  //#endregion
12
24
  exports.button = button;
25
+ exports.content = content;
26
+ exports.contentClosed = contentClosed;
27
+ exports.contentOpen = contentOpen;
13
28
  exports.focusRing = focusRing;
@@ -7,6 +7,18 @@ import { style } from "@vanilla-extract/css";
7
7
  setFileScope("src/lib/components/Accordion/AccordionItem.css.ts", "braid-design-system");
8
8
  const button = style([{}, virtualTouchable], "button");
9
9
  const focusRing = style([{ outlineOffset: vars.space.xxsmall }, atoms({ borderRadius: "small" })], "focusRing");
10
+ const content = style([atoms({ overflow: "hidden" }), {
11
+ transition: "height 200ms ease",
12
+ "@media": { "screen and (prefers-reduced-motion: reduce)": { transition: "none" } }
13
+ }], "content");
14
+ const contentOpen = style([atoms({ overflow: "visible" }), {
15
+ height: "auto",
16
+ transition: "none"
17
+ }], "contentOpen");
18
+ const contentClosed = style({
19
+ height: 0,
20
+ visibility: "hidden"
21
+ }, "contentClosed");
10
22
  endFileScope();
11
23
  //#endregion
12
- export { button, focusRing };
24
+ export { button, content, contentClosed, contentOpen, focusRing };
@@ -14,6 +14,11 @@ interface AccordionItemBaseProps {
14
14
  icon?: TextProps['icon'];
15
15
  data?: DataAttributeMap;
16
16
  badge?: ReactElement<BadgeProps> | null;
17
+ /**
18
+ * Key used when Accordion owns the open items.
19
+ * Required when `multiple` is `false`, or when `value`, `defaultValue`, or `onChange` is set on Accordion.
20
+ */
21
+ value?: string;
17
22
  }
18
23
  type AccordionItemProps = AccordionItemBaseProps & UseDisclosureProps;
19
24
  type AccordionItemStateProps = DisclosureStateProps;
@@ -14,6 +14,11 @@ interface AccordionItemBaseProps {
14
14
  icon?: TextProps['icon'];
15
15
  data?: DataAttributeMap;
16
16
  badge?: ReactElement<BadgeProps> | null;
17
+ /**
18
+ * Key used when Accordion owns the open items.
19
+ * Required when `multiple` is `false`, or when `value`, `defaultValue`, or `onChange` is set on Accordion.
20
+ */
21
+ value?: string;
17
22
  }
18
23
  type AccordionItemProps = AccordionItemBaseProps & UseDisclosureProps;
19
24
  type AccordionItemStateProps = DisclosureStateProps;
@@ -1,16 +1,16 @@
1
1
  import buildDataAttributes_default from "../private/buildDataAttributes.mjs";
2
2
  import { Box } from "../Box/Box.mjs";
3
- import { Stack } from "../Stack/Stack.mjs";
4
3
  import { AccordionContext, validTones } from "./AccordionContext.mjs";
5
4
  import "./Accordion.mjs";
5
+ import { useFallbackId } from "../../hooks/useFallbackId.mjs";
6
6
  import { useDisclosure } from "../Disclosure/useDisclosure.mjs";
7
7
  import { Spread } from "../Spread/Spread.mjs";
8
8
  import { Text } from "../Text/Text.mjs";
9
9
  import { IconChevron } from "../icons/IconChevron/IconChevron.mjs";
10
10
  import { badgeSlotSpace } from "../private/badgeSlotSpace.mjs";
11
- import { button, focusRing } from "./AccordionItem.css.mjs";
11
+ import { button, content, contentClosed, contentOpen, focusRing } from "./AccordionItem.css.mjs";
12
12
  import assert from "assert";
13
- import { cloneElement, useContext } from "react";
13
+ import { cloneElement, useCallback, useContext, useLayoutEffect, useRef, useState } from "react";
14
14
  import { jsx, jsxs } from "react/jsx-runtime";
15
15
  //#region src/lib/components/Accordion/AccordionItem.tsx
16
16
  const itemSpaceForSize = {
@@ -19,12 +19,21 @@ const itemSpaceForSize = {
19
19
  standard: "medium",
20
20
  large: "medium"
21
21
  };
22
- const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneProp, weight: weightProp, icon, data, ...restProps }) => {
22
+ const minDurationMs = 200;
23
+ const maxDurationMs = 500;
24
+ const pixelsPerSecond = 320;
25
+ const durationMsForHeight = (height) => Math.round(Math.min(maxDurationMs, Math.max(minDurationMs, height / pixelsPerSecond * 1e3)));
26
+ const prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
27
+ const isHeightTransition = (event) => event.propertyName === "height" && event.target === event.currentTarget;
28
+ const AccordionItem = ({ id, value, label, children, badge, size: sizeProp, tone: toneProp, weight: weightProp, icon, data, expanded: expandedProp, onToggle, ...restProps }) => {
23
29
  const accordionContext = useContext(AccordionContext);
30
+ const managed = Boolean(accordionContext?.managed);
24
31
  assert(!(accordionContext && sizeProp), "Size cannot be set on AccordionItem when inside Accordion. Size should be set on Accordion instead.");
25
32
  assert(!(accordionContext && toneProp), "Tone cannot be set on AccordionItem when inside Accordion. Tone should be set on Accordion instead.");
26
33
  assert(!(accordionContext && weightProp), "Weight cannot be set on AccordionItem when inside Accordion. Weight should be set on Accordion instead.");
27
- assert(toneProp === void 0 || validTones.includes(toneProp), `The 'tone' prop should be one of the following: ${validTones.map((x) => `"${x}"`).join(", ")}`);
34
+ assert(toneProp === void 0 || validTones.includes(toneProp), `The 'tone' prop should be one of the following: ${validTones.map((tone) => `"${tone}"`).join(", ")}`);
35
+ assert(!managed || typeof value === "string" && value.length > 0, "AccordionItem 'value' must be a non-empty string when Accordion controls which items are open.");
36
+ assert(!(managed && expandedProp !== void 0), "expanded cannot be set on AccordionItem when Accordion controls which items are open. Use 'value' and 'onChange' on Accordion.");
28
37
  assert(!badge || badge.type.__isBadge__, `AccordionItem badge prop can only be an instance of Badge. e.g. <AccordionItem badge={<Badge>New</Badge>}>`);
29
38
  assert(!badge || badge.props.bleedY === void 0, "Badge elements cannot set the 'bleedY' prop when passed to an AccordionItem component");
30
39
  assert(!icon || icon.props.size === void 0 && icon.props.tone === void 0, "Icons cannot set the 'size' or 'tone' prop when passed to an AccordionItem component");
@@ -32,14 +41,64 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
32
41
  const tone = accordionContext?.tone ?? toneProp ?? "neutral";
33
42
  const weight = accordionContext?.weight ?? weightProp ?? "medium";
34
43
  const itemSpace = itemSpaceForSize[size] ?? "none";
44
+ const contentRef = useRef(null);
45
+ const contentSizeRef = useRef(null);
35
46
  assert(typeof label === "undefined" || typeof label === "string", "Label must be a string");
47
+ const resolvedId = useFallbackId(id);
48
+ let disclosureState = { onToggle };
49
+ if (managed && accordionContext && value) disclosureState = {
50
+ expanded: accordionContext.openValues.includes(value),
51
+ onToggle: (nextExpanded) => {
52
+ accordionContext.toggleValue(value);
53
+ onToggle?.(nextExpanded);
54
+ }
55
+ };
56
+ else if (expandedProp !== void 0) disclosureState = {
57
+ expanded: expandedProp,
58
+ onToggle
59
+ };
36
60
  const { expanded, buttonProps, contentProps } = useDisclosure({
37
- id,
38
- ...restProps.expanded !== void 0 ? {
39
- onToggle: restProps.onToggle,
40
- expanded: restProps.expanded
41
- } : { onToggle: restProps.onToggle }
61
+ id: resolvedId,
62
+ ...disclosureState
42
63
  });
64
+ const [trackedExpanded, setTrackedExpanded] = useState(expanded);
65
+ const [animatedHeight, setAnimatedHeight] = useState(null);
66
+ const [animationDurationMs, setAnimationDurationMs] = useState(minDurationMs);
67
+ const isAnimating = animatedHeight !== null;
68
+ if (expanded !== trackedExpanded) {
69
+ setTrackedExpanded(expanded);
70
+ if (prefersReducedMotion()) setAnimatedHeight(null);
71
+ else {
72
+ const height = contentSizeRef.current?.scrollHeight ?? 0;
73
+ setAnimatedHeight(height);
74
+ setAnimationDurationMs(durationMsForHeight(height));
75
+ }
76
+ }
77
+ const finishAnimation = useCallback(() => {
78
+ setAnimatedHeight(null);
79
+ }, []);
80
+ const finishHeightTransition = useCallback((event) => {
81
+ if (isHeightTransition(event)) finishAnimation();
82
+ }, [finishAnimation]);
83
+ useLayoutEffect(() => {
84
+ contentRef.current?.toggleAttribute("inert", !expanded);
85
+ }, [expanded]);
86
+ useLayoutEffect(() => {
87
+ if (!isAnimating) return;
88
+ if (!expanded) {
89
+ contentRef.current?.getBoundingClientRect();
90
+ setAnimatedHeight(0);
91
+ }
92
+ const timeoutId = window.setTimeout(finishAnimation, animationDurationMs + 50);
93
+ return () => {
94
+ window.clearTimeout(timeoutId);
95
+ };
96
+ }, [
97
+ animationDurationMs,
98
+ expanded,
99
+ finishAnimation,
100
+ isAnimating
101
+ ]);
43
102
  if (process.env.NODE_ENV !== "production")
44
103
  /**
45
104
  * Validate that consumers are not passing `data-*`props,
@@ -49,8 +108,7 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
49
108
  data,
50
109
  validateRestProps: restProps
51
110
  });
52
- return /* @__PURE__ */ jsxs(Stack, {
53
- space: itemSpace,
111
+ return /* @__PURE__ */ jsxs(Box, {
54
112
  data,
55
113
  children: [/* @__PURE__ */ jsx(Box, {
56
114
  position: "relative",
@@ -89,9 +147,25 @@ const AccordionItem = ({ id, label, children, badge, size: sizeProp, tone: toneP
89
147
  })
90
148
  })
91
149
  }), /* @__PURE__ */ jsx(Box, {
92
- display: expanded ? "block" : "none",
150
+ className: {
151
+ [content]: true,
152
+ [contentOpen]: expanded && !isAnimating,
153
+ [contentClosed]: !expanded && !isAnimating
154
+ },
155
+ style: isAnimating ? {
156
+ height: animatedHeight,
157
+ transitionDuration: `${animationDurationMs}ms`
158
+ } : void 0,
159
+ onTransitionEnd: finishHeightTransition,
160
+ onTransitionCancel: finishHeightTransition,
161
+ "aria-hidden": expanded ? void 0 : true,
162
+ ref: contentRef,
93
163
  ...contentProps,
94
- children
164
+ children: /* @__PURE__ */ jsx(Box, {
165
+ ref: contentSizeRef,
166
+ paddingTop: itemSpace,
167
+ children
168
+ })
95
169
  })]
96
170
  });
97
171
  };
@@ -1,12 +1,16 @@
1
+ const require_AccordionContext = require("./AccordionContext.cjs");
1
2
  const require_AccordionItem = require("./AccordionItem.cjs");
2
3
  const require_playroomState = require("../../playroom/playroomState.cjs");
4
+ let react = require("react");
3
5
  let react_jsx_runtime = require("react/jsx-runtime");
4
6
  //#region src/lib/components/Accordion/AccordionItem.playroom.tsx
5
- const AccordionItem = ({ label, stateName, expanded, onToggle, size, tone, weight, badge, icon, ...restProps }) => {
7
+ const AccordionItem = ({ label, stateName, expanded, onToggle, size, tone, weight, badge, icon, value, ...restProps }) => {
8
+ const managed = Boolean((0, react.useContext)(require_AccordionContext.AccordionContext)?.managed);
6
9
  const [state, handleChange] = require_playroomState.useFallbackState(stateName, expanded, onToggle, false);
7
10
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AccordionItem.AccordionItem, {
8
- expanded: state,
11
+ value: typeof value === "string" ? value : void 0,
9
12
  onToggle: handleChange,
13
+ expanded: managed ? void 0 : state,
10
14
  label: typeof label !== "boolean" ? label : "",
11
15
  size: typeof size === "boolean" ? void 0 : size,
12
16
  tone: typeof tone === "boolean" ? void 0 : tone,
@@ -3,7 +3,7 @@ import { StateProp } from "../../playroom/playroomState.cjs";
3
3
  import { FC } from "react";
4
4
  //#region src/lib/components/Accordion/AccordionItem.playroom.d.ts
5
5
  type OptionalProps = 'id';
6
- type PlayroomAccordionItemProps = StateProp & AccordionItemBaseProps & AccordionItemStateProps & Partial<Pick<AccordionItemProps, OptionalProps>>;
6
+ type PlayroomAccordionItemProps = StateProp & AccordionItemBaseProps & AccordionItemStateProps & Partial<Pick<AccordionItemProps, OptionalProps | 'value'>>;
7
7
  declare const AccordionItem: FC<PlayroomAccordionItemProps>;
8
8
  //#endregion
9
9
  export { AccordionItem };
@@ -3,7 +3,7 @@ import { StateProp } from "../../playroom/playroomState.mjs";
3
3
  import { FC } from "react";
4
4
  //#region src/lib/components/Accordion/AccordionItem.playroom.d.ts
5
5
  type OptionalProps = 'id';
6
- type PlayroomAccordionItemProps = StateProp & AccordionItemBaseProps & AccordionItemStateProps & Partial<Pick<AccordionItemProps, OptionalProps>>;
6
+ type PlayroomAccordionItemProps = StateProp & AccordionItemBaseProps & AccordionItemStateProps & Partial<Pick<AccordionItemProps, OptionalProps | 'value'>>;
7
7
  declare const AccordionItem: FC<PlayroomAccordionItemProps>;
8
8
  //#endregion
9
9
  export { AccordionItem };
@@ -1,12 +1,16 @@
1
+ import { AccordionContext } from "./AccordionContext.mjs";
1
2
  import { AccordionItem as AccordionItem$1 } from "./AccordionItem.mjs";
2
3
  import { useFallbackState } from "../../playroom/playroomState.mjs";
4
+ import { useContext } from "react";
3
5
  import { jsx } from "react/jsx-runtime";
4
6
  //#region src/lib/components/Accordion/AccordionItem.playroom.tsx
5
- const AccordionItem = ({ label, stateName, expanded, onToggle, size, tone, weight, badge, icon, ...restProps }) => {
7
+ const AccordionItem = ({ label, stateName, expanded, onToggle, size, tone, weight, badge, icon, value, ...restProps }) => {
8
+ const managed = Boolean(useContext(AccordionContext)?.managed);
6
9
  const [state, handleChange] = useFallbackState(stateName, expanded, onToggle, false);
7
10
  return /* @__PURE__ */ jsx(AccordionItem$1, {
8
- expanded: state,
11
+ value: typeof value === "string" ? value : void 0,
9
12
  onToggle: handleChange,
13
+ expanded: managed ? void 0 : state,
10
14
  label: typeof label !== "boolean" ? label : "",
11
15
  size: typeof size === "boolean" ? void 0 : size,
12
16
  tone: typeof tone === "boolean" ? void 0 : tone,
@@ -64,7 +64,6 @@ const reducer = (prevState, action) => {
64
64
  };
65
65
  const ANIMATION_DURATION = 300;
66
66
  const Modal = ({ id, open, children, description, onClose, width, closeLabel, illustration, title, "aria-label": ariaLabel, "aria-description": ariaDescription, headingLevel, position, ...restProps }) => {
67
- const [trapActive, setTrapActive] = (0, react.useState)(true);
68
67
  const [state, dispatch] = (0, react.useReducer)(reducer, INITIAL);
69
68
  const allowClose = (0, react.useContext)(AllowCloseContext);
70
69
  const shouldFocus = typeof document !== "undefined" && typeof document.hasFocus === "function" && document.hasFocus();
@@ -100,17 +99,8 @@ const Modal = ({ id, open, children, description, onClose, width, closeLabel, il
100
99
  (0, react.useEffect)(() => {
101
100
  if (typeof onClose === "function") closeHandlerRef.current = onClose;
102
101
  }, [onClose]);
103
- (0, react.useEffect)(() => {
104
- const event = trapActive ? "blur" : "focus";
105
- const handleEvent = () => setTrapActive(!trapActive);
106
- window.addEventListener(event, handleEvent);
107
- return () => {
108
- window.removeEventListener(event, handleEvent);
109
- };
110
- }, [trapActive]);
111
102
  return state === OPENING || state === OPEN || state === CLOSING ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalPortal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FocusLock, {
112
103
  className: require_Modal_css.resetStackingContext,
113
- disabled: !trapActive,
114
104
  autoFocus: false,
115
105
  onActivation: () => {
116
106
  if (state === OPEN) return;
@@ -62,7 +62,6 @@ const reducer = (prevState, action) => {
62
62
  };
63
63
  const ANIMATION_DURATION = 300;
64
64
  const Modal = ({ id, open, children, description, onClose, width, closeLabel, illustration, title, "aria-label": ariaLabel, "aria-description": ariaDescription, headingLevel, position, ...restProps }) => {
65
- const [trapActive, setTrapActive] = useState(true);
66
65
  const [state, dispatch] = useReducer(reducer, INITIAL);
67
66
  const allowClose = useContext(AllowCloseContext);
68
67
  const shouldFocus = typeof document !== "undefined" && typeof document.hasFocus === "function" && document.hasFocus();
@@ -98,17 +97,8 @@ const Modal = ({ id, open, children, description, onClose, width, closeLabel, il
98
97
  useEffect(() => {
99
98
  if (typeof onClose === "function") closeHandlerRef.current = onClose;
100
99
  }, [onClose]);
101
- useEffect(() => {
102
- const event = trapActive ? "blur" : "focus";
103
- const handleEvent = () => setTrapActive(!trapActive);
104
- window.addEventListener(event, handleEvent);
105
- return () => {
106
- window.removeEventListener(event, handleEvent);
107
- };
108
- }, [trapActive]);
109
100
  return state === OPENING || state === OPEN || state === CLOSING ? /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsxs(FocusLock, {
110
101
  className: resetStackingContext,
111
- disabled: !trapActive,
112
102
  autoFocus: false,
113
103
  onActivation: () => {
114
104
  if (state === OPEN) return;
@@ -25,7 +25,11 @@ const toneToIcon = {
25
25
  critical: require_IconCritical.IconCritical,
26
26
  positive: require_IconPositive.IconPositive
27
27
  };
28
- const toastDuration = 1e4;
28
+ const toastDuration = {
29
+ short: 6e3,
30
+ standard: 1e4,
31
+ indefinite: null
32
+ };
29
33
  const borderRadius = "large";
30
34
  const Action = ({ label, onClick, removeToast }) => {
31
35
  const handleClick = (0, react.useCallback)((event) => {
@@ -54,14 +58,15 @@ const ToastIcon = ({ tone, icon }) => {
54
58
  if (icon) return (0, react.cloneElement)(icon, { tone });
55
59
  return null;
56
60
  };
57
- const Toast = (0, react.forwardRef)(({ toastKey, dedupeKey, message, description, tone, icon, onClose, closeLabel = "Close", action, shouldRemove, data, expanded = true, ...restProps }, ref) => {
61
+ const Toast = (0, react.forwardRef)(({ toastKey, dedupeKey, message, duration = "standard", description, tone, icon, onClose, closeLabel = "Close", action, shouldRemove, data, expanded = true, ...restProps }, ref) => {
58
62
  const remove = (0, react.useCallback)(() => onClose(dedupeKey, toastKey), [
59
63
  onClose,
60
64
  dedupeKey,
61
65
  toastKey
62
66
  ]);
67
+ const resolvedDuration = toastDuration[duration];
63
68
  const { stopTimeout, startTimeout } = require_useTimeout.useTimeout({
64
- duration: toastDuration,
69
+ duration: resolvedDuration,
65
70
  onTimeout: remove
66
71
  });
67
72
  (0, react.useEffect)(() => {
@@ -23,7 +23,11 @@ const toneToIcon = {
23
23
  critical: IconCritical,
24
24
  positive: IconPositive
25
25
  };
26
- const toastDuration = 1e4;
26
+ const toastDuration = {
27
+ short: 6e3,
28
+ standard: 1e4,
29
+ indefinite: null
30
+ };
27
31
  const borderRadius = "large";
28
32
  const Action = ({ label, onClick, removeToast }) => {
29
33
  const handleClick = useCallback((event) => {
@@ -52,14 +56,15 @@ const ToastIcon = ({ tone, icon }) => {
52
56
  if (icon) return cloneElement(icon, { tone });
53
57
  return null;
54
58
  };
55
- const Toast = forwardRef(({ toastKey, dedupeKey, message, description, tone, icon, onClose, closeLabel = "Close", action, shouldRemove, data, expanded = true, ...restProps }, ref) => {
59
+ const Toast = forwardRef(({ toastKey, dedupeKey, message, duration = "standard", description, tone, icon, onClose, closeLabel = "Close", action, shouldRemove, data, expanded = true, ...restProps }, ref) => {
56
60
  const remove = useCallback(() => onClose(dedupeKey, toastKey), [
57
61
  onClose,
58
62
  dedupeKey,
59
63
  toastKey
60
64
  ]);
65
+ const resolvedDuration = toastDuration[duration];
61
66
  const { stopTimeout, startTimeout } = useTimeout({
62
- duration: toastDuration,
67
+ duration: resolvedDuration,
63
68
  onTimeout: remove
64
69
  });
65
70
  useEffect(() => {
@@ -6,6 +6,7 @@ interface ToastAction {
6
6
  label: string;
7
7
  onClick: () => void;
8
8
  }
9
+ type ToastDuration = 'short' | 'standard' | 'indefinite';
9
10
  type Toast = {
10
11
  key?: string;
11
12
  message: string;
@@ -13,6 +14,7 @@ type Toast = {
13
14
  action?: ToastAction;
14
15
  closeLabel?: string;
15
16
  data?: DataAttributeMap;
17
+ duration?: ToastDuration;
16
18
  } & ({
17
19
  tone: 'positive' | 'critical';
18
20
  } | {
@@ -20,4 +22,4 @@ type Toast = {
20
22
  icon?: ReactElement<UseIconProps>;
21
23
  });
22
24
  //#endregion
23
- export { Toast, ToastAction };
25
+ export { Toast, ToastAction, ToastDuration };
@@ -6,6 +6,7 @@ interface ToastAction {
6
6
  label: string;
7
7
  onClick: () => void;
8
8
  }
9
+ type ToastDuration = 'short' | 'standard' | 'indefinite';
9
10
  type Toast = {
10
11
  key?: string;
11
12
  message: string;
@@ -13,6 +14,7 @@ type Toast = {
13
14
  action?: ToastAction;
14
15
  closeLabel?: string;
15
16
  data?: DataAttributeMap;
17
+ duration?: ToastDuration;
16
18
  } & ({
17
19
  tone: 'positive' | 'critical';
18
20
  } | {
@@ -20,4 +22,4 @@ type Toast = {
20
22
  icon?: ReactElement<UseIconProps>;
21
23
  });
22
24
  //#endregion
23
- export { Toast, ToastAction };
25
+ export { Toast, ToastAction, ToastDuration };
@@ -154,11 +154,11 @@ const useFlipList = (expanded) => {
154
154
  property: "transform",
155
155
  from: transform
156
156
  },
157
- {
157
+ ...element.classList.contains(require_Toast_css.collapsed) ? [{
158
158
  property: "className",
159
- from: expanded ? require_Toast_css.collapsed : void 0,
159
+ from: require_Toast_css.collapsed,
160
160
  to: void 0
161
- }
161
+ }] : []
162
162
  ]
163
163
  });
164
164
  }
@@ -154,11 +154,11 @@ const useFlipList = (expanded) => {
154
154
  property: "transform",
155
155
  from: transform
156
156
  },
157
- {
157
+ ...element.classList.contains(collapsed) ? [{
158
158
  property: "className",
159
- from: expanded ? collapsed : void 0,
159
+ from: collapsed,
160
160
  to: void 0
161
- }
161
+ }] : []
162
162
  ]
163
163
  });
164
164
  }
@@ -8,7 +8,7 @@ const useTimeout = ({ onTimeout, duration }) => {
8
8
  setActivated(false);
9
9
  }, []);
10
10
  (0, react.useEffect)(() => {
11
- if (activated) {
11
+ if (activated && duration !== null) {
12
12
  timeoutRef.current = window.setTimeout(() => {
13
13
  onTimeout();
14
14
  }, duration);
@@ -8,7 +8,7 @@ const useTimeout = ({ onTimeout, duration }) => {
8
8
  setActivated(false);
9
9
  }, []);
10
10
  useEffect(() => {
11
- if (activated) {
11
+ if (activated && duration !== null) {
12
12
  timeoutRef.current = window.setTimeout(() => {
13
13
  onTimeout();
14
14
  }, duration);
@@ -4,7 +4,7 @@ import { ConditionalValue, RequiredConditionalValue } from "@vanilla-extract/spr
4
4
  declare const colorAtomicProperties: {
5
5
  conditions: {
6
6
  defaultCondition: "lightMode";
7
- conditionNames: ("darkMode" | "lightMode")[];
7
+ conditionNames: ("lightMode" | "darkMode")[];
8
8
  };
9
9
  styles: {
10
10
  readonly background: {
@@ -4,7 +4,7 @@ import { ConditionalValue, RequiredConditionalValue } from "@vanilla-extract/spr
4
4
  declare const colorAtomicProperties: {
5
5
  conditions: {
6
6
  defaultCondition: "lightMode";
7
- conditionNames: ("darkMode" | "lightMode")[];
7
+ conditionNames: ("lightMode" | "darkMode")[];
8
8
  };
9
9
  styles: {
10
10
  readonly background: {
@@ -2,31 +2,27 @@
2
2
  const snippets = [
3
3
  {
4
4
  description: "Large",
5
- code: "<Accordion>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
5
+ code: "<Accordion>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
6
6
  },
7
7
  {
8
8
  description: "Large, without dividers",
9
- code: "<Accordion dividers={false}>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
9
+ code: "<Accordion dividers={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
10
10
  },
11
11
  {
12
12
  description: "Standard",
13
- code: "<Accordion size=\"standard\">\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
13
+ code: "<Accordion size=\"standard\">\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
14
14
  },
15
15
  {
16
16
  description: "Standard, without dividers",
17
- code: "<Accordion size=\"standard\" dividers={false}>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
17
+ code: "<Accordion size=\"standard\" dividers={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
18
18
  },
19
19
  {
20
- description: "Large standalone item",
21
- code: "<AccordionItem label=\"Label\">\n <Placeholder height={100} />\n</AccordionItem>"
20
+ description: "Single open item",
21
+ code: "<Accordion multiple={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
22
22
  },
23
23
  {
24
- description: "Standard standalone item",
25
- code: "<AccordionItem label=\"Label\" size=\"standard\">\n <Placeholder height={100} />\n</AccordionItem>"
26
- },
27
- {
28
- description: "Standalone item with a badge",
29
- code: "<AccordionItem\n label=\"Label\"\n badge={\n <Badge tone=\"promote\" weight=\"strong\">\n Badge\n </Badge>\n }\n>\n <Placeholder height={100} />\n</AccordionItem>"
24
+ description: "Single open item with a default value",
25
+ code: "<Accordion multiple={false} defaultValue=\"item-1\">\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
30
26
  }
31
27
  ];
32
28
  //#endregion
@@ -2,31 +2,27 @@
2
2
  const snippets = [
3
3
  {
4
4
  description: "Large",
5
- code: "<Accordion>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
5
+ code: "<Accordion>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
6
6
  },
7
7
  {
8
8
  description: "Large, without dividers",
9
- code: "<Accordion dividers={false}>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
9
+ code: "<Accordion dividers={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
10
10
  },
11
11
  {
12
12
  description: "Standard",
13
- code: "<Accordion size=\"standard\">\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
13
+ code: "<Accordion size=\"standard\">\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
14
14
  },
15
15
  {
16
16
  description: "Standard, without dividers",
17
- code: "<Accordion size=\"standard\" dividers={false}>\n <AccordionItem label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
17
+ code: "<Accordion size=\"standard\" dividers={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
18
18
  },
19
19
  {
20
- description: "Large standalone item",
21
- code: "<AccordionItem label=\"Label\">\n <Placeholder height={100} />\n</AccordionItem>"
20
+ description: "Single open item",
21
+ code: "<Accordion multiple={false}>\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
22
22
  },
23
23
  {
24
- description: "Standard standalone item",
25
- code: "<AccordionItem label=\"Label\" size=\"standard\">\n <Placeholder height={100} />\n</AccordionItem>"
26
- },
27
- {
28
- description: "Standalone item with a badge",
29
- code: "<AccordionItem\n label=\"Label\"\n badge={\n <Badge tone=\"promote\" weight=\"strong\">\n Badge\n </Badge>\n }\n>\n <Placeholder height={100} />\n</AccordionItem>"
24
+ description: "Single open item with a default value",
25
+ code: "<Accordion multiple={false} defaultValue=\"item-1\">\n <AccordionItem value=\"item-1\" label=\"Item 1\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-2\" label=\"Item 2\">\n <Placeholder height={100} />\n </AccordionItem>\n <AccordionItem value=\"item-3\" label=\"Item 3\">\n <Placeholder height={100} />\n </AccordionItem>\n</Accordion>"
30
26
  }
31
27
  ];
32
28
  //#endregion
@@ -1,2 +1 @@
1
- import FrameComponent_default from "../lib/playroom/FrameComponent.mjs";
2
- export { FrameComponent_default as default };
1
+ export { default } from "../lib/playroom/FrameComponent.mjs";
@@ -1,2 +1 @@
1
- import useScope from "../lib/playroom/useScope.mjs";
2
- export { useScope as default };
1
+ export { default } from "../lib/playroom/useScope.mjs";
@@ -1,2 +1 @@
1
- import allSnippets from "../lib/playroom/snippets.mjs";
2
- export { allSnippets as default };
1
+ export { default } from "../lib/playroom/snippets.mjs";
package/dist/test.mjs CHANGED
@@ -1,2 +1 @@
1
- import { BraidTestProvider } from "./lib/components/BraidTestProvider/BraidTestProvider.mjs";
2
- export { BraidTestProvider };
1
+ export { BraidTestProvider } from "./lib/components/BraidTestProvider/BraidTestProvider.mjs";
@@ -1,2 +1 @@
1
- import docsTheme_css_default from "../lib/themes/docs/docsTheme.css.mjs";
2
- export { docsTheme_css_default as default };
1
+ export { default } from "../lib/themes/docs/docsTheme.css.mjs";
@@ -1,2 +1 @@
1
- import seekBusinessTheme_css_default from "../lib/themes/seekBusiness/seekBusinessTheme.css.mjs";
2
- export { seekBusinessTheme_css_default as default };
1
+ export { default } from "../lib/themes/seekBusiness/seekBusinessTheme.css.mjs";
@@ -1,2 +1 @@
1
- import seekJobs_css_default from "../lib/themes/seekJobs/seekJobs.css.mjs";
2
- export { seekJobs_css_default as default };
1
+ export { default } from "../lib/themes/seekJobs/seekJobs.css.mjs";
@@ -1,2 +1 @@
1
- import wireframeTheme_css_default from "../lib/themes/wireframe/wireframeTheme.css.mjs";
2
- export { wireframeTheme_css_default as default };
1
+ export { default } from "../lib/themes/wireframe/wireframeTheme.css.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-design-system",
3
- "version": "34.9.1",
3
+ "version": "34.10.0",
4
4
  "description": "Themeable design system for the SEEK Group",
5
5
  "homepage": "https://seek-oss.github.io/braid-design-system/",
6
6
  "bugs": {