@sproutsocial/seeds-react-collapsible 1.0.12 → 2.0.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.
@@ -8,14 +8,14 @@ $ tsup --dts
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 5.92 KB
12
- CJS dist/index.js.map 8.98 KB
13
- CJS ⚡️ Build success in 294ms
14
- ESM dist/esm/index.js 3.92 KB
15
- ESM dist/esm/index.js.map 8.89 KB
16
- ESM ⚡️ Build success in 279ms
11
+ ESM dist/esm/index.js 1.79 KB
12
+ ESM dist/esm/index.js.map 3.85 KB
13
+ ESM ⚡️ Build success in 174ms
14
+ CJS dist/index.js 3.57 KB
15
+ CJS dist/index.js.map 3.91 KB
16
+ CJS ⚡️ Build success in 174ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 27923ms
19
- DTS dist/index.d.ts 1.28 KB
20
- DTS dist/index.d.mts 1.28 KB
21
- Done in 35.71s.
18
+ DTS ⚡️ Build success in 27032ms
19
+ DTS dist/index.d.ts 1.38 KB
20
+ DTS dist/index.d.mts 1.38 KB
21
+ Done in 37.56s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @sproutsocial/seeds-react-collapsible
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - b9c5376: Migrate Collapsible to Radix Collapsible
8
+
9
+ In-place replacement of the custom implementation with `@radix-ui/react-collapsible`. Removes `offset`, `collapsedHeight` (0 usages), and `openHeight` (1 usage in `UserPermissionsDrawer`). Adds `disabled`, `defaultOpen`, `onOpenChange`, and `forceMount` props.
10
+
11
+ ## 1.0.13
12
+
13
+ ### Patch Changes
14
+
15
+ - @sproutsocial/seeds-react-box@1.1.13
16
+ - @sproutsocial/seeds-react-hooks@3.1.5
17
+
3
18
  ## 1.0.12
4
19
 
5
20
  ### Patch Changes
package/dist/esm/index.js CHANGED
@@ -1,133 +1,72 @@
1
1
  // src/Collapsible.tsx
2
- import * as React from "react";
3
- import { useState, useRef, useContext, useEffect } from "react";
4
- import { useMeasure } from "@sproutsocial/seeds-react-hooks";
5
- import Box2 from "@sproutsocial/seeds-react-box";
2
+ import "react";
3
+ import * as RadixCollapsible2 from "@radix-ui/react-collapsible";
6
4
 
7
5
  // src/styles.ts
