braid-design-system 34.9.0 → 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.
- package/CHANGELOG.md +62 -0
- package/dist/color-mode/query-param.d.cts +1 -1
- package/dist/color-mode/query-param.d.mts +1 -1
- package/dist/lib/components/Accordion/Accordion.cjs +61 -13
- package/dist/lib/components/Accordion/Accordion.d.cts +56 -2
- package/dist/lib/components/Accordion/Accordion.d.mts +56 -2
- package/dist/lib/components/Accordion/Accordion.mjs +62 -14
- package/dist/lib/components/Accordion/AccordionContext.d.cts +7 -0
- package/dist/lib/components/Accordion/AccordionContext.d.mts +7 -0
- package/dist/lib/components/Accordion/AccordionItem.cjs +86 -12
- package/dist/lib/components/Accordion/AccordionItem.css.cjs +15 -0
- package/dist/lib/components/Accordion/AccordionItem.css.mjs +13 -1
- package/dist/lib/components/Accordion/AccordionItem.d.cts +5 -0
- package/dist/lib/components/Accordion/AccordionItem.d.mts +5 -0
- package/dist/lib/components/Accordion/AccordionItem.mjs +88 -14
- package/dist/lib/components/Accordion/AccordionItem.playroom.cjs +6 -2
- package/dist/lib/components/Accordion/AccordionItem.playroom.d.cts +1 -1
- package/dist/lib/components/Accordion/AccordionItem.playroom.d.mts +1 -1
- package/dist/lib/components/Accordion/AccordionItem.playroom.mjs +6 -2
- package/dist/lib/components/Avatar/Avatar.cjs +1 -1
- package/dist/lib/components/Avatar/Avatar.css.cjs +2 -1
- package/dist/lib/components/Avatar/Avatar.css.mjs +2 -1
- package/dist/lib/components/Avatar/Avatar.mjs +1 -1
- package/dist/lib/components/BraidPortal/BraidPortal.cjs +0 -1
- package/dist/lib/components/BraidPortal/BraidPortal.mjs +0 -1
- package/dist/lib/components/BraidProvider/BraidProvider.cjs +0 -1
- package/dist/lib/components/BraidProvider/BraidProvider.mjs +0 -1
- package/dist/lib/components/BraidProvider/VanillaThemeContainer.cjs +2 -2
- package/dist/lib/components/BraidProvider/VanillaThemeContainer.mjs +2 -2
- package/dist/lib/components/Drawer/Drawer.cjs +5 -0
- package/dist/lib/components/Drawer/Drawer.mjs +5 -0
- package/dist/lib/components/private/Modal/Modal.cjs +0 -10
- package/dist/lib/components/private/Modal/Modal.mjs +0 -10
- package/dist/lib/components/private/Modal/ModalContent.cjs +9 -4
- package/dist/lib/components/private/Modal/ModalContent.mjs +9 -4
- package/dist/lib/components/useToast/Toast.cjs +8 -3
- package/dist/lib/components/useToast/Toast.mjs +8 -3
- package/dist/lib/components/useToast/ToastTypes.d.cts +3 -1
- package/dist/lib/components/useToast/ToastTypes.d.mts +3 -1
- package/dist/lib/components/useToast/useFlipList.cjs +3 -3
- package/dist/lib/components/useToast/useFlipList.mjs +3 -3
- package/dist/lib/components/useToast/useTimeout.cjs +1 -1
- package/dist/lib/components/useToast/useTimeout.mjs +1 -1
- package/dist/lib/playroom/snippets/Accordion.cjs +8 -12
- package/dist/lib/playroom/snippets/Accordion.mjs +8 -12
- package/dist/lib/playroom/snippets/BulkActions.cjs +8 -0
- package/dist/lib/playroom/snippets/BulkActions.mjs +8 -0
- package/dist/lib/playroom/snippets/DividedLists.cjs +8 -0
- package/dist/lib/playroom/snippets/DividedLists.mjs +8 -0
- package/dist/lib/playroom/snippets/EmptyState.cjs +12 -0
- package/dist/lib/playroom/snippets/EmptyState.mjs +12 -0
- package/dist/lib/playroom/snippets/ErrorState.cjs +12 -0
- package/dist/lib/playroom/snippets/ErrorState.mjs +12 -0
- package/dist/lib/playroom/snippets/Forms.cjs +12 -0
- package/dist/lib/playroom/snippets/Forms.mjs +12 -0
- package/dist/lib/playroom/snippets/Nudge.cjs +20 -0
- package/dist/lib/playroom/snippets/Nudge.mjs +20 -0
- package/dist/lib/playroom/snippets/ServiceOutageBanner.cjs +12 -0
- package/dist/lib/playroom/snippets/ServiceOutageBanner.mjs +12 -0
- package/dist/lib/playroom/snippets/SkeletonLoader.cjs +8 -0
- package/dist/lib/playroom/snippets/SkeletonLoader.mjs +8 -0
- package/dist/lib/playroom/snippets/SocialShare.cjs +8 -0
- package/dist/lib/playroom/snippets/SocialShare.mjs +8 -0
- package/dist/lib/playroom/snippets.cjs +20 -1
- package/dist/lib/playroom/snippets.mjs +116 -97
- package/dist/playroom/FrameComponent.mjs +1 -2
- package/dist/playroom/components.d.cts +5 -0
- package/dist/playroom/components.d.mts +5 -0
- package/dist/playroom/scope.mjs +1 -2
- package/dist/playroom/snippets.mjs +1 -2
- package/dist/test.mjs +1 -2
- package/dist/themes/docs.mjs +1 -2
- package/dist/themes/seekBusiness.mjs +1 -2
- package/dist/themes/seekJobs.mjs +1 -2
- package/dist/themes/wireframe.mjs +1 -2
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
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
|
+
|
|
43
|
+
## 34.9.1
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- **Drawer:** Reject empty `title` ([#2171](https://github.com/seek-oss/braid-design-system/pull/2171))
|
|
48
|
+
|
|
49
|
+
An empty `title` is now treated as invalid, consistent with `aria-label`. Provide a non-empty `title`, or use `aria-label` for an untitled `Drawer`.
|
|
50
|
+
|
|
51
|
+
- **Avatar:** Ensure loading background fills completely in dark mode ([#2169](https://github.com/seek-oss/braid-design-system/pull/2169))
|
|
52
|
+
|
|
53
|
+
- playroom: Add Patterns group to snippets ([#2167](https://github.com/seek-oss/braid-design-system/pull/2167))
|
|
54
|
+
|
|
55
|
+
- **Avatar:** Prevent layout collapse inside Inline component ([#2169](https://github.com/seek-oss/braid-design-system/pull/2169))
|
|
56
|
+
|
|
57
|
+
- **BraidProvider:** Ensure default text tone follows the theme ([#2163](https://github.com/seek-oss/braid-design-system/pull/2163))
|
|
58
|
+
|
|
59
|
+
Nested `BraidProvider` components using different themes now always apply the default text tones according to the selected theme.
|
|
60
|
+
|
|
61
|
+
- **Dialog:** Restore header rendering when `title` is empty ([#2171](https://github.com/seek-oss/braid-design-system/pull/2171))
|
|
62
|
+
|
|
63
|
+
Fixes a regression where a `Dialog` with an empty `title` no longer rendered its `illustration` or `description`.
|
|
64
|
+
|
|
3
65
|
## 34.9.0
|
|
4
66
|
|
|
5
67
|
### Minor Changes
|
|
@@ -43,6 +43,6 @@ declare const colorModeQueryParamCheck: string;
|
|
|
43
43
|
*
|
|
44
44
|
* @returns Braid color mode preference as query string parameter
|
|
45
45
|
*/
|
|
46
|
-
declare const getColorModeQueryParam: () => "" | "_bdsdm=1" | "_bdsdm=2" | "
|
|
46
|
+
declare const getColorModeQueryParam: () => "_bdsdm=0" | "_bdsdm=1" | "_bdsdm=2" | "";
|
|
47
47
|
//#endregion
|
|
48
48
|
export { colorModeQueryParamCheck, getColorModeQueryParam };
|
|
@@ -43,6 +43,6 @@ declare const colorModeQueryParamCheck: string;
|
|
|
43
43
|
*
|
|
44
44
|
* @returns Braid color mode preference as query string parameter
|
|
45
45
|
*/
|
|
46
|
-
declare const getColorModeQueryParam: () => "" | "_bdsdm=1" | "_bdsdm=2" | "
|
|
46
|
+
declare const getColorModeQueryParam: () => "_bdsdm=0" | "_bdsdm=1" | "_bdsdm=2" | "";
|
|
47
47
|
//#endregion
|
|
48
48
|
export { colorModeQueryParamCheck, getColorModeQueryParam };
|
|
@@ -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
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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((
|
|
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
|
-
...
|
|
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)(
|
|
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
|
-
|
|
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;
|