@musecat/uikit 0.1.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.
Files changed (180) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +51 -0
  3. package/index.ts +78 -0
  4. package/package.json +103 -0
  5. package/packages/Components/Box/Box.tsx +173 -0
  6. package/packages/Components/Box/Box.types.ts +48 -0
  7. package/packages/Components/Button/Button.tsx +275 -0
  8. package/packages/Components/Button/Button.types.ts +44 -0
  9. package/packages/Components/Card/Card.default.tsx +180 -0
  10. package/packages/Components/Card/Card.foldable.tsx +383 -0
  11. package/packages/Components/Card/Card.module.scss +23 -0
  12. package/packages/Components/Card/Card.tsx +8 -0
  13. package/packages/Components/Card/Card.types.ts +91 -0
  14. package/packages/Components/Card/Card.utils.ts +66 -0
  15. package/packages/Components/Checkbox/Checkbox.module.scss +11 -0
  16. package/packages/Components/Checkbox/Checkbox.tsx +104 -0
  17. package/packages/Components/Checkbox/Checkbox.types.ts +24 -0
  18. package/packages/Components/CodeBox/CodeBox.copy.tsx +43 -0
  19. package/packages/Components/CodeBox/CodeBox.module.scss +72 -0
  20. package/packages/Components/CodeBox/CodeBox.tsx +91 -0
  21. package/packages/Components/CodeBox/CodeBox.types.ts +17 -0
  22. package/packages/Components/ContextMenu/ContextMenu.module.scss +8 -0
  23. package/packages/Components/ContextMenu/ContextMenu.options.tsx +126 -0
  24. package/packages/Components/ContextMenu/ContextMenu.tsx +381 -0
  25. package/packages/Components/ContextMenu/ContextMenu.types.ts +46 -0
  26. package/packages/Components/ContributionGraph/ContributionGraph.module.scss +42 -0
  27. package/packages/Components/ContributionGraph/ContributionGraph.tsx +454 -0
  28. package/packages/Components/ContributionGraph/ContributionGraph.types.ts +27 -0
  29. package/packages/Components/DatePicker/DatePicker.calendar.tsx +161 -0
  30. package/packages/Components/DatePicker/DatePicker.core.tsx +582 -0
  31. package/packages/Components/DatePicker/DatePicker.module.scss +26 -0
  32. package/packages/Components/DatePicker/DatePicker.tsx +786 -0
  33. package/packages/Components/DatePicker/DatePicker.types.ts +36 -0
  34. package/packages/Components/DatePicker/DatePicker.utils.ts +80 -0
  35. package/packages/Components/DatePicker/hooks/useCalendar.ts +61 -0
  36. package/packages/Components/DatePicker/hooks/useSelection.ts +208 -0
  37. package/packages/Components/Divider/Divider.tsx +46 -0
  38. package/packages/Components/Divider/Divider.types.ts +12 -0
  39. package/packages/Components/Header/Header.module.scss +74 -0
  40. package/packages/Components/Header/Header.tsx +47 -0
  41. package/packages/Components/Header/Header.types.ts +8 -0
  42. package/packages/Components/Icon/Icon.group.tsx +122 -0
  43. package/packages/Components/Icon/Icon.group.types.ts +20 -0
  44. package/packages/Components/Icon/Icon.tsx +341 -0
  45. package/packages/Components/Icon/Icon.types.ts +59 -0
  46. package/packages/Components/Icon/SVG/data.tsx +232 -0
  47. package/packages/Components/Input/Input.module.scss +5 -0
  48. package/packages/Components/Input/Input.tsx +87 -0
  49. package/packages/Components/Input/Input.types.ts +14 -0
  50. package/packages/Components/Label/Label.module.scss +19 -0
  51. package/packages/Components/Label/Label.tsx +115 -0
  52. package/packages/Components/Label/Label.types.ts +28 -0
  53. package/packages/Components/Layout/Layout.docs.module.scss +42 -0
  54. package/packages/Components/Layout/Layout.docs.tsx +150 -0
  55. package/packages/Components/Layout/Layout.docs.types.ts +18 -0
  56. package/packages/Components/Layout/Layout.module.scss +91 -0
  57. package/packages/Components/Layout/Layout.tsx +306 -0
  58. package/packages/Components/Layout/Layout.types.ts +71 -0
  59. package/packages/Components/Maps/OSM/MapOSM.module.scss +11 -0
  60. package/packages/Components/Maps/OSM/MapOSM.shared.ts +121 -0
  61. package/packages/Components/Maps/OSM/MapOSM.tsx +279 -0
  62. package/packages/Components/Maps/OSM/MapOSM.types.ts +18 -0
  63. package/packages/Components/Nav/Nav.module.scss +137 -0
  64. package/packages/Components/Nav/Nav.tsx +304 -0
  65. package/packages/Components/Nav/Nav.types.ts +32 -0
  66. package/packages/Components/Nav/hooks/useNavDrag.ts +374 -0
  67. package/packages/Components/Nav/hooks/useNavIndicator.ts +137 -0
  68. package/packages/Components/Pagination/Pagination.tsx +254 -0
  69. package/packages/Components/Pagination/Pagination.types.ts +18 -0
  70. package/packages/Components/Pill/Pill.tsx +127 -0
  71. package/packages/Components/Pill/Pill.types.ts +33 -0
  72. package/packages/Components/Profile/Profile.shared.ts +74 -0
  73. package/packages/Components/Profile/Profile.tsx +382 -0
  74. package/packages/Components/Profile/Profile.types.ts +55 -0
  75. package/packages/Components/Progress/Progress.module.scss +15 -0
  76. package/packages/Components/Progress/Progress.tsx +88 -0
  77. package/packages/Components/Progress/Progress.types.ts +22 -0
  78. package/packages/Components/Radio/Radio.module.scss +11 -0
  79. package/packages/Components/Radio/Radio.tsx +121 -0
  80. package/packages/Components/Radio/Radio.types.ts +20 -0
  81. package/packages/Components/Select/Select.keyboard.tsx +76 -0
  82. package/packages/Components/Select/Select.trigger.tsx +88 -0
  83. package/packages/Components/Select/Select.tsx +11 -0
  84. package/packages/Components/Select/Select.types.ts +38 -0
  85. package/packages/Components/Select/Select.utils.ts +84 -0
  86. package/packages/Components/Select/hooks/useSelectNavigation.ts +461 -0
  87. package/packages/Components/Select/hooks/useSelectSelection.ts +142 -0
  88. package/packages/Components/Select/hooks/useSelectState.ts +314 -0
  89. package/packages/Components/Select/select.control.tsx +228 -0
  90. package/packages/Components/Select/select.inner.tsx +312 -0
  91. package/packages/Components/Skeleton/Skeleton.tsx +30 -0
  92. package/packages/Components/Skeleton/Skeleton.types.ts +12 -0
  93. package/packages/Components/Spinner/Spinner.module.scss +78 -0
  94. package/packages/Components/Spinner/Spinner.tsx +125 -0
  95. package/packages/Components/Spinner/Spinner.types.ts +19 -0
  96. package/packages/Components/Text/Text.module.scss +7 -0
  97. package/packages/Components/Text/Text.tsx +49 -0
  98. package/packages/Components/Text/Text.types.ts +33 -0
  99. package/packages/Components/TimePicker/TimePicker.core.tsx +448 -0
  100. package/packages/Components/TimePicker/TimePicker.module.scss +30 -0
  101. package/packages/Components/TimePicker/TimePicker.tsx +511 -0
  102. package/packages/Components/TimePicker/TimePicker.types.ts +22 -0
  103. package/packages/Components/Timeline/Timeline.tsx +111 -0
  104. package/packages/Components/Timeline/Timeline.types.ts +23 -0
  105. package/packages/Components/Title/Title.tsx +305 -0
  106. package/packages/Components/Title/Title.types.ts +64 -0
  107. package/packages/Components/Toggle/Toggle.module.scss +32 -0
  108. package/packages/Components/Toggle/Toggle.tsx +272 -0
  109. package/packages/Components/Toggle/Toggle.types.ts +17 -0
  110. package/packages/Components/Tooltip/Tooltip.module.scss +44 -0
  111. package/packages/Components/Tooltip/Tooltip.tsx +340 -0
  112. package/packages/Components/Tooltip/Tooltip.types.ts +38 -0
  113. package/packages/Frameworks/Dialog/Dialog.background.tsx +88 -0
  114. package/packages/Frameworks/Dialog/Dialog.boot.tsx +32 -0
  115. package/packages/Frameworks/Dialog/Dialog.store.ts +199 -0
  116. package/packages/Frameworks/Dialog/Dialog.tsx +185 -0
  117. package/packages/Frameworks/Dialog/Dialog.types.ts +264 -0
  118. package/packages/Frameworks/Dialog/Dialog.utils.ts +22 -0
  119. package/packages/Frameworks/Dialog/contents/Dialog.footer.tsx +79 -0
  120. package/packages/Frameworks/Dialog/contents/Dialog.funnel.tsx +260 -0
  121. package/packages/Frameworks/Dialog/contents/Dialog.header.tsx +137 -0
  122. package/packages/Frameworks/Dialog/hooks/useDialogPosition.ts +298 -0
  123. package/packages/Frameworks/Dialog/hooks/useSheetDrag.ts +325 -0
  124. package/packages/Frameworks/Dialog/hooks/useSheetGeometry.ts +201 -0
  125. package/packages/Frameworks/Dialog/hooks/useSheetProgressive.ts +98 -0
  126. package/packages/Frameworks/Dialog/renderers/renderModal.tsx +228 -0
  127. package/packages/Frameworks/Dialog/renderers/renderPopover.tsx +220 -0
  128. package/packages/Frameworks/Dialog/renderers/renderSheet.tsx +571 -0
  129. package/packages/Frameworks/EdgeEffect/EdgeEffect.tsx +52 -0
  130. package/packages/Frameworks/EdgeEffect/EdgeEffect.types.ts +5 -0
  131. package/packages/Frameworks/Motion/Motion.presets.ts +49 -0
  132. package/packages/Frameworks/Pressable/Pressable.module.scss +19 -0
  133. package/packages/Frameworks/Pressable/Pressable.tsx +651 -0
  134. package/packages/Frameworks/Pressable/Pressable.types.ts +73 -0
  135. package/packages/Frameworks/Squircle/Squircle.tsx +263 -0
  136. package/packages/Frameworks/Squircle/Squircle.types.ts +15 -0
  137. package/packages/Frameworks/Theme/Radius.types.ts +55 -0
  138. package/packages/Frameworks/Theme/Theme.types.ts +325 -0
  139. package/packages/Frameworks/Toaster/Toaster.boot.tsx +47 -0
  140. package/packages/Frameworks/Toaster/Toaster.module.scss +168 -0
  141. package/packages/Frameworks/Toaster/Toaster.types.ts +13 -0
  142. package/packages/Frameworks/View/DNDView/DNDView.module.scss +8 -0
  143. package/packages/Frameworks/View/DNDView/DNDView.tsx +470 -0
  144. package/packages/Frameworks/View/DNDView/DNDView.types.ts +61 -0
  145. package/packages/Frameworks/View/HScrollView/HScrollView.module.scss +59 -0
  146. package/packages/Frameworks/View/HScrollView/HScrollView.tsx +386 -0
  147. package/packages/Frameworks/View/HScrollView/HScrollView.types.ts +46 -0
  148. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.counter.tsx +27 -0
  149. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.footer.tsx +76 -0
  150. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.header.tsx +49 -0
  151. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.list.tsx +74 -0
  152. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.slide.tsx +71 -0
  153. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.tsx +175 -0
  154. package/packages/Frameworks/View/ImageView/Image.controls.tsx +39 -0
  155. package/packages/Frameworks/View/ImageView/Image.hooks.ts +82 -0
  156. package/packages/Frameworks/View/ImageView/Image.module.scss +7 -0
  157. package/packages/Frameworks/View/ImageView/Image.tsx +375 -0
  158. package/packages/Frameworks/View/ImageView/Image.types.ts +71 -0
  159. package/packages/Frameworks/View/ImageView/Image.utils.ts +79 -0
  160. package/packages/Frameworks/View/View.module.scss +34 -0
  161. package/packages/Frameworks/View/View.tsx +200 -0
  162. package/packages/Frameworks/View/View.types.ts +36 -0
  163. package/packages/Frameworks/_shared/Padding.types.ts +52 -0
  164. package/packages/Frameworks/_shared/StopParentInteraction.tsx +45 -0
  165. package/packages/Frameworks/_shared/Wind.types.ts +17 -0
  166. package/packages/Frameworks/_shared/bodyScrollLock.ts +127 -0
  167. package/packages/Frameworks/_shared/layer.constants.ts +8 -0
  168. package/packages/Frameworks/_shared/normalize.ts +39 -0
  169. package/packages/Frameworks/_shared/sizing.ts +55 -0
  170. package/packages/Frameworks/_shared/useControllableState.ts +29 -0
  171. package/packages/Styles/_animation.scss +42 -0
  172. package/packages/Styles/_color.scss +131 -0
  173. package/packages/Styles/_font.scss +30 -0
  174. package/packages/Styles/_icon.scss +782 -0
  175. package/packages/Styles/_importer.scss +9 -0
  176. package/packages/Styles/_system.scss +69 -0
  177. package/packages/Styles/_textstyle.scss +111 -0
  178. package/packages/Styles/_theme.scss +452 -0
  179. package/packages/Styles/_viewport-global.scss +99 -0
  180. package/packages/Styles/_viewport.scss +108 -0
