@seed-design/react-collapsible 0.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/lib/Collapsible-12s-B5Klt2JP.cjs +156 -0
- package/lib/Collapsible-12s-CzE3seHv.js +151 -0
- package/lib/index.cjs +16 -0
- package/lib/index.d.ts +1173 -0
- package/lib/index.js +11 -0
- package/package.json +48 -0
- package/src/Collapsible.namespace.ts +8 -0
- package/src/Collapsible.tsx +69 -0
- package/src/dom.ts +1 -0
- package/src/index.ts +22 -0
- package/src/useCollapsible.test.tsx +203 -0
- package/src/useCollapsible.ts +121 -0
- package/src/useCollapsibleContext.tsx +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
3
|
+
var reactComposeRefs = require('@radix-ui/react-compose-refs');
|
|
4
|
+
var domUtils = require('@seed-design/dom-utils');
|
|
5
|
+
var reactPrimitive = require('@seed-design/react-primitive');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var reactUseControllableState = require('@radix-ui/react-use-controllable-state');
|
|
8
|
+
var reactUseLayoutEffect = require('@radix-ui/react-use-layout-effect');
|
|
9
|
+
|
|
10
|
+
const getContentId = (id)=>`collapsible:${id}:content`;
|
|
11
|
+
|
|
12
|
+
function useCollapsibleState(props) {
|
|
13
|
+
const [open, setOpen] = reactUseControllableState.useControllableState({
|
|
14
|
+
prop: props.open,
|
|
15
|
+
defaultProp: props.defaultOpen ?? false,
|
|
16
|
+
onChange: props.onOpenChange
|
|
17
|
+
});
|
|
18
|
+
return react.useMemo(()=>({
|
|
19
|
+
open,
|
|
20
|
+
setOpen
|
|
21
|
+
}), [
|
|
22
|
+
open,
|
|
23
|
+
setOpen
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
function useCollapsible(props) {
|
|
27
|
+
const { open, setOpen } = useCollapsibleState(props);
|
|
28
|
+
const { disabled } = props;
|
|
29
|
+
const id = react.useId();
|
|
30
|
+
const contentId = getContentId(id);
|
|
31
|
+
const contentRef = react.useRef(null);
|
|
32
|
+
const [height, setHeight] = react.useState(undefined);
|
|
33
|
+
const [visible, setVisible] = react.useState(open);
|
|
34
|
+
const hidden = !open && !visible;
|
|
35
|
+
reactUseLayoutEffect.useLayoutEffect(()=>{
|
|
36
|
+
if (!contentRef.current) return;
|
|
37
|
+
const updateHeight = ()=>{
|
|
38
|
+
if (!contentRef.current) return;
|
|
39
|
+
setHeight(contentRef.current.offsetHeight);
|
|
40
|
+
};
|
|
41
|
+
updateHeight();
|
|
42
|
+
const observer = new ResizeObserver(updateHeight);
|
|
43
|
+
observer.observe(contentRef.current);
|
|
44
|
+
return ()=>observer.disconnect();
|
|
45
|
+
}, []);
|
|
46
|
+
react.useEffect(()=>{
|
|
47
|
+
if (!open) return;
|
|
48
|
+
// When expanded, immediately show to allow transition
|
|
49
|
+
setVisible(true);
|
|
50
|
+
}, [
|
|
51
|
+
open
|
|
52
|
+
]);
|
|
53
|
+
const panelHeight = open ? `${height}px` : "0px";
|
|
54
|
+
const stateProps = react.useMemo(()=>domUtils.elementProps({
|
|
55
|
+
"data-collapsible": "",
|
|
56
|
+
"data-open": domUtils.dataAttr(open),
|
|
57
|
+
"data-disabled": domUtils.dataAttr(disabled)
|
|
58
|
+
}), [
|
|
59
|
+
open,
|
|
60
|
+
disabled
|
|
61
|
+
]);
|
|
62
|
+
return react.useMemo(()=>({
|
|
63
|
+
open,
|
|
64
|
+
setOpen,
|
|
65
|
+
disabled,
|
|
66
|
+
stateProps,
|
|
67
|
+
triggerAriaProps: domUtils.elementProps({
|
|
68
|
+
"aria-expanded": open,
|
|
69
|
+
"aria-controls": contentId,
|
|
70
|
+
"aria-disabled": disabled
|
|
71
|
+
}),
|
|
72
|
+
triggerHandlers: domUtils.elementProps({
|
|
73
|
+
onClick: (event)=>{
|
|
74
|
+
if (event.defaultPrevented) return;
|
|
75
|
+
if (disabled) return;
|
|
76
|
+
setOpen((prev)=>!prev);
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
contentProps: domUtils.elementProps({
|
|
80
|
+
...stateProps,
|
|
81
|
+
id: contentId,
|
|
82
|
+
hidden,
|
|
83
|
+
style: {
|
|
84
|
+
"--collapsible-content-height": height !== undefined ? panelHeight : undefined
|
|
85
|
+
},
|
|
86
|
+
onTransitionEnd: (event)=>{
|
|
87
|
+
if (event.propertyName !== "height") return;
|
|
88
|
+
if (open) return;
|
|
89
|
+
setVisible(false);
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
refs: {
|
|
93
|
+
content: contentRef
|
|
94
|
+
}
|
|
95
|
+
}), [
|
|
96
|
+
open,
|
|
97
|
+
setOpen,
|
|
98
|
+
disabled,
|
|
99
|
+
stateProps,
|
|
100
|
+
contentId,
|
|
101
|
+
hidden,
|
|
102
|
+
height,
|
|
103
|
+
panelHeight
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const CollapsibleContext = /*#__PURE__*/ react.createContext(null);
|
|
108
|
+
const CollapsibleProvider = CollapsibleContext.Provider;
|
|
109
|
+
function useCollapsibleContext({ strict = true } = {}) {
|
|
110
|
+
const context = react.useContext(CollapsibleContext);
|
|
111
|
+
if (!context && strict) {
|
|
112
|
+
throw new Error("useCollapsibleContext must be used within a CollapsibleRoot");
|
|
113
|
+
}
|
|
114
|
+
return context;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const CollapsibleRoot = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
118
|
+
const { open, defaultOpen, onOpenChange, disabled, ...otherProps } = props;
|
|
119
|
+
const api = useCollapsible({
|
|
120
|
+
open,
|
|
121
|
+
defaultOpen,
|
|
122
|
+
onOpenChange,
|
|
123
|
+
disabled
|
|
124
|
+
});
|
|
125
|
+
return /*#__PURE__*/ jsxRuntime.jsx(CollapsibleProvider, {
|
|
126
|
+
value: api,
|
|
127
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
128
|
+
ref: ref,
|
|
129
|
+
...domUtils.mergeProps(api.stateProps, otherProps)
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
CollapsibleRoot.displayName = "CollapsibleRoot";
|
|
134
|
+
const CollapsibleTrigger = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
135
|
+
const api = useCollapsibleContext();
|
|
136
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.button, {
|
|
137
|
+
ref: ref,
|
|
138
|
+
...domUtils.mergeProps(api.stateProps, api.triggerAriaProps, api.triggerHandlers, props)
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
CollapsibleTrigger.displayName = "CollapsibleTrigger";
|
|
142
|
+
const CollapsibleContent = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
143
|
+
const api = useCollapsibleContext();
|
|
144
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
145
|
+
ref: reactComposeRefs.composeRefs(ref, api.refs.content),
|
|
146
|
+
...domUtils.mergeProps(api.contentProps, props)
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
CollapsibleContent.displayName = "CollapsibleContent";
|
|
150
|
+
|
|
151
|
+
exports.CollapsibleContent = CollapsibleContent;
|
|
152
|
+
exports.CollapsibleProvider = CollapsibleProvider;
|
|
153
|
+
exports.CollapsibleRoot = CollapsibleRoot;
|
|
154
|
+
exports.CollapsibleTrigger = CollapsibleTrigger;
|
|
155
|
+
exports.useCollapsible = useCollapsible;
|
|
156
|
+
exports.useCollapsibleContext = useCollapsibleContext;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
4
|
+
import { elementProps, dataAttr, mergeProps } from '@seed-design/dom-utils';
|
|
5
|
+
import { Primitive } from '@seed-design/react-primitive';
|
|
6
|
+
import { useId, useRef, useState, useEffect, useMemo, createContext, useContext, forwardRef } from 'react';
|
|
7
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
8
|
+
import { useLayoutEffect } from '@radix-ui/react-use-layout-effect';
|
|
9
|
+
|
|
10
|
+
const getContentId = (id)=>`collapsible:${id}:content`;
|
|
11
|
+
|
|
12
|
+
function useCollapsibleState(props) {
|
|
13
|
+
const [open, setOpen] = useControllableState({
|
|
14
|
+
prop: props.open,
|
|
15
|
+
defaultProp: props.defaultOpen ?? false,
|
|
16
|
+
onChange: props.onOpenChange
|
|
17
|
+
});
|
|
18
|
+
return useMemo(()=>({
|
|
19
|
+
open,
|
|
20
|
+
setOpen
|
|
21
|
+
}), [
|
|
22
|
+
open,
|
|
23
|
+
setOpen
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
function useCollapsible(props) {
|
|
27
|
+
const { open, setOpen } = useCollapsibleState(props);
|
|
28
|
+
const { disabled } = props;
|
|
29
|
+
const id = useId();
|
|
30
|
+
const contentId = getContentId(id);
|
|
31
|
+
const contentRef = useRef(null);
|
|
32
|
+
const [height, setHeight] = useState(undefined);
|
|
33
|
+
const [visible, setVisible] = useState(open);
|
|
34
|
+
const hidden = !open && !visible;
|
|
35
|
+
useLayoutEffect(()=>{
|
|
36
|
+
if (!contentRef.current) return;
|
|
37
|
+
const updateHeight = ()=>{
|
|
38
|
+
if (!contentRef.current) return;
|
|
39
|
+
setHeight(contentRef.current.offsetHeight);
|
|
40
|
+
};
|
|
41
|
+
updateHeight();
|
|
42
|
+
const observer = new ResizeObserver(updateHeight);
|
|
43
|
+
observer.observe(contentRef.current);
|
|
44
|
+
return ()=>observer.disconnect();
|
|
45
|
+
}, []);
|
|
46
|
+
useEffect(()=>{
|
|
47
|
+
if (!open) return;
|
|
48
|
+
// When expanded, immediately show to allow transition
|
|
49
|
+
setVisible(true);
|
|
50
|
+
}, [
|
|
51
|
+
open
|
|
52
|
+
]);
|
|
53
|
+
const panelHeight = open ? `${height}px` : "0px";
|
|
54
|
+
const stateProps = useMemo(()=>elementProps({
|
|
55
|
+
"data-collapsible": "",
|
|
56
|
+
"data-open": dataAttr(open),
|
|
57
|
+
"data-disabled": dataAttr(disabled)
|
|
58
|
+
}), [
|
|
59
|
+
open,
|
|
60
|
+
disabled
|
|
61
|
+
]);
|
|
62
|
+
return useMemo(()=>({
|
|
63
|
+
open,
|
|
64
|
+
setOpen,
|
|
65
|
+
disabled,
|
|
66
|
+
stateProps,
|
|
67
|
+
triggerAriaProps: elementProps({
|
|
68
|
+
"aria-expanded": open,
|
|
69
|
+
"aria-controls": contentId,
|
|
70
|
+
"aria-disabled": disabled
|
|
71
|
+
}),
|
|
72
|
+
triggerHandlers: elementProps({
|
|
73
|
+
onClick: (event)=>{
|
|
74
|
+
if (event.defaultPrevented) return;
|
|
75
|
+
if (disabled) return;
|
|
76
|
+
setOpen((prev)=>!prev);
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
contentProps: elementProps({
|
|
80
|
+
...stateProps,
|
|
81
|
+
id: contentId,
|
|
82
|
+
hidden,
|
|
83
|
+
style: {
|
|
84
|
+
"--collapsible-content-height": height !== undefined ? panelHeight : undefined
|
|
85
|
+
},
|
|
86
|
+
onTransitionEnd: (event)=>{
|
|
87
|
+
if (event.propertyName !== "height") return;
|
|
88
|
+
if (open) return;
|
|
89
|
+
setVisible(false);
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
refs: {
|
|
93
|
+
content: contentRef
|
|
94
|
+
}
|
|
95
|
+
}), [
|
|
96
|
+
open,
|
|
97
|
+
setOpen,
|
|
98
|
+
disabled,
|
|
99
|
+
stateProps,
|
|
100
|
+
contentId,
|
|
101
|
+
hidden,
|
|
102
|
+
height,
|
|
103
|
+
panelHeight
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const CollapsibleContext = /*#__PURE__*/ createContext(null);
|
|
108
|
+
const CollapsibleProvider = CollapsibleContext.Provider;
|
|
109
|
+
function useCollapsibleContext({ strict = true } = {}) {
|
|
110
|
+
const context = useContext(CollapsibleContext);
|
|
111
|
+
if (!context && strict) {
|
|
112
|
+
throw new Error("useCollapsibleContext must be used within a CollapsibleRoot");
|
|
113
|
+
}
|
|
114
|
+
return context;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const CollapsibleRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
118
|
+
const { open, defaultOpen, onOpenChange, disabled, ...otherProps } = props;
|
|
119
|
+
const api = useCollapsible({
|
|
120
|
+
open,
|
|
121
|
+
defaultOpen,
|
|
122
|
+
onOpenChange,
|
|
123
|
+
disabled
|
|
124
|
+
});
|
|
125
|
+
return /*#__PURE__*/ jsx(CollapsibleProvider, {
|
|
126
|
+
value: api,
|
|
127
|
+
children: /*#__PURE__*/ jsx(Primitive.div, {
|
|
128
|
+
ref: ref,
|
|
129
|
+
...mergeProps(api.stateProps, otherProps)
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
CollapsibleRoot.displayName = "CollapsibleRoot";
|
|
134
|
+
const CollapsibleTrigger = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
135
|
+
const api = useCollapsibleContext();
|
|
136
|
+
return /*#__PURE__*/ jsx(Primitive.button, {
|
|
137
|
+
ref: ref,
|
|
138
|
+
...mergeProps(api.stateProps, api.triggerAriaProps, api.triggerHandlers, props)
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
CollapsibleTrigger.displayName = "CollapsibleTrigger";
|
|
142
|
+
const CollapsibleContent = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
143
|
+
const api = useCollapsibleContext();
|
|
144
|
+
return /*#__PURE__*/ jsx(Primitive.div, {
|
|
145
|
+
ref: composeRefs(ref, api.refs.content),
|
|
146
|
+
...mergeProps(api.contentProps, props)
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
CollapsibleContent.displayName = "CollapsibleContent";
|
|
150
|
+
|
|
151
|
+
export { CollapsibleContent as C, CollapsibleRoot as a, CollapsibleTrigger as b, CollapsibleProvider as c, useCollapsible as d, useCollapsibleContext as u };
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var Collapsible12s = require('./Collapsible-12s-B5Klt2JP.cjs');
|
|
2
|
+
|
|
3
|
+
var Collapsible_namespace = {
|
|
4
|
+
__proto__: null,
|
|
5
|
+
Content: Collapsible12s.CollapsibleContent,
|
|
6
|
+
Root: Collapsible12s.CollapsibleRoot,
|
|
7
|
+
Trigger: Collapsible12s.CollapsibleTrigger
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
exports.CollapsibleContent = Collapsible12s.CollapsibleContent;
|
|
11
|
+
exports.CollapsibleProvider = Collapsible12s.CollapsibleProvider;
|
|
12
|
+
exports.CollapsibleRoot = Collapsible12s.CollapsibleRoot;
|
|
13
|
+
exports.CollapsibleTrigger = Collapsible12s.CollapsibleTrigger;
|
|
14
|
+
exports.useCollapsible = Collapsible12s.useCollapsible;
|
|
15
|
+
exports.useCollapsibleContext = Collapsible12s.useCollapsibleContext;
|
|
16
|
+
exports.Collapsible = Collapsible_namespace;
|