@mks2508/mks-ui 0.2.1 → 0.3.1
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/dist/react-ui/hooks/Animation/UseAutoHeight.js +7 -7
- package/dist/react-ui/hooks/DOM/UseIsInView.js +3 -3
- package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts +49 -0
- package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts.map +1 -0
- package/dist/react-ui/hooks/Formatting/UseListFormat.js +105 -0
- package/dist/react-ui/hooks/State/UseControlledState.js +4 -4
- package/dist/react-ui/hooks/State/UseDataState.js +5 -5
- package/dist/react-ui/hooks/index.d.ts +2 -0
- package/dist/react-ui/hooks/index.d.ts.map +1 -1
- package/dist/react-ui/hooks/index.js +1 -0
- package/dist/react-ui/index.js +22 -2
- package/dist/react-ui/lib/get-strict-context.js +3 -3
- package/dist/react-ui/primitives/CountingNumber/index.js +3 -3
- package/dist/react-ui/primitives/Highlight/index.js +26 -26
- package/dist/react-ui/primitives/Slot/index.js +3 -3
- package/dist/react-ui/primitives/index.d.ts +1 -0
- package/dist/react-ui/primitives/index.d.ts.map +1 -1
- package/dist/react-ui/primitives/index.js +18 -0
- package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts +76 -0
- package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts +11 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.js +19 -0
- package/dist/react-ui/primitives/waapi/Morph/index.d.ts +23 -0
- package/dist/react-ui/primitives/waapi/Morph/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/index.js +45 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts +12 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts +38 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.js +78 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts +23 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.js +140 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts +28 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.js +77 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts +27 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.js +86 -0
- package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts +168 -0
- package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.d.ts +25 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.js +186 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts +26 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.js +48 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.js +137 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts +47 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.js +72 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts +10 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts +74 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.js +354 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts +25 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts +57 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts +26 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.js +105 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts +156 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.js +180 -0
- package/dist/react-ui/primitives/waapi/core/index.d.ts +16 -0
- package/dist/react-ui/primitives/waapi/core/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/index.js +5 -0
- package/dist/react-ui/primitives/waapi/core/types.d.ts +143 -0
- package/dist/react-ui/primitives/waapi/core/types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts +32 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.js +322 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts +21 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.js +65 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts +20 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.js +99 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts +24 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.js +75 -0
- package/dist/react-ui/primitives/waapi/index.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/index.js +18 -0
- package/dist/react-ui/ui/Accordion/index.js +3 -3
- package/dist/react-ui/ui/Button/index.js +8 -8
- package/dist/react-ui/ui/Combobox/index.js +2 -2
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +35 -0
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/DataCard.styles.js +114 -0
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +135 -0
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/index.d.ts +129 -0
- package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/index.js +276 -0
- package/dist/react-ui/ui/Menu/index.js +2 -2
- package/dist/react-ui/ui/Switch/index.js +3 -3
- package/dist/react-ui/ui/Tabs/index.js +3 -3
- package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts +16 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts +101 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/index.d.ts +26 -0
- package/dist/react-ui/ui/TextFlow/index.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/index.js +187 -0
- package/dist/react-ui/ui/index.d.ts +2 -1
- package/dist/react-ui/ui/index.d.ts.map +1 -1
- package/dist/react-ui/ui/index.js +3 -1
- package/package.json +6 -2
- package/src/react-ui/hooks/Formatting/UseListFormat.ts +134 -0
- package/src/react-ui/hooks/index.ts +3 -0
- package/src/react-ui/primitives/index.ts +3 -0
- package/src/react-ui/primitives/waapi/Morph/Morph.types.ts +106 -0
- package/src/react-ui/primitives/waapi/Morph/MorphContext.tsx +21 -0
- package/src/react-ui/primitives/waapi/Morph/index.tsx +56 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/index.ts +12 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.ts +88 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.ts +175 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.ts +86 -0
- package/src/react-ui/primitives/waapi/Morph/useMorph.ts +100 -0
- package/src/react-ui/primitives/waapi/Reorder/Reorder.types.ts +177 -0
- package/src/react-ui/primitives/waapi/Reorder/index.tsx +260 -0
- package/src/react-ui/primitives/waapi/Reorder/useReorder.ts +46 -0
- package/src/react-ui/primitives/waapi/Reorder/useReorderPresence.ts +208 -0
- package/src/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.ts +104 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.ts +14 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.ts +84 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/index.tsx +474 -0
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.styles.ts +32 -0
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.types.ts +69 -0
- package/src/react-ui/primitives/waapi/SlidingText/index.tsx +140 -0
- package/src/react-ui/primitives/waapi/core/animationConstants.ts +215 -0
- package/src/react-ui/primitives/waapi/core/index.ts +53 -0
- package/src/react-ui/primitives/waapi/core/types.ts +200 -0
- package/src/react-ui/primitives/waapi/core/useAnimationOrchestrator.ts +429 -0
- package/src/react-ui/primitives/waapi/core/useElementRegistry.ts +80 -0
- package/src/react-ui/primitives/waapi/core/useFLIPAnimation.ts +137 -0
- package/src/react-ui/primitives/waapi/core/usePositionCapture.ts +105 -0
- package/src/react-ui/primitives/waapi/index.ts +116 -0
- package/src/react-ui/styles/animations.css +369 -0
- package/src/react-ui/ui/DataCard/DataCard.styles.ts +150 -0
- package/src/react-ui/ui/DataCard/DataCard.types.ts +146 -0
- package/src/react-ui/ui/DataCard/index.tsx +406 -0
- package/src/react-ui/ui/TextFlow/TextFlow.styles.ts +36 -0
- package/src/react-ui/ui/TextFlow/TextFlow.types.ts +118 -0
- package/src/react-ui/ui/TextFlow/index.tsx +276 -0
- package/src/react-ui/ui/index.ts +4 -1
- /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-CgbYV_HS.css → morphing-popover.module-BycNI8nU.css} +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { SlidingText } from "../../primitives/waapi/SlidingText/index.js";
|
|
5
|
+
import { ReorderRoot } from "../../primitives/waapi/Reorder/index.js";
|
|
6
|
+
import { useListFormat } from "../../hooks/Formatting/UseListFormat.js";
|
|
7
|
+
import React, { useCallback, useMemo } from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
//#region src/react-ui/ui/TextFlow/index.tsx
|
|
11
|
+
const DEFAULT_SEPARATOR = {
|
|
12
|
+
listType: "conjunction",
|
|
13
|
+
listStyle: "long",
|
|
14
|
+
transition: "none",
|
|
15
|
+
duration: 200
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_OVERFLOW = {
|
|
18
|
+
prefix: "+",
|
|
19
|
+
label: " more",
|
|
20
|
+
showSeparator: true
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_ANIMATION = {
|
|
23
|
+
mode: "character",
|
|
24
|
+
direction: "vertical",
|
|
25
|
+
stagger: 15,
|
|
26
|
+
duration: 200,
|
|
27
|
+
blur: true,
|
|
28
|
+
widthAnimation: false
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* TextFlow - Locale-aware animated token list component
|
|
32
|
+
*
|
|
33
|
+
* Built on the Reorder primitive, provides smooth FLIP animations for token lists
|
|
34
|
+
* with proper locale-aware separators using Intl.ListFormat.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* // Basic usage
|
|
39
|
+
* <TextFlow tokens={tokens} />
|
|
40
|
+
*
|
|
41
|
+
* // Spanish multiselect with transition
|
|
42
|
+
* <TextFlow
|
|
43
|
+
* tokens={tokens}
|
|
44
|
+
* maxVisible={2}
|
|
45
|
+
* inline
|
|
46
|
+
* separator={{ locale: 'es', transition: 'fade' }}
|
|
47
|
+
* overflow={{ prefix: '+', label: ' más' }}
|
|
48
|
+
* />
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
const TextFlow = ({ tokens, placeholder = "No tokens", maxVisible, separator: separatorConfig, overflow: overflowConfig, animation: animationConfig, inline = false, className = "", tokenClassName = "", placeholderClassName = "" }) => {
|
|
52
|
+
const separator = {
|
|
53
|
+
...DEFAULT_SEPARATOR,
|
|
54
|
+
...separatorConfig
|
|
55
|
+
};
|
|
56
|
+
const overflow = {
|
|
57
|
+
...DEFAULT_OVERFLOW,
|
|
58
|
+
...overflowConfig
|
|
59
|
+
};
|
|
60
|
+
const animation = {
|
|
61
|
+
...DEFAULT_ANIMATION,
|
|
62
|
+
...animationConfig
|
|
63
|
+
};
|
|
64
|
+
const exitingIdsRef = React.useRef(/* @__PURE__ */ new Set());
|
|
65
|
+
const visibleTokens = useMemo(() => maxVisible ? tokens.slice(0, maxVisible) : tokens, [tokens, maxVisible]);
|
|
66
|
+
const overflowCount = useMemo(() => maxVisible ? Math.max(0, tokens.length - maxVisible) : 0, [tokens.length, maxVisible]);
|
|
67
|
+
const listParts = useListFormat(visibleTokens.map((t) => t.text), {
|
|
68
|
+
locale: separator.locale,
|
|
69
|
+
type: separator.listType,
|
|
70
|
+
style: separator.listStyle,
|
|
71
|
+
separator: separator.value,
|
|
72
|
+
hasOverflow: overflowCount > 0
|
|
73
|
+
});
|
|
74
|
+
const shouldShowSeparator = useCallback((tokenIndex) => {
|
|
75
|
+
return !(tokenIndex >= visibleTokens.length - 1);
|
|
76
|
+
}, [visibleTokens.length]);
|
|
77
|
+
const handleItemExit = useCallback((id) => {
|
|
78
|
+
exitingIdsRef.current.add(id);
|
|
79
|
+
}, []);
|
|
80
|
+
const handleItemEnter = useCallback((id) => {}, []);
|
|
81
|
+
const showPlaceholder = tokens.length === 0 && !!placeholder;
|
|
82
|
+
const tokenElements = useMemo(() => {
|
|
83
|
+
let elementIndex = 0;
|
|
84
|
+
return listParts.map((part, partIndex) => {
|
|
85
|
+
if (part.type === "element") {
|
|
86
|
+
const token = visibleTokens[part.index];
|
|
87
|
+
if (!token) return null;
|
|
88
|
+
const isExiting = exitingIdsRef.current.has(token.id);
|
|
89
|
+
elementIndex++;
|
|
90
|
+
return /* @__PURE__ */ jsx("span", {
|
|
91
|
+
className: cn("waapi-token-wrapper", tokenClassName),
|
|
92
|
+
"data-reorder-id": token.id,
|
|
93
|
+
children: /* @__PURE__ */ jsx(SlidingText, {
|
|
94
|
+
text: token.text,
|
|
95
|
+
mode: isExiting ? "none" : animation.mode,
|
|
96
|
+
direction: animation.direction,
|
|
97
|
+
staggerDelay: animation.stagger,
|
|
98
|
+
duration: animation.duration,
|
|
99
|
+
blur: animation.blur,
|
|
100
|
+
widthAnimation: !isExiting && animation.widthAnimation,
|
|
101
|
+
initial: isExiting ? false : "initial",
|
|
102
|
+
animate: "animate"
|
|
103
|
+
})
|
|
104
|
+
}, token.id);
|
|
105
|
+
}
|
|
106
|
+
const prevToken = visibleTokens[part.index];
|
|
107
|
+
if (!prevToken) return null;
|
|
108
|
+
const tokenIndex = part.index;
|
|
109
|
+
if (!shouldShowSeparator(tokenIndex)) return null;
|
|
110
|
+
const separatorKey = `sep-${prevToken.id}`;
|
|
111
|
+
return /* @__PURE__ */ jsx("span", {
|
|
112
|
+
className: cn("waapi-token-separator", separator.className ?? ""),
|
|
113
|
+
children: part.value
|
|
114
|
+
}, separatorKey);
|
|
115
|
+
}).filter(Boolean);
|
|
116
|
+
}, [
|
|
117
|
+
listParts,
|
|
118
|
+
visibleTokens,
|
|
119
|
+
tokenClassName,
|
|
120
|
+
animation,
|
|
121
|
+
separator,
|
|
122
|
+
shouldShowSeparator
|
|
123
|
+
]);
|
|
124
|
+
const overflowElement = useMemo(() => {
|
|
125
|
+
if (overflowCount === 0) return null;
|
|
126
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
127
|
+
"data-reorder-id": "overflow-counter",
|
|
128
|
+
className: cn("waapi-token-overflow", overflow.className ?? ""),
|
|
129
|
+
children: [
|
|
130
|
+
overflow.showSeparator && overflow.separator && /* @__PURE__ */ jsx("span", {
|
|
131
|
+
className: cn("waapi-token-separator", separator.className ?? ""),
|
|
132
|
+
children: overflow.separator
|
|
133
|
+
}),
|
|
134
|
+
overflow.prefix !== "" && /* @__PURE__ */ jsx("span", {
|
|
135
|
+
className: cn("waapi-token-separator", separator.className ?? ""),
|
|
136
|
+
children: overflow.prefix
|
|
137
|
+
}),
|
|
138
|
+
/* @__PURE__ */ jsx(SlidingText, {
|
|
139
|
+
text: String(overflowCount),
|
|
140
|
+
mode: "character",
|
|
141
|
+
duration: animation.duration,
|
|
142
|
+
blur: animation.blur,
|
|
143
|
+
initial: "initial",
|
|
144
|
+
animate: "animate"
|
|
145
|
+
}),
|
|
146
|
+
overflow.label && overflow.label !== "" && /* @__PURE__ */ jsx(SlidingText, {
|
|
147
|
+
text: overflow.label,
|
|
148
|
+
mode: animation.mode,
|
|
149
|
+
direction: animation.direction,
|
|
150
|
+
staggerDelay: animation.stagger,
|
|
151
|
+
duration: animation.duration,
|
|
152
|
+
blur: animation.blur,
|
|
153
|
+
initial: "initial",
|
|
154
|
+
animate: "animate"
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
}, "overflow-counter");
|
|
158
|
+
}, [
|
|
159
|
+
overflowCount,
|
|
160
|
+
overflow,
|
|
161
|
+
separator,
|
|
162
|
+
animation
|
|
163
|
+
]);
|
|
164
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
165
|
+
role: "text",
|
|
166
|
+
className: cn("waapi-animated-tokens-container", inline && "waapi-animated-tokens-container--inline", className),
|
|
167
|
+
children: [showPlaceholder && /* @__PURE__ */ jsx(SlidingText, {
|
|
168
|
+
text: placeholder,
|
|
169
|
+
mode: "word",
|
|
170
|
+
direction: "vertical",
|
|
171
|
+
blur: true,
|
|
172
|
+
duration: 150,
|
|
173
|
+
initial: "initial",
|
|
174
|
+
animate: "animate",
|
|
175
|
+
className: cn("waapi-token-placeholder", placeholderClassName)
|
|
176
|
+
}, "placeholder"), /* @__PURE__ */ jsxs(ReorderRoot, {
|
|
177
|
+
layout: inline ? "inline-horizontal" : "horizontal",
|
|
178
|
+
onItemExit: handleItemExit,
|
|
179
|
+
onItemEnter: handleItemEnter,
|
|
180
|
+
children: [tokenElements, overflowElement]
|
|
181
|
+
})]
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
TextFlow.displayName = "TextFlow";
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
export { TextFlow };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react-ui/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react-ui/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAG3B,cAAc,YAAY,CAAC"}
|
|
@@ -23,7 +23,6 @@ import { Button } from "./Button/index.js";
|
|
|
23
23
|
import { cardVariants } from "./Card/Card.styles.js";
|
|
24
24
|
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./Card/index.js";
|
|
25
25
|
import { Input } from "./Input/index.js";
|
|
26
|
-
import { Textarea } from "./Textarea/index.js";
|
|
27
26
|
import { inputGroupAddonVariants, inputGroupButtonVariants } from "./InputGroup/InputGroup.styles.js";
|
|
28
27
|
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea } from "./InputGroup/index.js";
|
|
29
28
|
import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator, ComboboxTrigger, ComboboxValue, useComboboxAnchor } from "./Combobox/index.js";
|
|
@@ -35,3 +34,6 @@ import { Separator } from "./Separator/index.js";
|
|
|
35
34
|
import { fieldVariants } from "./Field/Field.styles.js";
|
|
36
35
|
import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle } from "./Field/index.js";
|
|
37
36
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue } from "./Select/index.js";
|
|
37
|
+
import { dataCardStyles, dataCardVariants } from "./DataCard/DataCard.styles.js";
|
|
38
|
+
import { DataCard, DataCardActions, DataCardBracket, DataCardLabel, DataCardToggle, DataCardValue, useDataCard } from "./DataCard/index.js";
|
|
39
|
+
import { TextFlow } from "./TextFlow/index.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mks2508/mks-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "UI component library - Shadcn/Animate UI based with DevEnv components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
"./react/ui/*": "./src/react-ui/ui/*/index.tsx",
|
|
21
21
|
"./react/primitives/*": "./src/react-ui/primitives/*/index.tsx",
|
|
22
22
|
"./react/components/*": "./src/react-ui/components/*/index.tsx",
|
|
23
|
-
"./react/hooks/*": "./src/react-ui/hooks
|
|
23
|
+
"./react/hooks/*": "./src/react-ui/hooks/*/*.ts",
|
|
24
|
+
"./react/hooks/Animation/*": "./src/react-ui/hooks/Animation/*.tsx",
|
|
25
|
+
"./react/hooks/DOM/*": "./src/react-ui/hooks/DOM/*.tsx",
|
|
26
|
+
"./react/hooks/State/*": "./src/react-ui/hooks/State/*.tsx",
|
|
27
|
+
"./react/hooks/Formatting/*": "./src/react-ui/hooks/Formatting/*.ts",
|
|
24
28
|
"./react/icons/*": "./src/react-ui/icons/*.tsx",
|
|
25
29
|
"./react/lib/*": "./src/react-ui/lib/*.ts"
|
|
26
30
|
},
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/** Intl.ListFormat conjunction/disjunction/unit type */
|
|
4
|
+
export type ListFormatType = 'conjunction' | 'disjunction' | 'unit';
|
|
5
|
+
|
|
6
|
+
/** Intl.ListFormat style: long, short, or narrow */
|
|
7
|
+
export type ListFormatStyle = 'long' | 'short' | 'narrow';
|
|
8
|
+
|
|
9
|
+
/** Options for useListFormat hook */
|
|
10
|
+
export interface IUseListFormatOptions {
|
|
11
|
+
locale?: string;
|
|
12
|
+
type?: ListFormatType;
|
|
13
|
+
style?: ListFormatStyle;
|
|
14
|
+
separator?: string;
|
|
15
|
+
/** If true, format as if there's one more item (avoids "and" before overflow counter) */
|
|
16
|
+
hasOverflow?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A single part of a formatted list (element or separator) */
|
|
20
|
+
export interface IListPart {
|
|
21
|
+
type: 'element' | 'literal';
|
|
22
|
+
value: string;
|
|
23
|
+
index: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getDefaultLocale = (): string => {
|
|
27
|
+
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
28
|
+
return navigator.language;
|
|
29
|
+
}
|
|
30
|
+
return 'en';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook for locale-aware list formatting using Intl.ListFormat.
|
|
35
|
+
*
|
|
36
|
+
* Provides parts that can be rendered individually with animations.
|
|
37
|
+
* Uses the browser's Intl.ListFormat API for proper locale handling.
|
|
38
|
+
*
|
|
39
|
+
* @param items - Array of strings to format
|
|
40
|
+
* @param options - Locale, type, style, separator override, overflow flag
|
|
41
|
+
* @returns Array of list parts (elements and literals)
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* // Automatic locale detection
|
|
46
|
+
* const parts = useListFormat(['Apple', 'Banana', 'Cherry']);
|
|
47
|
+
* // en: [{ type: 'element', value: 'Apple' }, { type: 'literal', value: ', ' }, ...]
|
|
48
|
+
*
|
|
49
|
+
* // Spanish
|
|
50
|
+
* const parts = useListFormat(['Apple', 'Banana'], { locale: 'es' });
|
|
51
|
+
*
|
|
52
|
+
* // Disjunction ("or")
|
|
53
|
+
* const parts = useListFormat(['A', 'B', 'C'], { type: 'disjunction' });
|
|
54
|
+
* // en: "A, B, or C"
|
|
55
|
+
*
|
|
56
|
+
* // Manual separator override
|
|
57
|
+
* const parts = useListFormat(['A', 'B', 'C'], { separator: ' | ' });
|
|
58
|
+
* // "A | B | C"
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function useListFormat(
|
|
62
|
+
items: string[],
|
|
63
|
+
options: IUseListFormatOptions = {}
|
|
64
|
+
): IListPart[] {
|
|
65
|
+
const {
|
|
66
|
+
locale,
|
|
67
|
+
type = 'conjunction',
|
|
68
|
+
style = 'long',
|
|
69
|
+
separator,
|
|
70
|
+
hasOverflow
|
|
71
|
+
} = options;
|
|
72
|
+
|
|
73
|
+
return useMemo(() => {
|
|
74
|
+
if (items.length === 0) return [];
|
|
75
|
+
|
|
76
|
+
// Manual override mode: use fixed separator
|
|
77
|
+
if (separator !== undefined) {
|
|
78
|
+
const parts: IListPart[] = [];
|
|
79
|
+
items.forEach((item, i) => {
|
|
80
|
+
parts.push({ type: 'element', value: item, index: i });
|
|
81
|
+
if (i < items.length - 1) {
|
|
82
|
+
parts.push({ type: 'literal', value: separator, index: i });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return parts;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Use Intl.ListFormat for locale-aware formatting
|
|
89
|
+
const resolvedLocale = locale ?? getDefaultLocale();
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const formatter = new Intl.ListFormat(resolvedLocale, { type, style });
|
|
93
|
+
|
|
94
|
+
// If hasOverflow, add a placeholder to make Intl.ListFormat use commas
|
|
95
|
+
// instead of "and" before the overflow counter
|
|
96
|
+
const itemsToFormat = (hasOverflow && items.length > 1)
|
|
97
|
+
? [...items, '']
|
|
98
|
+
: items;
|
|
99
|
+
|
|
100
|
+
const formatted = formatter.formatToParts(itemsToFormat);
|
|
101
|
+
|
|
102
|
+
// If hasOverflow, remove the last part (placeholder and its separator)
|
|
103
|
+
const partsToProcess = hasOverflow
|
|
104
|
+
? formatted.slice(0, -2)
|
|
105
|
+
: formatted;
|
|
106
|
+
|
|
107
|
+
let elementIndex = 0;
|
|
108
|
+
return partsToProcess.map((part): IListPart => {
|
|
109
|
+
if (part.type === 'element') {
|
|
110
|
+
return {
|
|
111
|
+
type: 'element',
|
|
112
|
+
value: part.value,
|
|
113
|
+
index: elementIndex++
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
type: 'literal',
|
|
118
|
+
value: part.value,
|
|
119
|
+
index: Math.max(0, elementIndex - 1)
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
} catch {
|
|
123
|
+
// Fallback if Intl.ListFormat fails
|
|
124
|
+
const parts: IListPart[] = [];
|
|
125
|
+
items.forEach((item, i) => {
|
|
126
|
+
parts.push({ type: 'element', value: item, index: i });
|
|
127
|
+
if (i < items.length - 1) {
|
|
128
|
+
parts.push({ type: 'literal', value: ', ', index: i });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return parts;
|
|
132
|
+
}
|
|
133
|
+
}, [items, locale, type, style, separator, hasOverflow]);
|
|
134
|
+
}
|
|
@@ -15,3 +15,6 @@ export type { DataStateValue } from './State/UseDataState';
|
|
|
15
15
|
|
|
16
16
|
export { useIsInView } from './DOM/UseIsInView';
|
|
17
17
|
export type { IUseIsInViewOptions } from './DOM/UseIsInView';
|
|
18
|
+
|
|
19
|
+
export { useListFormat } from './Formatting/UseListFormat';
|
|
20
|
+
export type { IUseListFormatOptions, IListPart, ListFormatType, ListFormatStyle } from './Formatting/UseListFormat';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Available morphing techniques.
|
|
5
|
+
*/
|
|
6
|
+
export type MorphTechnique = 'flip-clip-path' | 'css-grid' | 'view-transitions';
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// FLIP + clip-path
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/** Options for the FLIP + clip-path morphing technique */
|
|
13
|
+
export interface IFLIPClipPathOptions {
|
|
14
|
+
duration?: number;
|
|
15
|
+
easing?: string;
|
|
16
|
+
clipPathStart?: string;
|
|
17
|
+
clipPathEnd?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** API returned by useFLIPClipPath */
|
|
21
|
+
export interface IFLIPClipPathAPI {
|
|
22
|
+
isMorphing: boolean;
|
|
23
|
+
morph: (fromElement: HTMLElement, toElement: HTMLElement) => Promise<void>;
|
|
24
|
+
cancel: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// CSS Grid
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/** Options for the CSS Grid expand/collapse technique */
|
|
32
|
+
export interface ICSSGridMorphOptions {
|
|
33
|
+
duration?: number;
|
|
34
|
+
easing?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** API returned by useCSSGridMorph */
|
|
38
|
+
export interface ICSSGridMorphAPI {
|
|
39
|
+
isExpanded: boolean;
|
|
40
|
+
expand: () => void;
|
|
41
|
+
collapse: () => void;
|
|
42
|
+
toggle: () => void;
|
|
43
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// View Transitions
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/** Options for the View Transitions API wrapper */
|
|
51
|
+
export interface IViewTransitionsOptions {
|
|
52
|
+
name?: string;
|
|
53
|
+
types?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** API returned by useViewTransitions */
|
|
57
|
+
export interface IViewTransitionsAPI {
|
|
58
|
+
isSupported: boolean;
|
|
59
|
+
startTransition: (callback: () => void | Promise<void>) => Promise<void>;
|
|
60
|
+
setTypes: (types: string[]) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Unified Morph
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
/** Configuration for useMorph hook */
|
|
68
|
+
export interface IUseMorphConfig {
|
|
69
|
+
technique?: MorphTechnique;
|
|
70
|
+
duration?: number;
|
|
71
|
+
easing?: string;
|
|
72
|
+
onMorphStart?: () => void;
|
|
73
|
+
onMorphEnd?: () => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Return type for useMorph hook */
|
|
77
|
+
export interface IUseMorphReturn {
|
|
78
|
+
isMorphing: boolean;
|
|
79
|
+
technique: MorphTechnique;
|
|
80
|
+
isViewTransitionsSupported: boolean;
|
|
81
|
+
morph: (fromElement: HTMLElement, toElement: HTMLElement) => Promise<void>;
|
|
82
|
+
cancel: () => void;
|
|
83
|
+
flipClipPath: IFLIPClipPathAPI;
|
|
84
|
+
cssGrid: ICSSGridMorphAPI;
|
|
85
|
+
viewTransitions: IViewTransitionsAPI;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// Component Props
|
|
90
|
+
// =============================================================================
|
|
91
|
+
|
|
92
|
+
/** Props for Morph container component */
|
|
93
|
+
export interface IMorphProps {
|
|
94
|
+
children: ReactNode;
|
|
95
|
+
technique?: MorphTechnique;
|
|
96
|
+
duration?: number;
|
|
97
|
+
easing?: string;
|
|
98
|
+
className?: string;
|
|
99
|
+
onMorphStart?: () => void;
|
|
100
|
+
onMorphEnd?: () => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Context value for Morph components */
|
|
104
|
+
export interface IMorphContextValue {
|
|
105
|
+
morph: IUseMorphReturn;
|
|
106
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext } from 'react';
|
|
4
|
+
import type { IMorphContextValue } from './Morph.types';
|
|
5
|
+
|
|
6
|
+
const MorphContext = createContext<IMorphContextValue | null>(null);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook to access morph context
|
|
10
|
+
*
|
|
11
|
+
* @throws Error if used outside of Morph provider
|
|
12
|
+
*/
|
|
13
|
+
export function useMorphContext(): IMorphContextValue {
|
|
14
|
+
const context = useContext(MorphContext);
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error('useMorphContext must be used within a Morph component');
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { MorphContext };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, type ReactNode } from 'react';
|
|
4
|
+
import { MorphContext } from './MorphContext';
|
|
5
|
+
import { useMorph } from './useMorph';
|
|
6
|
+
import type { IMorphProps, IMorphContextValue } from './Morph.types';
|
|
7
|
+
import { cn } from '@/react-ui/lib/utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Morph - Container component for morphable elements.
|
|
11
|
+
*
|
|
12
|
+
* Provides morphing capabilities to child components through context.
|
|
13
|
+
* Supports FLIP + clip-path, CSS Grid, and View Transitions techniques.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Morph technique="flip-clip-path" duration={300}>
|
|
18
|
+
* <button onClick={() => morph(fromRef.current, toRef.current)}>
|
|
19
|
+
* Morph
|
|
20
|
+
* </button>
|
|
21
|
+
* </Morph>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function Morph({
|
|
25
|
+
children,
|
|
26
|
+
technique = 'flip-clip-path',
|
|
27
|
+
duration,
|
|
28
|
+
easing,
|
|
29
|
+
className = '',
|
|
30
|
+
onMorphStart,
|
|
31
|
+
onMorphEnd
|
|
32
|
+
}: IMorphProps): ReactNode {
|
|
33
|
+
const morph = useMorph({
|
|
34
|
+
technique,
|
|
35
|
+
duration,
|
|
36
|
+
easing,
|
|
37
|
+
onMorphStart,
|
|
38
|
+
onMorphEnd
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const contextValue = useMemo<IMorphContextValue>(() => ({
|
|
42
|
+
morph
|
|
43
|
+
}), [morph]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<MorphContext.Provider value={contextValue}>
|
|
47
|
+
<div className={cn('morph-container', className)}>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
</MorphContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Morph.displayName = 'Morph';
|
|
55
|
+
|
|
56
|
+
export type { IMorphProps, IMorphContextValue };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Morph techniques.
|
|
3
|
+
*
|
|
4
|
+
* Individual morphing implementations that can be used
|
|
5
|
+
* standalone or composed through useMorph.
|
|
6
|
+
*
|
|
7
|
+
* @module primitives/waapi/Morph/techniques
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { useFLIPClipPath } from './useFLIPClipPath';
|
|
11
|
+
export { useCSSGridMorph } from './useCSSGridMorph';
|
|
12
|
+
export { useViewTransitions } from './useViewTransitions';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useRef, useCallback, useState } from 'react';
|
|
2
|
+
import { TIMING, EASINGS } from '../../core/animationConstants';
|
|
3
|
+
import type { ICSSGridMorphOptions, ICSSGridMorphAPI } from '../Morph.types';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DURATION = TIMING.FLIP_DURATION;
|
|
6
|
+
const DEFAULT_EASING = EASINGS.MATERIAL_STANDARD;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for CSS Grid-based expand/collapse animations.
|
|
10
|
+
*
|
|
11
|
+
* Uses the `grid-template-rows: 0fr/1fr` technique for smooth height
|
|
12
|
+
* animations without needing to know the content height in advance.
|
|
13
|
+
*
|
|
14
|
+
* CSS required on container:
|
|
15
|
+
* ```css
|
|
16
|
+
* .morph-container {
|
|
17
|
+
* display: grid;
|
|
18
|
+
* grid-template-rows: 0fr;
|
|
19
|
+
* transition: grid-template-rows 300ms ease;
|
|
20
|
+
* }
|
|
21
|
+
* .morph-container.expanded {
|
|
22
|
+
* grid-template-rows: 1fr;
|
|
23
|
+
* }
|
|
24
|
+
* .morph-content {
|
|
25
|
+
* overflow: hidden;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param options - Duration and easing overrides
|
|
30
|
+
* @returns Grid morph API with expand, collapse, toggle, and container ref
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const { isExpanded, toggle, containerRef } = useCSSGridMorph();
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <div ref={containerRef} className={isExpanded ? 'expanded' : ''}>
|
|
38
|
+
* <div className="morph-content">Collapsible content</div>
|
|
39
|
+
* </div>
|
|
40
|
+
* );
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function useCSSGridMorph(options?: ICSSGridMorphOptions): ICSSGridMorphAPI {
|
|
44
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
45
|
+
const containerRef = useRef<HTMLElement | null>(null);
|
|
46
|
+
const optionsRef = useRef(options);
|
|
47
|
+
|
|
48
|
+
const duration = optionsRef.current?.duration ?? DEFAULT_DURATION;
|
|
49
|
+
const easing = optionsRef.current?.easing ?? DEFAULT_EASING;
|
|
50
|
+
|
|
51
|
+
const applyTransition = useCallback(() => {
|
|
52
|
+
if (containerRef.current) {
|
|
53
|
+
containerRef.current.style.transition = `grid-template-rows ${duration}ms ${easing}`;
|
|
54
|
+
}
|
|
55
|
+
}, [duration, easing]);
|
|
56
|
+
|
|
57
|
+
const expand = useCallback(() => {
|
|
58
|
+
applyTransition();
|
|
59
|
+
if (containerRef.current) {
|
|
60
|
+
containerRef.current.style.gridTemplateRows = '1fr';
|
|
61
|
+
}
|
|
62
|
+
setIsExpanded(true);
|
|
63
|
+
}, [applyTransition]);
|
|
64
|
+
|
|
65
|
+
const collapse = useCallback(() => {
|
|
66
|
+
applyTransition();
|
|
67
|
+
if (containerRef.current) {
|
|
68
|
+
containerRef.current.style.gridTemplateRows = '0fr';
|
|
69
|
+
}
|
|
70
|
+
setIsExpanded(false);
|
|
71
|
+
}, [applyTransition]);
|
|
72
|
+
|
|
73
|
+
const toggle = useCallback(() => {
|
|
74
|
+
if (isExpanded) {
|
|
75
|
+
collapse();
|
|
76
|
+
} else {
|
|
77
|
+
expand();
|
|
78
|
+
}
|
|
79
|
+
}, [isExpanded, expand, collapse]);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
isExpanded,
|
|
83
|
+
expand,
|
|
84
|
+
collapse,
|
|
85
|
+
toggle,
|
|
86
|
+
containerRef
|
|
87
|
+
};
|
|
88
|
+
}
|