8
- import styled from "styled-components";
9
- import Box from "@sproutsocial/seeds-react-box";
10
- var CollapsingBox = styled(Box)`
11
- transition: max-height ${(p) => p.theme.duration.medium}
12
- ${(p) => p.theme.easing.ease_inout};
13
- will-change: max-height;
14
- position: relative;
15
- overflow: auto;
16
- ${({ hasShadow, scrollable }) => hasShadow ? `background: /* Shadow covers */ linear-gradient(
17
- transparent 30%,
18
- rgba(255, 255, 255, 0)
19
- ),
20
- linear-gradient(rgba(255, 255, 255, 0), transparent 70%) 0 100%,
21
- /* Shadows */
22
- radial-gradient(
23
- farthest-side at 50% 0,
24
- rgb(39 51 51 / 5%),
25
- rgba(0, 0, 0, 0)
26
- ),
27
- radial-gradient(
28
- farthest-side at 50% 100%,
29
- rgb(39 51 51 / 5%),
30
- rgba(0, 0, 0, 0)
31
- )
32
- 0 100%;
33
- background-repeat: no-repeat;
34
- background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
35
- background-attachment: local, local, scroll, scroll;
36
- ${scrollable ? `overflow: auto` : `overflow: hidden`};` : ""}
6
+ import styled, { css } from "styled-components";
7
+ import * as RadixCollapsible from "@radix-ui/react-collapsible";
8
+ var animations = css`
9
+ @keyframes collapsibleSlideDown {
10
+ from {
11
+ height: 0;
12
+ }
13
+ to {
14
+ height: var(--radix-collapsible-content-height);
15
+ }
16
+ }
17
+
18
+ @keyframes collapsibleSlideUp {
19
+ from {
20
+ height: var(--radix-collapsible-content-height);
21
+ }
22
+ to {
23
+ height: 0;
24
+ }
25
+ }
26
+ `;
27
+ var StyledCollapsibleContent = styled(RadixCollapsible.Content)`
28
+ ${animations}
29
+
30
+ overflow: hidden;
31
+
32
+ &[data-state="open"] {
33
+ animation: collapsibleSlideDown 300ms ease-in-out;
34
+ }
35
+
36
+ &[data-state="closed"] {
37
+ animation: collapsibleSlideUp 300ms ease-in-out;
38
+ }
37
39
  `;
38
40
 
39
41
  // src/Collapsible.tsx
40
42
  import { jsx } from "react/jsx-runtime";
41
- var idCounter = 0;
42
- var CollapsibleContext = React.createContext({});
43
43
  var Collapsible = ({
44
44
  children,
45
45
  isOpen = false,
46
- offset = 0,
47
- collapsedHeight = 0,
48
- openHeight
46
+ disabled,
47
+ defaultOpen,
48
+ onOpenChange
49
49
  }) => {
50
- const [id] = useState(`Racine-collapsible-${idCounter++}`);
51
50
  return /* @__PURE__ */ jsx(
52
- CollapsibleContext.Provider,
51
+ RadixCollapsible2.Root,
53
52
  {
54
- value: {
55
- isOpen,
56
- id,
57
- offset,
58
- collapsedHeight,
59
- openHeight
60
- },
53
+ open: isOpen,
54
+ disabled,
55
+ defaultOpen,
56
+ onOpenChange,
61
57
  children
62
58
  }
63
59
  );
64
60
  };
65
- var determineMaxHeight = (isHidden, openHeight, computedHeight) => {
66
- if (isHidden === void 0) return void 0;
67
- if (openHeight) return openHeight;
68
- return computedHeight;
69
- };
70
- var Trigger = ({ children, ...rest }) => {
71
- const { isOpen, id } = useContext(CollapsibleContext);
72
- return /* @__PURE__ */ jsx(React.Fragment, { children: React.cloneElement(children, {
73
- "aria-controls": id,
74
- "aria-expanded": !!isOpen,
75
- ...rest
76
- }) });
61
+ var Trigger2 = ({ children }) => {
62
+ return /* @__PURE__ */ jsx(RadixCollapsible2.Trigger, { asChild: true, children });
77
63
  };
78
- Trigger.displayName = "Collapsible.Trigger";
79
- var Panel = ({ children, ...rest }) => {
80
- const {
81
- isOpen,
82
- id,
83
- offset = 0,
84
- collapsedHeight,
85
- openHeight
86
- } = useContext(CollapsibleContext);
87
- const ref = useRef(null);
88
- const measurement = useMeasure(ref);
89
- const [isHidden, setIsHidden] = useState(void 0);
90
- const maxHeight = determineMaxHeight(
91
- isHidden,
92
- openHeight,
93
- // Round up to the nearest pixel to prevent subpixel rendering issues
94
- Math.ceil(measurement.height + offset)
95
- );
96
- useEffect(() => {
97
- if (!isOpen) {
98
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 300);
99
- return () => clearTimeout(timeoutID);
100
- } else {
101
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 0);
102
- return () => clearTimeout(timeoutID);
103
- }
104
- }, [isOpen]);
105
- return /* @__PURE__ */ jsx(
106
- CollapsingBox,
107
- {
108
- hasShadow: Boolean(collapsedHeight || openHeight && openHeight > 0),
109
- scrollable: isOpen,
110
- maxHeight: isOpen ? maxHeight : collapsedHeight,
111
- minHeight: collapsedHeight,
112
- "data-qa-collapsible": "",
113
- "data-qa-collapsible-isopen": isOpen === true,
114
- ...rest,
115
- children: /* @__PURE__ */ jsx(
116
- Box2,
117
- {
118
- width: "100%",
119
- hidden: isHidden && collapsedHeight === 0,
120
- "aria-hidden": !isOpen,
121
- id,
122
- ref,
123
- children
124
- }
125
- )
126
- }
127
- );
64
+ Trigger2.displayName = "Collapsible.Trigger";
65
+ var Panel = ({ children, forceMount }) => {
66
+ return /* @__PURE__ */ jsx(StyledCollapsibleContent, { forceMount, "data-qa-collapsible": "", children });
128
67
  };
129
68
  Panel.displayName = "Collapsible.Panel";
130
- Collapsible.Trigger = Trigger;
69
+ Collapsible.Trigger = Trigger2;
131
70
  Collapsible.Panel = Panel;
132
71
  var Collapsible_default = Collapsible;
133
72
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Collapsible.tsx","../../src/styles.ts","../../src/CollapsibleTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useRef, useContext, useEffect } from \"react\";\nimport { useMeasure } from \"@sproutsocial/seeds-react-hooks\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport { CollapsingBox } from \"./styles\";\nimport type {\n TypeCollapsibleProps,\n TypeCollapsiblePanelProps,\n TypeCollapsibleTriggerProps,\n} from \"./CollapsibleTypes\";\n\nlet idCounter = 0;\n\ninterface TypeCollapsibleContext {\n isOpen?: boolean;\n id?: string;\n offset?: number;\n openHeight?: number;\n collapsedHeight?: number;\n}\n\nconst CollapsibleContext = React.createContext<TypeCollapsibleContext>({});\n\nconst Collapsible = ({\n children,\n isOpen = false,\n offset = 0,\n collapsedHeight = 0,\n openHeight,\n}: TypeCollapsibleProps) => {\n const [id] = useState(`Racine-collapsible-${idCounter++}`);\n return (\n <CollapsibleContext.Provider\n value={{\n isOpen,\n id,\n offset,\n collapsedHeight,\n openHeight,\n }}\n >\n {children}\n </CollapsibleContext.Provider>\n );\n};\n\nconst determineMaxHeight = (\n isHidden?: boolean,\n openHeight?: number,\n computedHeight?: number\n): number | undefined => {\n // If isHidden is undefined this is the first render. Return undefined so the max-height prop is not added\n // This is a hack to prevent css from animating if it begins in the open state\n // css animates when attribute values change (IE from 0 to another number)\n // css does not animate when simply adding an attribute to an HTML element\n if (isHidden === undefined) return undefined;\n // If the user has defined an explicit open height, return that as the max height\n if (openHeight) return openHeight;\n // Otherwise, fallback to the computed height\n return computedHeight;\n};\n\nconst Trigger = ({ children, ...rest }: TypeCollapsibleTriggerProps) => {\n const { isOpen, id } = useContext(CollapsibleContext);\n return (\n <React.Fragment>\n {React.cloneElement(children, {\n \"aria-controls\": id,\n \"aria-expanded\": !!isOpen,\n ...rest,\n })}\n </React.Fragment>\n );\n};\n\nTrigger.displayName = \"Collapsible.Trigger\";\n\nconst Panel = ({ children, ...rest }: TypeCollapsiblePanelProps) => {\n const {\n isOpen,\n id,\n offset = 0,\n collapsedHeight,\n openHeight,\n } = useContext(CollapsibleContext);\n\n const ref = useRef<HTMLDivElement | null>(null);\n const measurement = useMeasure(ref);\n const [isHidden, setIsHidden] = useState<boolean | undefined>(undefined);\n const maxHeight = determineMaxHeight(\n isHidden,\n openHeight,\n // Round up to the nearest pixel to prevent subpixel rendering issues\n Math.ceil(measurement.height + offset)\n );\n\n /* We use the \"hidden\" attribute to remove the contents of the panel from the tab order of the page, but it interferes with the animation. This logic sets a slight timeout on setting the prop so that the animation has time to complete before the attribute is set. */\n useEffect(() => {\n if (!isOpen) {\n const timeoutID = setTimeout(() => setIsHidden(!isOpen), 300);\n return () => clearTimeout(timeoutID);\n } else {\n // Similar to the close animation, we need to delay setting hidden to run slightly async.\n // An issue occurs with the initial render isHidden logic that causes the animation to occur sporadically.\n // using this 0 second timeout just allows this component to initially render with an undefined max height,\n // Then go directly from undefined to the full max height, without a brief 0 value that triggers an animation\n const timeoutID = setTimeout(() => setIsHidden(!isOpen), 0);\n return () => clearTimeout(timeoutID);\n }\n }, [isOpen]);\n\n return (\n <CollapsingBox\n hasShadow={Boolean(collapsedHeight || (openHeight && openHeight > 0))}\n scrollable={isOpen}\n maxHeight={isOpen ? maxHeight : collapsedHeight}\n minHeight={collapsedHeight}\n data-qa-collapsible=\"\"\n data-qa-collapsible-isopen={isOpen === true}\n {...rest}\n >\n <Box\n width=\"100%\"\n hidden={isHidden && collapsedHeight === 0}\n aria-hidden={!isOpen}\n id={id}\n ref={ref}\n >\n {children}\n </Box>\n </CollapsingBox>\n );\n};\n\nPanel.displayName = \"Collapsible.Panel\";\n\nCollapsible.Trigger = Trigger;\nCollapsible.Panel = Panel;\n\nexport default Collapsible;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const CollapsingBox = styled(Box)<{\n hasShadow?: boolean;\n scrollable?: boolean;\n}>`\n transition: max-height ${(p) => p.theme.duration.medium}\n ${(p) => p.theme.easing.ease_inout};\n will-change: max-height;\n position: relative;\n overflow: auto;\n ${({ hasShadow, scrollable }) =>\n hasShadow\n ? `background: /* Shadow covers */ linear-gradient(\n transparent 30%,\n rgba(255, 255, 255, 0)\n ),\n linear-gradient(rgba(255, 255, 255, 0), transparent 70%) 0 100%,\n /* Shadows */\n radial-gradient(\n farthest-side at 50% 0,\n rgb(39 51 51 / 5%),\n rgba(0, 0, 0, 0)\n ),\n radial-gradient(\n farthest-side at 50% 100%,\n rgb(39 51 51 / 5%),\n rgba(0, 0, 0, 0)\n )\n 0 100%;\n background-repeat: no-repeat;\n background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;\n background-attachment: local, local, scroll, scroll;\n ${scrollable ? `overflow: auto` : `overflow: hidden`};`\n : \"\"}\n`;\n","import * as React from \"react\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\n// The flow type is inexact but the underlying component does not accept any other props.\n// It might be worth extending the box props here for the refactor, but allowing it would provide no functionality right now.\nexport interface TypeCollapsibleProps {\n isOpen: boolean;\n children: React.ReactNode;\n\n /** If the children of the collapsible panel have a top or bottom margin, it will throw off the calculations for the height of the content. The total amount of vertical margin (in pixels) can be supplied to this prop to correct this. */\n offset?: number;\n collapsedHeight?: number;\n openHeight?: number;\n}\n\nexport interface TypeCollapsibleTriggerProps\n extends Omit<TypeBoxProps, \"children\"> {\n // Children is required for the Trigger\n children: React.ReactElement;\n}\n\nexport interface TypeCollapsiblePanelProps extends TypeBoxProps {}\n","import Collapsible from \"./Collapsible\";\n\nexport default Collapsible;\nexport { Collapsible };\nexport * from \"./CollapsibleTypes\";\n"],"mappings":"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","names":["Box","Box"]}
1
+ {"version":3,"sources":["../../src/Collapsible.tsx","../../src/styles.ts","../../src/CollapsibleTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\nimport { StyledCollapsibleContent } from \"./styles\";\nimport type {\n TypeCollapsibleProps,\n TypeCollapsiblePanelProps,\n TypeCollapsibleTriggerProps,\n} from \"./CollapsibleTypes\";\n\nconst Collapsible = ({\n children,\n isOpen = false,\n disabled,\n defaultOpen,\n onOpenChange,\n}: TypeCollapsibleProps) => {\n return (\n <RadixCollapsible.Root\n open={isOpen}\n disabled={disabled}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n >\n {children}\n </RadixCollapsible.Root>\n );\n};\n\nconst Trigger = ({ children }: TypeCollapsibleTriggerProps) => {\n return (\n <RadixCollapsible.Trigger asChild>{children}</RadixCollapsible.Trigger>\n );\n};\n\nTrigger.displayName = \"Collapsible.Trigger\";\n\nconst Panel = ({ children, forceMount }: TypeCollapsiblePanelProps) => {\n return (\n <StyledCollapsibleContent forceMount={forceMount} data-qa-collapsible=\"\">\n {children}\n </StyledCollapsibleContent>\n );\n};\n\nPanel.displayName = \"Collapsible.Panel\";\n\nCollapsible.Trigger = Trigger;\nCollapsible.Panel = Panel;\n\nexport default Collapsible;\n","import styled, { css } from \"styled-components\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\n\nconst animations = css`\n @keyframes collapsibleSlideDown {\n from {\n height: 0;\n }\n to {\n height: var(--radix-collapsible-content-height);\n }\n }\n\n @keyframes collapsibleSlideUp {\n from {\n height: var(--radix-collapsible-content-height);\n }\n to {\n height: 0;\n }\n }\n`;\n\nexport const StyledCollapsibleContent = styled(RadixCollapsible.Content)`\n ${animations}\n\n overflow: hidden;\n\n &[data-state=\"open\"] {\n animation: collapsibleSlideDown 300ms ease-in-out;\n }\n\n &[data-state=\"closed\"] {\n animation: collapsibleSlideUp 300ms ease-in-out;\n }\n`;\n","import * as React from \"react\";\n\nexport interface TypeCollapsibleProps {\n isOpen: boolean;\n children: React.ReactNode;\n\n /** Whether the collapsible is disabled */\n disabled?: boolean;\n\n /** The open state when initially rendered. Use when you don't need controlled state. */\n defaultOpen?: boolean;\n\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface TypeCollapsibleTriggerProps {\n /** A single React element that will be used as the trigger via Radix asChild */\n children: React.ReactElement;\n}\n\nexport interface TypeCollapsiblePanelProps {\n children?: React.ReactNode;\n\n /** Force mounting the content when closed. Note: when enabled, content stays in the DOM but animations are not supported. */\n forceMount?: true;\n}\n","import Collapsible from \"./Collapsible\";\n\nexport default Collapsible;\nexport { Collapsible };\nexport * from \"./CollapsibleTypes\";\n"],"mappings":";AAAA,OAAuB;AACvB,YAAYA,uBAAsB;;;ACDlC,OAAO,UAAU,WAAW;AAC5B,YAAY,sBAAsB;AAElC,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBZ,IAAM,2BAA2B,OAAwB,wBAAO;AAAA,IACnE,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ADPV;AARJ,IAAM,cAAc,CAAC;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AACF,MAA4B;AAC1B,SACE;AAAA,IAAkB;AAAA,IAAjB;AAAA,MACC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEA,IAAMC,WAAU,CAAC,EAAE,SAAS,MAAmC;AAC7D,SACE,oBAAkB,2BAAjB,EAAyB,SAAO,MAAE,UAAS;AAEhD;AAEAA,SAAQ,cAAc;AAEtB,IAAM,QAAQ,CAAC,EAAE,UAAU,WAAW,MAAiC;AACrE,SACE,oBAAC,4BAAyB,YAAwB,uBAAoB,IACnE,UACH;AAEJ;AAEA,MAAM,cAAc;AAEpB,YAAY,UAAUA;AACtB,YAAY,QAAQ;AAEpB,IAAO,sBAAQ;;;AEjDf,OAAuB;;;ACEvB,IAAO,gBAAQ;","names":["RadixCollapsible","Trigger"]}
package/dist/index.d.mts CHANGED
@@ -1,29 +1,34 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { TypeBoxProps } from '@sproutsocial/seeds-react-box';
4
3
 