@@ -0,0 +1,571 @@
1
+ "use client";
2
+
3
+ import {
4
+ animate,
5
+ useDragControls,
6
+ useMotionValue,
7
+ useTransform,
8
+ } from "motion/react";
9
+ import {
10
+ type ReactNode,
11
+ useCallback,
12
+ useEffect,
13
+ useRef,
14
+ useState,
15
+ } from "react";
16
+ import { useScrollLock } from "@/packages/Frameworks/_shared/bodyScrollLock";
17
+ import { LAYER_Z_INDEX } from "@/packages/Frameworks/_shared/layer.constants";
18
+ import { Size } from "@/packages/Frameworks/_shared/sizing";
19
+ import DialogFooter from "@/packages/Frameworks/Dialog/contents/Dialog.footer";
20
+ import DialogHeader from "@/packages/Frameworks/Dialog/contents/Dialog.header";
21
+ import BackgroundWrapper from "@/packages/Frameworks/Dialog/Dialog.background";
22
+ import type {
23
+ DialogOutsideOptions,
24
+ SheetConfig,
25
+ } from "@/packages/Frameworks/Dialog/Dialog.types";
26
+ import {
27
+ DialogPortal,
28
+ useEscapeClose,
29
+ } from "@/packages/Frameworks/Dialog/Dialog.utils";
30
+ import { useSheetDrag } from "@/packages/Frameworks/Dialog/hooks/useSheetDrag";
31
+ import { resolveInitialSnapIndex } from "@/packages/Frameworks/Dialog/hooks/useSheetGeometry";
32
+ import { useSheetGeometry } from "@/packages/Frameworks/Dialog/hooks/useSheetGeometry";
33
+ import { useSheetProgressive } from "@/packages/Frameworks/Dialog/hooks/useSheetProgressive";
34
+ import { motionTransitions } from "@/packages/Frameworks/Motion/Motion.presets";
35
+ import Pressable from "@/packages/Frameworks/Pressable/Pressable";
36
+ import View from "@/packages/Frameworks/View/View";
37
+
38
+ interface RenderSheetProps {
39
+ open: boolean;
40
+ onOpenChange: (open: boolean) => void;
41
+ config?: SheetConfig;
42
+ id?: string;
43
+ zIndex?: number;
44
+ width?: string | number;
45
+ height?: string | number;
46
+ children?: ReactNode;
47
+ role?: string;
48
+ "data-color-mode"?: string;
49
+ }
50
+
51
+ const usePrevious = <T,>(value: T): T | undefined => {
52
+ const ref = useRef<T | undefined>(undefined);
53
+ useEffect(() => {
54
+ ref.current = value;
55
+ });
56
+ return ref.current;
57
+ };
58
+
59
+ export default function RenderSheet({
60
+ open,
61
+ onOpenChange,
62
+ config,
63
+ id,
64
+ zIndex,
65
+ width,
66
+ height,
67
+ children,
68
+ role = "dialog",
69
+ "data-color-mode": dataColorMode,
70
+ }: RenderSheetProps) {
71
+ const sheetRef = useRef<HTMLDivElement>(null);
72
+ const scrollRef = useRef<HTMLDivElement>(null);
73
+ const y = useMotionValue(0);
74
+ const dragControls = useDragControls();
75
+ const [mounted, setMounted] = useState(false);
76
+ const [rendered, setRendered] = useState(false);
77
+ const [hasEntered, setHasEntered] = useState(false);
78
+ const [sheetHeightPx, setSheetHeightPx] = useState(0);
79
+ const entranceFrameRef = useRef<number | null>(null);
80
+ const entranceControlsRef = useRef<{ stop: () => void } | null>(null);
81
+ const isClosingRef = useRef(false);
82
+ const openRef = useRef(open);
83
+ openRef.current = open;
84
+ const openValue = useMotionValue(0);
85
+
86
+ const {
87
+ viewportHeight,
88
+ getViewportHeight,
89
+ minPx,
90
+ maxPx,
91
+ maxSnap,
92
+ normalizedSnapPoints,
93
+ resolvedFreeDragHeightPx,
94
+ getTargetYForSnap,
95
+ getGapHiddenY,
96
+ isFreeDrag,
97
+ isMinDefault,
98
+ } = useSheetGeometry({ config, height, sheetHeightPx, open, rendered });
99
+
100
+ const resolveSnap = useCallback(
101
+ () => resolveInitialSnapIndex(normalizedSnapPoints, config?.defaultSnap),
102
+ [normalizedSnapPoints, config?.defaultSnap],
103
+ );
104
+ const [snapIndex, setSnapIndex] = useState(resolveSnap);
105
+ const currentSnap =
106
+ normalizedSnapPoints[snapIndex] ?? normalizedSnapPoints.at(-1) ?? 1;
107
+
108
+ useEffect(() => {
109
+ if (isFreeDrag) return;
110
+ setSnapIndex(resolveSnap());
111
+ }, [isFreeDrag, resolveSnap]);
112
+
113
+ const { inset, bottomGap, radiusArr } = useSheetProgressive({
114
+ y,
115
+ normalizedSnapPoints,
116
+ viewportHeight,
117
+ maxSnap,
118
+ isFreeDrag,
119
+ hasExplicitSnapPoints: !!config?.snapPoints?.length,
120
+ });
121
+
122
+ const backdropOpacity = useMotionValue(1);
123
+
124
+ useEffect(() => {
125
+ const updateOpacity = (latestY: number) => {
126
+ if (isFreeDrag) return;
127
+ const h =
128
+ (sheetRef.current?.offsetHeight ?? sheetHeightPx ?? viewportHeight) ||
129
+ 800;
130
+ const restY = getTargetYForSnap(currentSnap);
131
+ const maxY = Math.max(100, h);
132
+ const relativeY = Math.max(0, latestY - restY);
133
+ const ratio = Math.min(1, relativeY / maxY);
134
+ backdropOpacity.set(0.1 + 0.9 * (1 - ratio));
135
+ };
136
+ updateOpacity(y.get());
137
+ return y.on("change", updateOpacity);
138
+ }, [
139
+ y,
140
+ viewportHeight,
141
+ isFreeDrag,
142
+ currentSnap,
143
+ getTargetYForSnap,
144
+ sheetHeightPx,
145
+ backdropOpacity,
146
+ ]);
147
+
148
+ const overlayOpacity = useTransform(
149
+ [backdropOpacity, openValue],
150
+ ([latestBackdrop, latestOpen]: number[]) => latestBackdrop * latestOpen,
151
+ );
152
+
153
+ const resolvedZIndex = zIndex ?? LAYER_Z_INDEX.modal;
154
+
155
+ const sheetHeight =
156
+ height ??
157
+ (config?.snapPoints?.length ? `${maxSnap * 100}vh` : "max-content");
158
+ const dragEnabled = !(config?.disableDrag ?? false);
159
+ const isOutsideConfig = config?.outside !== false;
160
+ const outsideOptions: DialogOutsideOptions =
161
+ config?.outside && typeof config.outside === "object" ? config.outside : {};
162
+ const lockBodyScroll = outsideOptions.lockBodyScroll !== false;
163
+
164
+ useScrollLock(open && lockBodyScroll);
165
+
166
+ useEffect(() => {
167
+ setMounted(true);
168
+ }, []);
169
+
170
+ useEffect(() => {
171
+ if (open) setRendered(true);
172
+ }, [open]);
173
+
174
+ useEffect(() => {
175
+ const el = sheetRef.current;
176
+ if (!el) return;
177
+ const update = () => setSheetHeightPx(el.offsetHeight);
178
+ update();
179
+ const observer = new ResizeObserver(update);
180
+ observer.observe(el);
181
+ return () => observer.disconnect();
182
+ }, []);
183
+
184
+ const cancelEntranceAnimation = useCallback(() => {
185
+ if (entranceFrameRef.current !== null) {
186
+ cancelAnimationFrame(entranceFrameRef.current);
187
+ entranceFrameRef.current = null;
188
+ }
189
+ entranceControlsRef.current?.stop();
190
+ entranceControlsRef.current = null;
191
+ }, []);
192
+
193
+ const exitControlsRef = useRef<{ stop: () => void } | null>(null);
194
+ const exitTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
195
+
196
+ const cancelExitAnimation = useCallback(() => {
197
+ if (exitTimeoutRef.current !== null) {
198
+ clearTimeout(exitTimeoutRef.current);
199
+ exitTimeoutRef.current = null;
200
+ }
201
+ exitControlsRef.current?.stop();
202
+ exitControlsRef.current = null;
203
+ }, []);
204
+
205
+ const getGapHiddenYCurried = useCallback(
206
+ () => getGapHiddenY(currentSnap, isFreeDrag, getTargetYForSnap),
207
+ [getGapHiddenY, currentSnap, isFreeDrag, getTargetYForSnap],
208
+ );
209
+
210
+ useEffect(() => {
211
+ if (open) {
212
+ isClosingRef.current = false;
213
+ cancelExitAnimation();
214
+ if (rendered && hasEntered) {
215
+ animate(openValue, 1, { duration: 0.18 });
216
+ }
217
+ }
218
+ }, [open, rendered, hasEntered, cancelExitAnimation, openValue]);
219
+
220
+ const close = useCallback(() => {
221
+ if (isClosingRef.current) return;
222
+ isClosingRef.current = true;
223
+ cancelEntranceAnimation();
224
+ cancelExitAnimation();
225
+ let hasReleasedWrapper = false;
226
+ let hasCompleted = false;
227
+
228
+ const releaseWrapper = () => {
229
+ if (hasReleasedWrapper) return;
230
+ hasReleasedWrapper = true;
231
+ onOpenChange(false);
232
+ };
233
+
234
+ releaseWrapper();
235
+
236
+ const complete = () => {
237
+ if (hasCompleted) return;
238
+ hasCompleted = true;
239
+ isClosingRef.current = false;
240
+ if (!openRef.current) {
241
+ setRendered(false);
242
+ setHasEntered(false);
243
+ openValue.set(0);
244
+ }
245
+ };
246
+
247
+ const fromY = y.get();
248
+ const toY = Math.max(getGapHiddenYCurried(), fromY);
249
+ animate(openValue, 0, { duration: 0.18 });
250
+
251
+ exitTimeoutRef.current = setTimeout(() => complete(), 450);
252
+
253
+ exitControlsRef.current = animate(y, toY, {
254
+ ...motionTransitions.sheet.exit,
255
+ onUpdate: (latest) => {
256
+ if (latest >= toY - 10) {
257
+ if (exitTimeoutRef.current !== null) {
258
+ clearTimeout(exitTimeoutRef.current);
259
+ exitTimeoutRef.current = null;
260
+ }
261
+ complete();
262
+ }
263
+ },
264
+ onComplete: () => {
265
+ if (exitTimeoutRef.current !== null) {
266
+ clearTimeout(exitTimeoutRef.current);
267
+ exitTimeoutRef.current = null;
268
+ }
269
+ complete();
270
+ },
271
+ });
272
+ }, [
273
+ onOpenChange,
274
+ y,
275
+ cancelEntranceAnimation,
276
+ cancelExitAnimation,
277
+ getGapHiddenYCurried,
278
+ openValue,
279
+ ]);
280
+
281
+ useEffect(() => {
282
+ if (!open) return;
283
+ if (rendered && !hasEntered) {
284
+ cancelEntranceAnimation();
285
+ const currentVh = getViewportHeight();
286
+ let initSnap: number;
287
+ if (config?.defaultSnap !== undefined) {
288
+ const points = normalizedSnapPoints;
289
+ const ds = config.defaultSnap;
290
+ if (Number.isInteger(ds) && ds >= 0 && ds < points.length) {
291
+ initSnap = points[ds];
292
+ } else {
293
+ const idx = points.indexOf(ds);
294
+ initSnap = idx >= 0 ? ds : maxSnap;
295
+ }
296
+ } else {
297
+ initSnap = maxSnap;
298
+ }
299
+ const targetY = currentVh * (maxSnap - initSnap);
300
+
301
+ y.jump(isFreeDrag ? 0 : currentVh * (maxSnap - initSnap + 1));
302
+ setHasEntered(true);
303
+ animate(openValue, 1, { duration: 0.18 });
304
+ entranceFrameRef.current = requestAnimationFrame(() => {
305
+ entranceControlsRef.current = animate(
306
+ y,
307
+ targetY,
308
+ motionTransitions.sheet.entrance,
309
+ );
310
+ });
311
+ }
312
+ }, [
313
+ open,
314
+ rendered,
315
+ hasEntered,
316
+ maxSnap,
317
+ normalizedSnapPoints,
318
+ config?.defaultSnap,
319
+ getViewportHeight,
320
+ y,
321
+ cancelEntranceAnimation,
322
+ isFreeDrag,
323
+ openValue,
324
+ ]);
325
+
326
+ const prevOpen = usePrevious(open);
327
+ useEffect(() => {
328
+ if (prevOpen && !open && rendered) close();
329
+ }, [open, rendered, close, prevOpen]);
330
+
331
+ useEffect(() => {
332
+ if (!rendered || !hasEntered || isFreeDrag || !open) return;
333
+ const targetY = getTargetYForSnap(currentSnap);
334
+ const controls = animate(y, targetY, motionTransitions.sheet.snap);
335
+ return () => controls.stop();
336
+ }, [
337
+ rendered,
338
+ hasEntered,
339
+ currentSnap,
340
+ getTargetYForSnap,
341
+ isFreeDrag,
342
+ open,
343
+ y,
344
+ ]);
345
+
346
+ useEscapeClose(open && config?.exit?.escape !== false, close);
347
+
348
+ useEffect(() => {
349
+ if (!open || !rendered) return;
350
+ sheetRef.current?.focus({ preventScroll: true });
351
+ }, [open, rendered]);
352
+
353
+ const {
354
+ handleDragEnd,
355
+ handleContentPointerDown,
356
+ handleContentPointerMove,
357
+ handleContentPointerUp,
358
+ handleContentPointerCancel,
359
+ } = useSheetDrag({
360
+ y,
361
+ scrollRef,
362
+ config,
363
+ viewportHeight,
364
+ getViewportHeight,
365
+ sheetHeightPx,
366
+ isFreeDrag,
367
+ minPx,
368
+ maxPx,
369
+ maxSnap,
370
+ normalizedSnapPoints,
371
+ resolvedFreeDragHeightPx,
372
+ isMinDefault,
373
+ snapIndex,
374
+ currentSnap,
375
+ getTargetYForSnap,
376
+ getGapHiddenY,
377
+ close,
378
+ cancelEntranceAnimation,
379
+ onSnapIndexChange: setSnapIndex,
380
+ });
381
+
382
+ const handleDialogKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
383
+ if (event.key !== "Escape") return;
384
+ if (config?.exit?.escape === false) return;
385
+ event.stopPropagation();
386
+ close();
387
+ };
388
+
389
+ const showProgress =
390
+ !!config?.snapPoints?.length &&
391
+ normalizedSnapPoints.length > 1 &&
392
+ !isFreeDrag;
393
+
394
+ const insetPx = showProgress ? inset : 8;
395
+
396
+ const sheetNode = rendered ? (
397
+ <View
398
+ data-open="true"
399
+ data-color-mode={dataColorMode}
400
+ style={{
401
+ position: "fixed",
402
+ left: 0,
403
+ right: 0,
404
+ top:
405
+ typeof window !== "undefined" && window.visualViewport
406
+ ? `${window.visualViewport.offsetTop}px`
407
+ : 0,
408
+ height: viewportHeight ? `${viewportHeight}px` : "100dvh",
409
+ zIndex: resolvedZIndex,
410
+ pointerEvents: "none",
411
+ }}
412
+ >
413
+ {isOutsideConfig && (
414
+ <BackgroundWrapper
415
+ open={open}
416
+ showBackdrop={isOutsideConfig}
417
+ background={outsideOptions.background}
418
+ backgroundBlur={outsideOptions.backgroundBlur}
419
+ className={outsideOptions.className}
420
+ style={outsideOptions.style}
421
+ zIndex={resolvedZIndex - 1}
422
+ overlayOpacity={overlayOpacity}
423
+ onClick={config?.exit?.overlay !== false ? close : undefined}
424
+ />
425
+ )}
426
+ <View
427
+ alignItems="flex-end"
428
+ justifyContent="center"
429
+ style={{
430
+ position: "absolute",
431
+ bottom: showProgress ? `${bottomGap}px` : "0.8rem",
432
+ left: `${insetPx}px`,
433
+ right: `${insetPx}px`,
434
+ pointerEvents: "none",
435
+ zIndex: 99990,
436
+ }}
437
+ >
438
+ <View
439
+ id={id}
440
+ ref={sheetRef}
441
+ data-popover-owner={config?.popoverOwnerId}
442
+ className={config?.className}
443
+ role={role}
444
+ aria-modal={isOutsideConfig ? "true" : "false"}
445
+ tabIndex={-1}
446
+ shadow={config?.shadow ?? "Regular"}
447
+ radius={showProgress ? radiusArr : (config?.radius ?? "ExtraBold")}
448
+ motion={
449
+ {
450
+ drag: dragEnabled ? "y" : false,
451
+ dragControls: dragControls,
452
+ dragListener: false,
453
+ dragConstraints: {
454
+ top: 0,
455
+ bottom:
456
+ isFreeDrag || config?.min !== undefined
457
+ ? Math.max(0, resolvedFreeDragHeightPx - minPx)
458
+ : viewportHeight,
459
+ },
460
+ dragElastic: {
461
+ top: 0.18,
462
+ bottom: config?.min !== undefined ? 0 : 0.85,
463
+ },
464
+ dragMomentum: false,
465
+ onDragEnd: handleDragEnd as any,
466
+ } as any
467
+ }
468
+ onKeyDown={handleDialogKeyDown}
469
+ style={
470
+ {
471
+ y,
472
+ width: width ? Size(width) : "100%",
473
+ height:
474
+ sheetHeight === "max-content"
475
+ ? "max-content"
476
+ : Size(sheetHeight),
477
+ maxHeight:
478
+ "calc(var(--sheet-viewport-height, 100dvh) - env(safe-area-inset-top) - 0.8rem)",
479
+ "--sheet-viewport-height": viewportHeight
480
+ ? `${viewportHeight}px`
481
+ : "100dvh",
482
+ flexDirection: "column",
483
+ pointerEvents: "auto",
484
+ position: "relative",
485
+ visibility: hasEntered ? "visible" : "hidden",
486
+ overflow: "hidden",
487
+ zIndex: resolvedZIndex,
488
+ willChange: "transform",
489
+ } as any
490
+ }
491
+ >
492
+ <View
493
+ themePreset={config?.themePreset}
494
+ background={config?.background ?? "Base2"}
495
+ color={config?.color}
496
+ backgroundBlur={config?.backgroundBlur}
497
+ column
498
+ gap={0}
499
+ style={{
500
+ width: "100%",
501
+ height: "100%",
502
+ position: "relative",
503
+ overflow: "hidden",
504
+ ...config?.style,
505
+ }}
506
+ >
507
+ <Pressable
508
+ type="button"
509
+ aria-label="시트 이동"
510
+ onPointerDown={(event) => {
511
+ if (!dragEnabled) return;
512
+ dragControls.start(event);
513
+ }}
514
+ themeInteractive={false}
515
+ paddingVertical={12}
516
+ alignItems="center"
517
+ justifyContent="center"
518
+ style={{
519
+ width: "100%",
520
+ cursor: "default",
521
+ }}
522
+ >
523
+ <View
524
+ radius="Circle"
525
+ background="Base6TP4"
526
+ style={{
527
+ width: "4.2rem",
528
+ height: "0.6rem",
529
+ }}
530
+ />
531
+ </Pressable>
532
+
533
+ <DialogHeader
534
+ config={config?.header}
535
+ exitIcon={config?.exit?.icon}
536
+ onExit={close}
537
+ />
538
+
539
+ <View
540
+ ref={scrollRef}
541
+ onPointerDown={handleContentPointerDown}
542
+ onPointerMove={handleContentPointerMove}
543
+ onPointerUp={handleContentPointerUp}
544
+ onPointerCancel={handleContentPointerCancel}
545
+ padding={[0, 4, 4, 4]}
546
+ style={{
547
+ flex: "1 1 auto",
548
+ overflowY: "auto",
549
+ touchAction: "pan-y",
550
+ minHeight: 0,
551
+ }}
552
+ >
553
+ {children}
554
+ </View>
555
+
556
+ <DialogFooter
557
+ config={config?.footer}
558
+ exitButton={config?.exit?.footerButton}
559
+ onExit={close}
560
+ />
561
+ </View>
562
+ </View>
563
+ </View>
564
+ </View>
565
+ ) : null;
566
+
567
+ if (!mounted) return null;
568
+ return <DialogPortal>{sheetNode}</DialogPortal>;
569
+ }
570
+
571
+
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { memo, useMemo } from "react";
4
+ import type { EdgeEffectProps } from "@/packages/Frameworks/EdgeEffect/EdgeEffect.types";
5
+ import View from "@/packages/Frameworks/View/View";
6
+
7
+ const OPPOSITE_SIDE: Record<
8
+ "left" | "right" | "top" | "bottom",
9
+ "left" | "right" | "top" | "bottom"
10
+ > = {
11
+ left: "right",
12
+ right: "left",
13
+ top: "bottom",
14
+ bottom: "top",
15
+ };
16
+
17
+ const EdgeEffect = memo(function EdgeEffect({
18
+ side = "top",
19
+ className,
20
+ style,
21
+ ...restProps
22
+ }: EdgeEffectProps) {
23
+ const oppositeSide = OPPOSITE_SIDE[side];
24
+
25
+ const backgroundGradient = useMemo(() => {
26
+ return `linear-gradient(to ${oppositeSide}, var(--color-base-light-1, rgba(255, 255, 255, 0.8)), transparent)`;
27
+ }, [oppositeSide]);
28
+
29
+ return (
30
+ <View
31
+ className={className}
32
+ {...restProps}
33
+ style={{
34
+ pointerEvents: "none",
35
+ ...style,
36
+ }}
37
+ >
38
+ <View
39
+ suppressHydrationWarning
40
+ style={{
41
+ width: "100%",
42
+ height: "100%",
43
+ background: backgroundGradient,
44
+ }}
45
+ />
46
+ </View>
47
+ );
48
+ });
49
+
50
+ EdgeEffect.displayName = "EdgeEffect";
51
+
52
+ export default EdgeEffect;
@@ -0,0 +1,5 @@
1
+ import type { ViewProps } from "@/packages/Frameworks/View/View.types";
2
+
3
+ export interface EdgeEffectProps extends Omit<ViewProps, "children"> {
4
+ side?: "left" | "right" | "top" | "bottom";
5
+ }
@@ -0,0 +1,49 @@
1
+ export const motionTransitions = {
2
+ sheet: {
3
+ snap: { type: "spring" as const, stiffness: 520, damping: 48, mass: 0.9 },
4
+ entrance: {
5
+ type: "spring" as const,
6
+ stiffness: 480,
7
+ damping: 44,
8
+ mass: 0.95,
9
+ },
10
+ exit: { type: "spring" as const, stiffness: 400, damping: 46, mass: 1.0 },
11
+ },
12
+ modal: {
13
+ transition: { type: "spring" as const, stiffness: 380, damping: 38 },
14
+ },
15
+ popover: {
16
+ transition: { duration: 0.16, ease: "easeOut" },
17
+ },
18
+ backdrop: {
19
+ transition: { duration: 0.18 },
20
+ },
21
+ } as const;
22
+
23
+ export const motionPresets = {
24
+ modal: {
25
+ initial: { opacity: 0, scale: 0.94, y: 8 },
26
+ animate: { opacity: 1, scale: 1, y: 0 },
27
+ exit: { opacity: 0, scale: 0.94, y: 8 },
28
+ transition: motionTransitions.modal.transition,
29
+ },
30
+ popover: (isPositionReady: boolean) => ({
31
+ initial: { opacity: 0, scale: 0.96 },
32
+ animate: isPositionReady
33
+ ? { opacity: 1, scale: 1 }
34
+ : { opacity: 0, scale: 0.96 },
35
+ exit: { opacity: 0, scale: 0.96 },
36
+ transition: motionTransitions.popover.transition,
37
+ }),
38
+ backdrop: (open?: boolean) => ({
39
+ initial: { opacity: 0 },
40
+ animate:
41
+ open === undefined
42
+ ? { opacity: 1 }
43
+ : open
44
+ ? { opacity: 1 }
45
+ : { opacity: 0 },
46
+ exit: { opacity: 0 },
47
+ transition: motionTransitions.backdrop.transition,
48
+ }),
49
+ } as const;
@@ -0,0 +1,19 @@
1
+ .Pressable {
2
+ text-decoration: none !important;
3
+ touch-action: manipulation;
4
+
5
+ &:is([href], button, [role="button"], label) {
6
+ -webkit-appearance: none;
7
+ appearance: none;
8
+
9
+ &:disabled,
10
+ &[disabled],
11
+ &[aria-disabled="true"],
12
+ &[data-disabled="true"] {
13
+ cursor: not-allowed;
14
+ opacity: 0.4;
15
+ }
16
+ }
17
+ }
18
+
19
+