@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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +15 -0
- package/dist/esm/index.js +49 -110
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +15 -10
- package/dist/index.d.ts +15 -10
- package/dist/index.js +48 -109
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/Collapsible.stories.tsx +64 -88
- package/src/Collapsible.tsx +17 -107
- package/src/CollapsibleTypes.ts +16 -11
- package/src/__tests__/features.test.tsx +11 -4
- package/src/__tests__/types.test.tsx +4 -9
- package/src/styles.ts +34 -35
package/.turbo/turbo-build.log
CHANGED
|
@@ -8,14 +8,14 @@ $ tsup --dts
|
|
|
8
8
|
[34mCLI[39m Cleaning output folder
|
|
9
9
|
[34mCJS[39m Build start
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
11
|
+
[32mESM[39m [1mdist/esm/index.js [22m[32m1.79 KB[39m
|
|
12
|
+
[32mESM[39m [1mdist/esm/index.js.map [22m[32m3.85 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 174ms
|
|
14
|
+
[32mCJS[39m [1mdist/index.js [22m[32m3.57 KB[39m
|
|
15
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m3.91 KB[39m
|
|
16
|
+
[32mCJS[39m ⚡️ Build success in 174ms
|
|
17
17
|
[34mDTS[39m Build start
|
|
18
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.
|
|
20
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.
|
|
21
|
-
Done in
|
|
18
|
+
[32mDTS[39m ⚡️ Build success in 27032ms
|
|
19
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.38 KB[39m
|
|
20
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.38 KB[39m
|
|
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
|
|
3
|
-
import
|
|
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
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
disabled,
|
|
47
|
+
defaultOpen,
|
|
48
|
+
onOpenChange
|
|
49
49
|
}) => {
|
|
50
|
-
const [id] = useState(`Racine-collapsible-${idCounter++}`);
|
|
51
50
|
return /* @__PURE__ */ jsx(
|
|
52
|
-
|
|
51
|
+
RadixCollapsible2.Root,
|
|
53
52
|
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
66
|
-
|
|
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
|
-
|
|
79
|
-
var Panel = ({ children,
|
|
80
|
-
|
|
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 =
|
|
69
|
+
Collapsible.Trigger = Trigger2;
|
|
131
70
|
Collapsible.Panel = Panel;
|
|
132
71
|
var Collapsible_default = Collapsible;
|
|
133
72
|
|
package/dist/esm/index.js.map
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
|
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,
|
|
25
|
+
({ children, isOpen, disabled, defaultOpen, onOpenChange, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
|
|
21
26
|
Trigger: {
|
|
22
|
-
({ children
|
|
27
|
+
({ children }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
|
|
23
28
|
displayName: string;
|
|
24
29
|
};
|
|
25
30
|
Panel: {
|
|
26
|
-
({ children,
|
|
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
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
|
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,
|
|
25
|
+
({ children, isOpen, disabled, defaultOpen, onOpenChange, }: TypeCollapsibleProps): react_jsx_runtime.JSX.Element;
|
|
21
26
|
Trigger: {
|
|
22
|
-
({ children
|
|
27
|
+
({ children }: TypeCollapsibleTriggerProps): react_jsx_runtime.JSX.Element;
|
|
23
28
|
displayName: string;
|
|
24
29
|
};
|
|
25
30
|
Panel: {
|
|
26
|
-
({ children,
|
|
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 =
|
|
40
|
-
var
|
|
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
|
|
47
|
-
var
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
+
RadixCollapsible2.Root,
|
|
90
89
|
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
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 =
|
|
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
|
|
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": "
|
|
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
|
-
"@
|
|
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.
|
|
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}
|
|
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
|
|
43
|
+
export const Default: Story = {
|
|
45
44
|
render: () => (
|
|
46
45
|
<StatefulCollapse>
|
|
47
|
-
<Box
|
|
48
|
-
<
|
|
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
|
|
56
|
+
export const OpenByDefault: Story = {
|
|
55
57
|
render: () => (
|
|
56
58
|
<StatefulCollapse initialIsOpen>
|
|
57
|
-
<Box
|
|
58
|
-
<
|
|
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
|
|
68
|
+
export const SmallContent: Story = {
|
|
65
69
|
render: () => (
|
|
66
|
-
<StatefulCollapse
|
|
70
|
+
<StatefulCollapse>
|
|
67
71
|
<Box
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
display="inline-block"
|
|
73
|
+
bg="neutral.100"
|
|
74
|
+
p={300}
|
|
75
|
+
mt={300}
|
|
76
|
+
borderRadius="inner"
|
|
73
77
|
>
|
|
74
|
-
<
|
|
78
|
+
<Text.SmallBodyCopy>Short message</Text.SmallBodyCopy>
|
|
75
79
|
</Box>
|
|
76
80
|
</StatefulCollapse>
|
|
77
81
|
),
|
|
78
82
|
};
|
|
79
83
|
|
|
80
|
-
export const
|
|
84
|
+
export const OverflowingContent: Story = {
|
|
81
85
|
render: () => (
|
|
82
86
|
<StatefulCollapse>
|
|
83
|
-
<Box
|
|
84
|
-
|
|
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
|
|
102
|
+
export const RichContent: Story = {
|
|
91
103
|
render: () => (
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
135
|
-
<Box
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
</
|
|
139
|
+
</StatefulCollapse>
|
|
164
140
|
),
|
|
165
141
|
};
|
package/src/Collapsible.tsx
CHANGED
|
@@ -1,134 +1,44 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
disabled,
|
|
14
|
+
defaultOpen,
|
|
15
|
+
onOpenChange,
|
|
30
16
|
}: TypeCollapsibleProps) => {
|
|
31
|
-
const [id] = useState(`Racine-collapsible-${idCounter++}`);
|
|
32
17
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
</
|
|
25
|
+
</RadixCollapsible.Root>
|
|
44
26
|
);
|
|
45
27
|
};
|
|
46
28
|
|
|
47
|
-
const
|
|
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
|
-
<
|
|
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,
|
|
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
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
package/src/CollapsibleTypes.ts
CHANGED
|
@@ -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
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
52
|
+
|
|
53
53
|
trigger && fireEvent.click(trigger);
|
|
54
|
+
|
|
54
55
|
const panel = screen.queryByText(/Panel text/);
|
|
55
|
-
expect(panel
|
|
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
|
|
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}
|
|
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}
|
|
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}
|
|
48
|
-
<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
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
import * as RadixCollapsible from "@radix-ui/react-collapsible";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
`;
|