5
4
  interface TypeCollapsibleProps {
6
5
  isOpen: boolean;
7
6
  children: React.ReactNode;
8
- /** If the children of the collapsible panel have a top or bottom margin, it will throw off the calculations for the height of the content. The total amount of vertical margin (in pixels) can be supplied to this prop to correct this. */
9
- offset?: number;
10
- collapsedHeight?: number;
11
- openHeight?: number;
7
+ /** Whether the collapsible is disabled */
8
+ disabled?: boolean;
9
+ /** The open state when initially rendered. Use when you don't need controlled state. */
10
+ defaultOpen?: boolean;
11
+ /** Called when the open state changes */
12
+ onOpenChange?: (open: boolean) => void;
12
13
  }
13
- interface TypeCollapsibleTriggerProps extends Omit<TypeBoxProps, "children"> {
14
+ interface TypeCollapsibleTriggerProps {
15
+ /** A single React element that will be used as the trigger via Radix asChild */
14
16
  children: React.ReactElement;
15
17
  }
16
- interface TypeCollapsiblePanelProps extends TypeBoxProps {
18
+ interface TypeCollapsiblePanelProps {
19
+ children?: React.ReactNode;
20
+ /** Force mounting the content when closed. Note: when enabled, content stays in the DOM but animations are not supported. */
21
+ forceMount?: true;
17
22
  }
18
23
 
19
24
  declare const Collapsible: {
20
- ({ children, isOpen, offset, collapsedHeight, openHeight, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
25
+ ({ children, isOpen, disabled, defaultOpen, onOpenChange, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
21
26
  Trigger: {
22
- ({ children, ...rest }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
27
+ ({ children }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
23
28
  displayName: string;
24
29
  };
25
30
  Panel: {
26
- ({ children, ...rest }: TypeCollapsiblePanelProps): react_jsx_runtime.JSX.Element;
31
+ ({ children, forceMount }: TypeCollapsiblePanelProps): react_jsx_runtime.JSX.Element;
27
32
  displayName: string;
28
33
  };
29
34
  };
package/dist/index.d.ts CHANGED
@@ -1,29 +1,34 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { TypeBoxProps } from '@sproutsocial/seeds-react-box';
4
3
 
5
4
  interface TypeCollapsibleProps {
6
5
  isOpen: boolean;
7
6
  children: React.ReactNode;
8
- /** If the children of the collapsible panel have a top or bottom margin, it will throw off the calculations for the height of the content. The total amount of vertical margin (in pixels) can be supplied to this prop to correct this. */
9
- offset?: number;
10
- collapsedHeight?: number;
11
- openHeight?: number;
7
+ /** Whether the collapsible is disabled */
8
+ disabled?: boolean;
9
+ /** The open state when initially rendered. Use when you don't need controlled state. */
10
+ defaultOpen?: boolean;
11
+ /** Called when the open state changes */
12
+ onOpenChange?: (open: boolean) => void;
12
13
  }
13
- interface TypeCollapsibleTriggerProps extends Omit<TypeBoxProps, "children"> {
14
+ interface TypeCollapsibleTriggerProps {
15
+ /** A single React element that will be used as the trigger via Radix asChild */
14
16
  children: React.ReactElement;
15
17
  }
16
- interface TypeCollapsiblePanelProps extends TypeBoxProps {
18
+ interface TypeCollapsiblePanelProps {
19
+ children?: React.ReactNode;
20
+ /** Force mounting the content when closed. Note: when enabled, content stays in the DOM but animations are not supported. */
21
+ forceMount?: true;
17
22
  }
18
23
 
19
24
  declare const Collapsible: {
20
- ({ children, isOpen, offset, collapsedHeight, openHeight, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
25
+ ({ children, isOpen, disabled, defaultOpen, onOpenChange, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
21
26
  Trigger: {
22
- ({ children, ...rest }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
27
+ ({ children }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
23
28
  displayName: string;
24
29
  };
25
30
  Panel: {
26
- ({ children, ...rest }: TypeCollapsiblePanelProps): react_jsx_runtime.JSX.Element;
31
+ ({ children, forceMount }: TypeCollapsiblePanelProps): react_jsx_runtime.JSX.Element;
27
32
  displayName: string;
28
33
  };
29
34
  };
package/dist/index.js CHANGED
@@ -36,135 +36,74 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/Collapsible.tsx
39
- var React = __toESM(require("react"));
40
- var import_react = require("react");
41
- var import_seeds_react_hooks = require("@sproutsocial/seeds-react-hooks");
42
- var import_seeds_react_box2 = __toESM(require("@sproutsocial/seeds-react-box"));
39
+ var React = require("react");
40
+ var RadixCollapsible2 = __toESM(require("@radix-ui/react-collapsible"));
43
41
 
44
42
  // src/styles.ts
45
43
  var import_styled_components = __toESM(require("styled-components"));
46
- var import_seeds_react_box = __toESM(require("@sproutsocial/seeds-react-box"));
47
- var CollapsingBox = (0, import_styled_components.default)(import_seeds_react_box.default)`
48
- transition: max-height ${(p) => p.theme.duration.medium}
49
- ${(p) => p.theme.easing.ease_inout};
50
- will-change: max-height;
51
- position: relative;
52
- overflow: auto;
53
- ${({ hasShadow, scrollable }) => hasShadow ? `background: /* Shadow covers */ linear-gradient(
54
- transparent 30%,
55
- rgba(255, 255, 255, 0)
56
- ),
57
- linear-gradient(rgba(255, 255, 255, 0), transparent 70%) 0 100%,
58
- /* Shadows */
59
- radial-gradient(
60
- farthest-side at 50% 0,
61
- rgb(39 51 51 / 5%),
62
- rgba(0, 0, 0, 0)
63
- ),
64
- radial-gradient(
65
- farthest-side at 50% 100%,
66
- rgb(39 51 51 / 5%),
67
- rgba(0, 0, 0, 0)
68
- )
69
- 0 100%;
70
- background-repeat: no-repeat;
71
- background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
72
- background-attachment: local, local, scroll, scroll;
73
- ${scrollable ? `overflow: auto` : `overflow: hidden`};` : ""}
44
+ var RadixCollapsible = __toESM(require("@radix-ui/react-collapsible"));
45
+ var animations = import_styled_components.css`
46
+ @keyframes collapsibleSlideDown {
47
+ from {
48
+ height: 0;
49
+ }
50
+ to {
51
+ height: var(--radix-collapsible-content-height);
52
+ }
53
+ }
54
+
55
+ @keyframes collapsibleSlideUp {
56
+ from {
57
+ height: var(--radix-collapsible-content-height);
58
+ }
59
+ to {
60
+ height: 0;
61
+ }
62
+ }
63
+ `;
64
+ var StyledCollapsibleContent = (0, import_styled_components.default)(RadixCollapsible.Content)`
65
+ ${animations}
66
+
67
+ overflow: hidden;
68
+
69
+ &[data-state="open"] {
70
+ animation: collapsibleSlideDown 300ms ease-in-out;
71
+ }
72
+
73
+ &[data-state="closed"] {
74
+ animation: collapsibleSlideUp 300ms ease-in-out;
75
+ }
74
76
  `;
75
77
 
76
78
  // src/Collapsible.tsx
77
79
  var import_jsx_runtime = require("react/jsx-runtime");
78
- var idCounter = 0;
79
- var CollapsibleContext = React.createContext({});
80
80
  var Collapsible = ({
81
81
  children,
82
82
  isOpen = false,
83
- offset = 0,
84
- collapsedHeight = 0,
85
- openHeight
83
+ disabled,
84
+ defaultOpen,
85
+ onOpenChange
86
86
  }) => {
87
- const [id] = (0, import_react.useState)(`Racine-collapsible-${idCounter++}`);
88
87
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
89
- CollapsibleContext.Provider,
88
+ RadixCollapsible2.Root,
90
89
  {
91
- value: {
92
- isOpen,
93
- id,
94
- offset,
95
- collapsedHeight,
96
- openHeight
97
- },
90
+ open: isOpen,
91
+ disabled,
92
+ defaultOpen,
93
+ onOpenChange,
98
94
  children
99
95
  }
100
96
  );
101
97
  };
102
- var determineMaxHeight = (isHidden, openHeight, computedHeight) => {
103
- if (isHidden === void 0) return void 0;
104
- if (openHeight) return openHeight;
105
- return computedHeight;
98
+ var Trigger2 = ({ children }) => {
99
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixCollapsible2.Trigger, { asChild: true, children });
106
100
  };
107
- var Trigger = ({ children, ...rest }) => {
108
- const { isOpen, id } = (0, import_react.useContext)(CollapsibleContext);
109
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(React.Fragment, { children: React.cloneElement(children, {
110
- "aria-controls": id,
111
- "aria-expanded": !!isOpen,
112
- ...rest
113
- }) });
114
- };
115
- Trigger.displayName = "Collapsible.Trigger";
116
- var Panel = ({ children, ...rest }) => {
117
- const {
118
- isOpen,
119
- id,
120
- offset = 0,
121
- collapsedHeight,
122
- openHeight
123
- } = (0, import_react.useContext)(CollapsibleContext);
124
- const ref = (0, import_react.useRef)(null);
125
- const measurement = (0, import_seeds_react_hooks.useMeasure)(ref);
126
- const [isHidden, setIsHidden] = (0, import_react.useState)(void 0);
127
- const maxHeight = determineMaxHeight(
128
- isHidden,
129
- openHeight,
130
- // Round up to the nearest pixel to prevent subpixel rendering issues
131
- Math.ceil(measurement.height + offset)
132
- );
133
- (0, import_react.useEffect)(() => {
134
- if (!isOpen) {
135
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 300);
136
- return () => clearTimeout(timeoutID);
137
- } else {
138
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 0);
139
- return () => clearTimeout(timeoutID);
140
- }
141
- }, [isOpen]);
142
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
143
- CollapsingBox,
144
- {
145
- hasShadow: Boolean(collapsedHeight || openHeight && openHeight > 0),
146
- scrollable: isOpen,
147
- maxHeight: isOpen ? maxHeight : collapsedHeight,
148
- minHeight: collapsedHeight,
149
- "data-qa-collapsible": "",
150
- "data-qa-collapsible-isopen": isOpen === true,
151
- ...rest,
152
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
153
- import_seeds_react_box2.default,
154
- {
155
- width: "100%",
156
- hidden: isHidden && collapsedHeight === 0,
157
- "aria-hidden": !isOpen,
158
- id,
159
- ref,
160
- children
161
- }
162
- )
163
- }
164
- );
101
+ Trigger2.displayName = "Collapsible.Trigger";
102
+ var Panel = ({ children, forceMount }) => {
103
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StyledCollapsibleContent, { forceMount, "data-qa-collapsible": "", children });
165
104
  };
166
105
  Panel.displayName = "Collapsible.Panel";
167
- Collapsible.Trigger = Trigger;
106
+ Collapsible.Trigger = Trigger2;
168
107
  Collapsible.Panel = Panel;
169
108
  var Collapsible_default = Collapsible;
170
109
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Collapsible.tsx","../src/styles.ts","../src/CollapsibleTypes.ts"],"sourcesContent":["import Collapsible from \"./Collapsible\";\n\nexport default Collapsible;\nexport { Collapsible };\nexport * from \"./CollapsibleTypes\";\n","import * as React from \"react\";\nimport { useState, useRef, useContext, useEffect } from \"react\";\nimport { useMeasure } from \"@sproutsocial/seeds-react-hooks\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport { CollapsingBox } from \"./styles\";\nimport type {\n TypeCollapsibleProps,\n TypeCollapsiblePanelProps,\n TypeCollapsibleTriggerProps,\n} from \"./CollapsibleTypes\";\n\nlet idCounter = 0;\n\ninterface TypeCollapsibleContext {\n isOpen?: boolean;\n id?: string;\n offset?: number;\n openHeight?: number;\n collapsedHeight?: number;\n}\n\nconst CollapsibleContext = React.createContext<TypeCollapsibleContext>({});\n\nconst Collapsible = ({\n children,\n isOpen = false,\n offset = 0,\n collapsedHeight = 0,\n openHeight,\n}: TypeCollapsibleProps) => {\n const [id] = useState(`Racine-collapsible-${idCounter++}`);\n return (\n <CollapsibleContext.Provider\n value={{\n isOpen,\n id,\n offset,\n collapsedHeight,\n openHeight,\n }}\n >\n {children}\n </CollapsibleContext.Provider>\n );\n};\n\nconst determineMaxHeight = (\n isHidden?: boolean,\n openHeight?: number,\n computedHeight?: number\n): number | undefined => {\n // If isHidden is undefined this is the first render. Return undefined so the max-height prop is not added\n // This is a hack to prevent css from animating if it begins in the open state\n // css animates when attribute values change (IE from 0 to another number)\n // css does not animate when simply adding an attribute to an HTML element\n if (isHidden === undefined) return undefined;\n // If the user has defined an explicit open height, return that as the max height\n if (openHeight) return openHeight;\n // Otherwise, fallback to the computed height\n return computedHeight;\n};\n\nconst Trigger = ({ children, ...rest }: TypeCollapsibleTriggerProps) => {\n const { isOpen, id } = useContext(CollapsibleContext);\n return (\n <React.Fragment>\n {React.cloneElement(children, {\n \"aria-controls\": id,\n \"aria-expanded\": !!isOpen,\n ...rest,\n })}\n </React.Fragment>\n );\n};\n\nTrigger.displayName = \"Collapsible.Trigger\";\n\nconst Panel = ({ children, ...rest }: TypeCollapsiblePanelProps) => {\n const {\n isOpen,\n id,\n offset = 0,\n collapsedHeight,\n openHeight,\n } = useContext(CollapsibleContext);\n\n const ref = useRef<HTMLDivElement | null>(null);\n const measurement = useMeasure(ref);\n const [isHidden, setIsHidden] = useState<boolean | undefined>(undefined);\n const maxHeight = determineMaxHeight(\n isHidden,\n openHeight,\n // Round up to the nearest pixel to prevent subpixel rendering issues\n Math.ceil(measurement.height + offset)\n );\n\n /* We use the \"hidden\" attribute to remove the contents of the panel from the tab order of the page, but it interferes with the animation. This logic sets a slight timeout on setting the prop so that the animation has time to complete before the attribute is set. */\n useEffect(() => {\n if (!isOpen) {\n const timeoutID = setTimeout(() => setIsHidden(!isOpen), 300);\n return () => clearTimeout(timeoutID);\n } else {\n // Similar to the close animation, we need to delay setting hidden to run slightly async.\n // An issue occurs with the initial render isHidden logic that causes the animation to occur sporadically.\n // using this 0 second timeout just allows this component to initially render with an undefined max height,\n // Then go directly from undefined to the full max height, without a brief 0 value that triggers an animation\n const timeoutID = setTimeout(() => setIsHidden(!isOpen), 0);\n return () => clearTimeout(timeoutID);\n }\n }, [isOpen]);\n\n return (\n <CollapsingBox\n hasShadow={Boolean(collapsedHeight || (openHeight && openHeight > 0))}\n scrollable={isOpen}\n maxHeight={isOpen ? maxHeight : collapsedHeight}\n minHeight={collapsedHeight}\n data-qa-collapsible=\"\"\n data-qa-collapsible-isopen={isOpen === true}\n {...rest}\n >\n <Box\n width=\"100%\"\n hidden={isHidden && collapsedHeight === 0}\n aria-hidden={!isOpen}\n id={id}\n ref={ref}\n >\n {children}\n </Box>\n </CollapsingBox>\n );\n};\n\nPanel.displayName = \"Collapsible.Panel\";\n\nCollapsible.Trigger = Trigger;\nCollapsible.Panel = Panel;\n\nexport default Collapsible;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const CollapsingBox = styled(Box)<{\n hasShadow?: boolean;\n scrollable?: boolean;\n}>`\n transition: max-height ${(p) => p.theme.duration.medium}\n ${(p) => p.theme.easing.ease_inout};\n will-change: max-height;\n position: relative;\n overflow: auto;\n ${({ hasShadow, scrollable }) =>\n hasShadow\n ? `background: /* Shadow covers */ linear-gradient(\n transparent 30%,\n rgba(255, 255, 255, 0)\n ),\n linear-gradient(rgba(255, 255, 255, 0), transparent 70%) 0 100%,\n /* Shadows */\n radial-gradient(\n farthest-side at 50% 0,\n rgb(39 51 51 / 5%),\n rgba(0, 0, 0, 0)\n ),\n radial-gradient(\n farthest-side at 50% 100%,\n rgb(39 51 51 / 5%),\n rgba(0, 0, 0, 0)\n )\n 0 100%;\n background-repeat: no-repeat;\n background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;\n background-attachment: local, local, scroll, scroll;\n ${scrollable ? `overflow: auto` : `overflow: hidden`};`\n : \"\"}\n`;\n","import * as React from \"react\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\n// The flow type is inexact but the underlying component does not accept any other props.\n// It might be worth extending the box props here for the refactor, but allowing it would provide no functionality right now.\nexport interface TypeCollapsibleProps {\n isOpen: boolean;\n children: React.ReactNode;\n\n /** If the children of the collapsible panel have a top or bottom margin, it will throw off the calculations for the height of the content. The total amount of vertical margin (in pixels) can be supplied to this prop to correct this. */\n offset?: number;\n collapsedHeight?: number;\n openHeight?: number;\n}\n\nexport interface TypeCollapsibleTriggerProps\n extends Omit<TypeBoxProps, \"children\"> {\n // Children is required for the Trigger\n children: React.ReactElement;\n}\n\nexport interface TypeCollapsiblePanelProps extends TypeBoxProps {}\n"],"mappings":"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","names":["import_seeds_react_box","styled","Box","Box","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Collapsible.tsx","../src/styles.ts","../src/CollapsibleTypes.ts"],"sourcesContent":["import Collapsible from \"./Collapsible\";\n\nexport default Collapsible;\nexport { Collapsible };\nexport * from \"./CollapsibleTypes\";\n","import * as React from \"react\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\nimport { StyledCollapsibleContent } from \"./styles\";\nimport type {\n TypeCollapsibleProps,\n TypeCollapsiblePanelProps,\n TypeCollapsibleTriggerProps,\n} from \"./CollapsibleTypes\";\n\nconst Collapsible = ({\n children,\n isOpen = false,\n disabled,\n defaultOpen,\n onOpenChange,\n}: TypeCollapsibleProps) => {\n return (\n <RadixCollapsible.Root\n open={isOpen}\n disabled={disabled}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n >\n {children}\n </RadixCollapsible.Root>\n );\n};\n\nconst Trigger = ({ children }: TypeCollapsibleTriggerProps) => {\n return (\n <RadixCollapsible.Trigger asChild>{children}</RadixCollapsible.Trigger>\n );\n};\n\nTrigger.displayName = \"Collapsible.Trigger\";\n\nconst Panel = ({ children, forceMount }: TypeCollapsiblePanelProps) => {\n return (\n <StyledCollapsibleContent forceMount={forceMount} data-qa-collapsible=\"\">\n {children}\n </StyledCollapsibleContent>\n );\n};\n\nPanel.displayName = \"Collapsible.Panel\";\n\nCollapsible.Trigger = Trigger;\nCollapsible.Panel = Panel;\n\nexport default Collapsible;\n","import styled, { css } from \"styled-components\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\n\nconst animations = css`\n @keyframes collapsibleSlideDown {\n from {\n height: 0;\n }\n to {\n height: var(--radix-collapsible-content-height);\n }\n }\n\n @keyframes collapsibleSlideUp {\n from {\n height: var(--radix-collapsible-content-height);\n }\n to {\n height: 0;\n }\n }\n`;\n\nexport const StyledCollapsibleContent = styled(RadixCollapsible.Content)`\n ${animations}\n\n overflow: hidden;\n\n &[data-state=\"open\"] {\n animation: collapsibleSlideDown 300ms ease-in-out;\n }\n\n &[data-state=\"closed\"] {\n animation: collapsibleSlideUp 300ms ease-in-out;\n }\n`;\n","import * as React from \"react\";\n\nexport interface TypeCollapsibleProps {\n isOpen: boolean;\n children: React.ReactNode;\n\n /** Whether the collapsible is disabled */\n disabled?: boolean;\n\n /** The open state when initially rendered. Use when you don't need controlled state. */\n defaultOpen?: boolean;\n\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface TypeCollapsibleTriggerProps {\n /** A single React element that will be used as the trigger via Radix asChild */\n children: React.ReactElement;\n}\n\nexport interface TypeCollapsiblePanelProps {\n children?: React.ReactNode;\n\n /** Force mounting the content when closed. Note: when enabled, content stays in the DOM but animations are not supported. */\n forceMount?: true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;AACvB,IAAAA,oBAAkC;;;ACDlC,+BAA4B;AAC5B,uBAAkC;AAElC,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBZ,IAAM,+BAA2B,yBAAAC,SAAwB,wBAAO;AAAA,IACnE,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ADPV;AARJ,IAAM,cAAc,CAAC;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AACF,MAA4B;AAC1B,SACE;AAAA,IAAkB;AAAA,IAAjB;AAAA,MACC,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEA,IAAMC,WAAU,CAAC,EAAE,SAAS,MAAmC;AAC7D,SACE,4CAAkB,2BAAjB,EAAyB,SAAO,MAAE,UAAS;AAEhD;AAEAA,SAAQ,cAAc;AAEtB,IAAM,QAAQ,CAAC,EAAE,UAAU,WAAW,MAAiC;AACrE,SACE,4CAAC,4BAAyB,YAAwB,uBAAoB,IACnE,UACH;AAEJ;AAEA,MAAM,cAAc;AAEpB,YAAY,UAAUA;AACtB,YAAY,QAAQ;AAEpB,IAAO,sBAAQ;;;AEjDf,IAAAC,SAAuB;;;AHEvB,IAAO,gBAAQ;","names":["RadixCollapsible","styled","Trigger","React"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-collapsible",
3
- "version": "1.0.12",
3
+ "version": "2.0.0",
4
4
  "description": "Seeds React Collapsible",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -18,8 +18,7 @@
18
18
  "test:watch": "jest --watch --coverage=false"
19
19
  },
20
20
  "dependencies": {
21
- "@sproutsocial/seeds-react-box": "^1.1.12",
22
- "@sproutsocial/seeds-react-hooks": "^3.1.4"
21
+ "@radix-ui/react-collapsible": "^1.1.7"
23
22
  },
24
23
  "devDependencies": {
25
24
  "tsup": "^8.3.4",
@@ -31,7 +30,8 @@
31
30
  "@sproutsocial/seeds-tsconfig": "*",
32
31
  "@sproutsocial/seeds-testing": "*",
33
32
  "@sproutsocial/seeds-react-testing-library": "*",
34
- "@sproutsocial/seeds-react-button": "^1.3.14"
33
+ "@sproutsocial/seeds-react-button": "^1.3.18",
34
+ "@sproutsocial/seeds-react-box": "^1.1.13"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "styled-components": "^5.2.3"
@@ -1,18 +1,17 @@
1
1
  import React, { useState } from "react";
2
2
  import { Box } from "@sproutsocial/seeds-react-box";
3
+ import { Text } from "@sproutsocial/seeds-react-text";
3
4
  import { Button } from "@sproutsocial/seeds-react-button";
4
5
  import { Collapsible } from "./";
5
6
  import type { Meta, StoryObj } from "@storybook/react";
6
7
 
7
8
  export interface TypeStatefulCollapseProps {
8
9
  children: React.ReactElement;
9
- offset?: number;
10
10
  initialIsOpen?: boolean;
11
11
  }
12
12
 
13
13
  const StatefulCollapse = ({
14
14
  children,
15
- offset = 0,
16
15
  initialIsOpen = false,
17
16
  }: TypeStatefulCollapseProps) => {
18
17
  const [open, setOpen] = useState(initialIsOpen);
@@ -20,7 +19,7 @@ const StatefulCollapse = ({
20
19
  const toggle = () => setOpen(!open);
21
20
 
22
21
  return (
23
- <Collapsible isOpen={open} offset={offset}>
22
+ <Collapsible isOpen={open}>
24
23
  <Collapsible.Trigger>
25
24
  <Button appearance="secondary" onClick={toggle}>
26
25
  {open ? "Hide" : "Show"}
@@ -41,125 +40,102 @@ export default meta;
41
40
 
42
41
  type Story = StoryObj<typeof Collapsible>;
43
42
 
44
- export const DefaultStory: Story = {
43
+ export const Default: Story = {
45
44
  render: () => (
46
45
  <StatefulCollapse>
47
- <Box width="100%" height="200px" bg="container.background.base" p={400}>
48
- <Button appearance="secondary">A button</Button>
46
+ <Box bg="neutral.100" p={400} mt={300} borderRadius="inner">
47
+ <Text.BodyCopy as="p">
48
+ This content is revealed when you click the trigger above. Click
49
+ "Hide" to collapse it again.
50
+ </Text.BodyCopy>
49
51
  </Box>
50
52
  </StatefulCollapse>
51
53
  ),
52
54
  };
53
55
 
54
- export const InitialIsOpen: Story = {
56
+ export const OpenByDefault: Story = {
55
57
  render: () => (
56
58
  <StatefulCollapse initialIsOpen>
57
- <Box width="100%" height="200px" bg="container.background.base" p={400}>
58
- <Button appearance="secondary">A button</Button>
59
+ <Box bg="neutral.100" p={400} mt={300} borderRadius="inner">
60
+ <Text.BodyCopy as="p">
61
+ This panel was open when the page loaded. Click "Hide" to collapse it.
62
+ </Text.BodyCopy>
59
63
  </Box>
60
64
  </StatefulCollapse>
61
65
  ),
62
66
  };
63
67
 
64
- export const WithOffset: Story = {
68
+ export const SmallContent: Story = {
65
69
  render: () => (
66
- <StatefulCollapse offset={100}>
70
+ <StatefulCollapse>
67
71
  <Box
68
- width="100%"
69
- height="200px"
70
- bg="container.background.base"
71
- p={400}
72
- mt="100px"
72
+ display="inline-block"
73
+ bg="neutral.100"
74
+ p={300}
75
+ mt={300}
76
+ borderRadius="inner"
73
77
  >
74
- <Button appearance="secondary">A button</Button>
78
+ <Text.SmallBodyCopy>Short message</Text.SmallBodyCopy>
75
79
  </Box>
76
80
  </StatefulCollapse>
77
81
  ),
78
82
  };
79
83
 
80
- export const WithShortContent: Story = {
84
+ export const OverflowingContent: Story = {
81
85
  render: () => (
82
86
  <StatefulCollapse>
83
- <Box width="15%" height="50px" bg="container.background.base" p={400}>
84
- Hello.
87
+ <Box bg="neutral.100" p={400} mt={300} borderRadius="inner">
88
+ <Text.BodyCopy as="p" mb={400}>
89
+ This content is intentionally very tall (200vh) to demonstrate how the
90
+ collapsible handles content that exceeds the viewport height.
91
+ </Text.BodyCopy>
92
+ <Box bg="neutral.200" borderRadius="inner" height="200vh" p={400}>
93
+ <Text.SmallBodyCopy color="text.body">
94
+ 200vh tall region — scroll down to see the full height
95
+ </Text.SmallBodyCopy>
96
+ </Box>
85
97
  </Box>
86
98
  </StatefulCollapse>
87
99
  ),
88
100
  };
89
101
 
90
- export const WithTallContent: Story = {
102
+ export const RichContent: Story = {
91
103
  render: () => (
92
- <StatefulCollapse>
93
- <Box width="15%" height="200vh" bg="container.background.base" p={400}>
94
- Hello.
95
- </Box>
96
- </StatefulCollapse>
104
+ <Box bg="neutral.200" p={500} borderRadius="outer" maxWidth="600px">
105
+ <StatefulCollapse>
106
+ <Box
107
+ bg="container.background.base"
108
+ p={400}
109
+ mt={300}
110
+ borderRadius="inner"
111
+ >
112
+ <Text.BodyCopy as="p" mb={400}>
113
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
114
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
115
+ ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
116
+ aliquip ex ea commodo consequat.
117
+ </Text.BodyCopy>
118
+ <Text.BodyCopy as="p">
119
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
120
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
121
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
122
+ anim id est laborum.
123
+ </Text.BodyCopy>
124
+ </Box>
125
+ </StatefulCollapse>
126
+ </Box>
97
127
  ),
98
128
  };
99
129
 
100
- interface TypeStatefulCollapseMinHeightProps {
101
- children: React.ReactElement;
102
- offset?: number;
103
- collapsedHeight?: number;
104
- openHeight?: number;
105
- }
106
-
107
- const StatefulCollapseWithMinHeight = ({
108
- children,
109
- offset = 0,
110
- collapsedHeight = 0,
111
- openHeight,
112
- }: TypeStatefulCollapseMinHeightProps) => {
113
- const [open, setOpen] = useState(false);
114
-
115
- const toggle = () => setOpen(!open);
116
-
117
- return (
118
- <Collapsible
119
- isOpen={open}
120
- offset={offset}
121
- openHeight={openHeight}
122
- collapsedHeight={collapsedHeight}
123
- >
124
- <Collapsible.Panel>{children}</Collapsible.Panel>
125
- <Collapsible.Trigger>
126
- <Button onClick={toggle}>{open ? "Show Less" : "Show More"}</Button>
127
- </Collapsible.Trigger>
128
- </Collapsible>
129
- );
130
- };
131
-
132
- export const WithCollapsedHeight: Story = {
130
+ export const ContentWithTopMargin: Story = {
133
131
  render: () => (
134
- <StatefulCollapseWithMinHeight collapsedHeight={100} openHeight={300}>
135
- <Box width="500px" p={400}>
136
- Threepio! Come in, Threepio! Threepio! Get to the top! I can’t Where
137
- could he be? Threepio! Threepio, will you come in? They aren’t here!
138
- Something must have happened to them. See if they’ve been captured.
139
- Hurry! One thing’s for sure. We’re all going to be a lot thinner! Get on
140
- top of it! I’m trying! Thank goodness, they haven’t found them! Where
141
- could they be? Use the comlink? Oh, my! I forgot I turned it off! Are
142
- you there, sir? Threepio! We’ve had some problems… Will you shut up and
143
- listen to me? Shut down all garbage mashers on the detention level, will
144
- you? Do you copy? Shut down all the garbage mashers on the detention
145
- level. Shut down all the garbage mashers on the detention level. No.
146
- Shut them all down! Hurry! Listen to them! They’re dying, Artoo! Curse
147
- my metal body! I wasn’t fast enough. It’s all my fault! My poor master!
148
- Threepio, we’re all right! We’re all right. You did great. Threepio!
149
- Come in, Threepio! Threepio! Get to the top! I can’t Where could he be?
150
- Threepio! Threepio, will you come in? They aren’t here! Something must
151
- have happened to them. See if they’ve been captured. Hurry! One thing’s
152
- for sure. We’re all going to be a lot thinner! Get on top of it! I’m
153
- trying! Thank goodness, they haven’t found them! Where could they be?
154
- Use the comlink? Oh, my! I forgot I turned it off! Are you there, sir?
155
- Threepio! We’ve had some problems… Will you shut up and listen to me?
156
- Shut down all garbage mashers on the detention level, will you? Do you
157
- copy? Shut down all the garbage mashers on the detention level. Shut
158
- down all the garbage mashers on the detention level. No. Shut them all
159
- down! Hurry! Listen to them! They’re dying, Artoo! Curse my metal body!
160
- I wasn’t fast enough. It’s all my fault! My poor master! Threepio, we’re
161
- all right! We’re all right. You did great.
132
+ <StatefulCollapse>
133
+ <Box bg="neutral.100" p={400} mt="100px" borderRadius="inner">
134
+ <Text.BodyCopy as="p">
135
+ This content has a large top margin (100px) to verify the collapse
136
+ animation accounts for margin when calculating height.
137
+ </Text.BodyCopy>
162
138
  </Box>
163
- </StatefulCollapseWithMinHeight>
139
+ </StatefulCollapse>
164
140
  ),
165
141
  };
@@ -1,134 +1,44 @@
1
1
  import * as React from "react";
2
- import { useState, useRef, useContext, useEffect } from "react";
3
- import { useMeasure } from "@sproutsocial/seeds-react-hooks";
4
- import Box from "@sproutsocial/seeds-react-box";
5
- import { CollapsingBox } from "./styles";
2
+ import * as RadixCollapsible from "@radix-ui/react-collapsible";
3
+ import { StyledCollapsibleContent } from "./styles";
6
4
  import type {
7
5
  TypeCollapsibleProps,
8
6
  TypeCollapsiblePanelProps,
9
7
  TypeCollapsibleTriggerProps,
10
8
  } from "./CollapsibleTypes";
11
9
 
12
- let idCounter = 0;
13
-
14
- interface TypeCollapsibleContext {
15
- isOpen?: boolean;
16
- id?: string;
17
- offset?: number;
18
- openHeight?: number;
19
- collapsedHeight?: number;
20
- }
21
-
22
- const CollapsibleContext = React.createContext<TypeCollapsibleContext>({});
23
-
24
10
  const Collapsible = ({
25
11
  children,
26
12
  isOpen = false,
27
- offset = 0,
28
- collapsedHeight = 0,
29
- openHeight,
13
+ disabled,
14
+ defaultOpen,
15
+ onOpenChange,
30
16
  }: TypeCollapsibleProps) => {
31
- const [id] = useState(`Racine-collapsible-${idCounter++}`);
32
17
  return (
33
- <CollapsibleContext.Provider
34
- value={{
35
- isOpen,
36
- id,
37
- offset,
38
- collapsedHeight,
39
- openHeight,
40
- }}
18
+ <RadixCollapsible.Root
19
+ open={isOpen}
20
+ disabled={disabled}
21
+ defaultOpen={defaultOpen}
22
+ onOpenChange={onOpenChange}
41
23
  >
42
24
  {children}
43
- </CollapsibleContext.Provider>
25
+ </RadixCollapsible.Root>
44
26
  );
45
27
  };
46
28
 
47
- const determineMaxHeight = (
48
- isHidden?: boolean,
49
- openHeight?: number,
50
- computedHeight?: number
51
- ): number | undefined => {
52
- // If isHidden is undefined this is the first render. Return undefined so the max-height prop is not added
53
- // This is a hack to prevent css from animating if it begins in the open state
54
- // css animates when attribute values change (IE from 0 to another number)
55
- // css does not animate when simply adding an attribute to an HTML element
56
- if (isHidden === undefined) return undefined;
57
- // If the user has defined an explicit open height, return that as the max height
58
- if (openHeight) return openHeight;
59
- // Otherwise, fallback to the computed height
60
- return computedHeight;
61
- };
62
-
63
- const Trigger = ({ children, ...rest }: TypeCollapsibleTriggerProps) => {
64
- const { isOpen, id } = useContext(CollapsibleContext);
29
+ const Trigger = ({ children }: TypeCollapsibleTriggerProps) => {
65
30
  return (
66
- <React.Fragment>
67
- {React.cloneElement(children, {
68
- "aria-controls": id,
69
- "aria-expanded": !!isOpen,
70
- ...rest,
71
- })}
72
- </React.Fragment>
31
+ <RadixCollapsible.Trigger asChild>{children}</RadixCollapsible.Trigger>
73
32
  );
74
33
  };
75
34
 
76
35
  Trigger.displayName = "Collapsible.Trigger";
77
36
 
78
- const Panel = ({ children, ...rest }: TypeCollapsiblePanelProps) => {
79
- const {
80
- isOpen,
81
- id,
82
- offset = 0,
83
- collapsedHeight,
84
- openHeight,
85
- } = useContext(CollapsibleContext);
86
-
87
- const ref = useRef<HTMLDivElement | null>(null);
88
- const measurement = useMeasure(ref);
89
- const [isHidden, setIsHidden] = useState<boolean | undefined>(undefined);
90
- const maxHeight = determineMaxHeight(
91
- isHidden,
92
- openHeight,
93
- // Round up to the nearest pixel to prevent subpixel rendering issues
94
- Math.ceil(measurement.height + offset)
95
- );
96
-
97
- /* We use the "hidden" attribute to remove the contents of the panel from the tab order of the page, but it interferes with the animation. This logic sets a slight timeout on setting the prop so that the animation has time to complete before the attribute is set. */
98
- useEffect(() => {
99
- if (!isOpen) {
100
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 300);
101
- return () => clearTimeout(timeoutID);
102
- } else {
103
- // Similar to the close animation, we need to delay setting hidden to run slightly async.
104
- // An issue occurs with the initial render isHidden logic that causes the animation to occur sporadically.
105
- // using this 0 second timeout just allows this component to initially render with an undefined max height,
106
- // Then go directly from undefined to the full max height, without a brief 0 value that triggers an animation
107
- const timeoutID = setTimeout(() => setIsHidden(!isOpen), 0);
108
- return () => clearTimeout(timeoutID);
109
- }
110
- }, [isOpen]);
111
-
37
+ const Panel = ({ children, forceMount }: TypeCollapsiblePanelProps) => {
112
38
  return (
113
- <CollapsingBox
114
- hasShadow={Boolean(collapsedHeight || (openHeight && openHeight > 0))}
115
- scrollable={isOpen}
116
- maxHeight={isOpen ? maxHeight : collapsedHeight}
117
- minHeight={collapsedHeight}
118
- data-qa-collapsible=""
119
- data-qa-collapsible-isopen={isOpen === true}
120
- {...rest}
121
- >
122
- <Box
123
- width="100%"
124
- hidden={isHidden && collapsedHeight === 0}
125
- aria-hidden={!isOpen}
126
- id={id}
127
- ref={ref}
128
- >
129
- {children}
130
- </Box>
131
- </CollapsingBox>
39
+ <StyledCollapsibleContent forceMount={forceMount} data-qa-collapsible="">
40
+ {children}
41
+ </StyledCollapsibleContent>
132
42
  );
133
43
  };
134
44
 
@@ -1,22 +1,27 @@
1
1
  import * as React from "react";
2
- import type { TypeBoxProps } from "@sproutsocial/seeds-react-box";
3
2
 
4
- // The flow type is inexact but the underlying component does not accept any other props.
5
- // It might be worth extending the box props here for the refactor, but allowing it would provide no functionality right now.
6
3
  export interface TypeCollapsibleProps {
7
4
  isOpen: boolean;
8
5
  children: React.ReactNode;
9
6
 
10
- /** If the children of the collapsible panel have a top or bottom margin, it will throw off the calculations for the height of the content. The total amount of vertical margin (in pixels) can be supplied to this prop to correct this. */
11
- offset?: number;
12
- collapsedHeight?: number;
13
- openHeight?: number;
7
+ /** Whether the collapsible is disabled */
8
+ disabled?: boolean;
9
+
10
+ /** The open state when initially rendered. Use when you don't need controlled state. */
11
+ defaultOpen?: boolean;
12
+
13
+ /** Called when the open state changes */
14
+ onOpenChange?: (open: boolean) => void;
14
15
  }
15
16
 
16
- export interface TypeCollapsibleTriggerProps
17
- extends Omit<TypeBoxProps, "children"> {
18
- // Children is required for the Trigger
17
+ export interface TypeCollapsibleTriggerProps {
18
+ /** A single React element that will be used as the trigger via Radix asChild */
19
19
  children: React.ReactElement;
20
20
  }
21
21
 
22
- export interface TypeCollapsiblePanelProps extends TypeBoxProps {}
22
+ export interface TypeCollapsiblePanelProps {
23
+ children?: React.ReactNode;
24
+
25
+ /** Force mounting the content when closed. Note: when enabled, content stays in the DOM but animations are not supported. */
26
+ forceMount?: true;
27
+ }
@@ -49,10 +49,14 @@ describe("Collapsible", () => {
49
49
  render(<StatefulCollapse>Panel text</StatefulCollapse>);
50
50
  const trigger = screen.queryByRole("button");
51
51
  expect(trigger).toHaveAttribute("aria-expanded", "false");
52
- // TS thinks the trigger might be null even though the previous expect would have failed if it was
52
+
53
53
  trigger && fireEvent.click(trigger);
54
+
54
55
  const panel = screen.queryByText(/Panel text/);
55
- expect(panel).toHaveAttribute("aria-hidden", "false");
56
+ expect(panel?.closest("[data-state]")).toHaveAttribute(
57
+ "data-state",
58
+ "open"
59
+ );
56
60
  expect(trigger).toHaveAttribute("aria-expanded", "true");
57
61
  });
58
62
 
@@ -60,14 +64,17 @@ describe("Collapsible", () => {
60
64
  render(<StatefulCollapse isOpen>Panel text</StatefulCollapse>);
61
65
  const trigger = screen.queryByRole("button");
62
66
  const panel = screen.queryByText(/Panel text/);
63
- expect(panel).toHaveAttribute("aria-hidden", "false");
67
+ expect(panel?.closest("[data-state]")).toHaveAttribute(
68
+ "data-state",
69
+ "open"
70
+ );
64
71
  expect(trigger).toHaveAttribute("aria-expanded", "true");
65
72
  });
66
73
 
67
74
  it("trigger should be properly labelled", async () => {
68
75
  render(<StatefulCollapse isOpen>Panel text</StatefulCollapse>);
69
76
  const trigger = screen.queryByRole("button");
70
- const panel = screen.queryByText(/Panel text/);
77
+ const panel = screen.queryByText(/Panel text/)?.closest("[data-state]");
71
78
  expect(trigger).toHaveAttribute("aria-controls", panel?.id);
72
79
  });
73
80
  });
@@ -19,14 +19,9 @@ describe.skip("Collapsible/types", () => {
19
19
  <Button appearance="secondary">A button</Button>
20
20
  </Box>
21
21
  );
22
- const shortContent = (
23
- <Box width="15%" height="50px" bg="container.background.base" p={400}>
24
- Hello.
25
- </Box>
26
- );
27
22
  render(
28
23
  <>
29
- <Collapsible isOpen={true} offset={0}>
24
+ <Collapsible isOpen={true}>
30
25
  <Collapsible.Trigger>
31
26
  <Button appearance="secondary" onClick={toggle}>
32
27
  Hide
@@ -35,7 +30,7 @@ describe.skip("Collapsible/types", () => {
35
30
  <Collapsible.Panel>{contentWithButton}</Collapsible.Panel>
36
31
  </Collapsible>
37
32
 
38
- <Collapsible isOpen={false} offset={100}>
33
+ <Collapsible isOpen={false} disabled>
39
34
  <Collapsible.Trigger>
40
35
  <Button appearance="secondary" onClick={toggle}>
41
36
  Show
@@ -44,8 +39,8 @@ describe.skip("Collapsible/types", () => {
44
39
  <Collapsible.Panel>{contentWithButton}</Collapsible.Panel>
45
40
  </Collapsible>
46
41
 
47
- <Collapsible isOpen={false} collapsedHeight={500}>
48
- <Collapsible.Panel>{shortContent}</Collapsible.Panel>
42
+ <Collapsible isOpen={false} onOpenChange={toggle}>
43
+ <Collapsible.Panel forceMount>{contentWithButton}</Collapsible.Panel>
49
44
  <Collapsible.Trigger>
50
45
  <Button onClick={toggle}>Show More</Button>
51
46
  </Collapsible.Trigger>
package/src/styles.ts CHANGED
@@ -1,37 +1,36 @@
1
- import styled from "styled-components";
2
- import Box from "@sproutsocial/seeds-react-box";
1
+ import styled, { css } from "styled-components";
2
+ import * as RadixCollapsible from "@radix-ui/react-collapsible";
3
3
 
4
- export const CollapsingBox = styled(Box)<{
5
- hasShadow?: boolean;
6
- scrollable?: boolean;
7
- }>`
8
- transition: max-height ${(p) => p.theme.duration.medium}
9
- ${(p) => p.theme.easing.ease_inout};
10
- will-change: max-height;
11
- position: relative;
12
- overflow: auto;
13
- ${({ hasShadow, scrollable }) =>
14
- hasShadow
15
- ? `background: /* Shadow covers */ linear-gradient(
16
- transparent 30%,
17
- rgba(255, 255, 255, 0)
18
- ),
19
- linear-gradient(rgba(255, 255, 255, 0), transparent 70%) 0 100%,
20
- /* Shadows */
21
- radial-gradient(
22
- farthest-side at 50% 0,
23
- rgb(39 51 51 / 5%),
24
- rgba(0, 0, 0, 0)
25
- ),
26
- radial-gradient(
27
- farthest-side at 50% 100%,
28
- rgb(39 51 51 / 5%),
29
- rgba(0, 0, 0, 0)
30
- )
31
- 0 100%;
32
- background-repeat: no-repeat;
33
- background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
34
- background-attachment: local, local, scroll, scroll;
35
- ${scrollable ? `overflow: auto` : `overflow: hidden`};`
36
- : ""}
4
+ const animations = css`
5
+ @keyframes collapsibleSlideDown {
6
+ from {
7
+ height: 0;
8
+ }
9
+ to {
10
+ height: var(--radix-collapsible-content-height);
11
+ }
12
+ }
13
+
14
+ @keyframes collapsibleSlideUp {
15
+ from {
16
+ height: var(--radix-collapsible-content-height);
17
+ }
18
+ to {
19
+ height: 0;
20
+ }
21
+ }
22
+ `;
23
+
24
+ export const StyledCollapsibleContent = styled(RadixCollapsible.Content)`
25
+ ${animations}
26
+
27
+ overflow: hidden;
28
+
29
+ &[data-state="open"] {
30
+ animation: collapsibleSlideDown 300ms ease-in-out;
31
+ }
32
+
33
+ &[data-state="closed"] {
34
+ animation: collapsibleSlideUp 300ms ease-in-out;
35
+ }
37
36
  `;