From c79f2410a0592e7d58441e33e19201139118a7e9 Mon Sep 17 00:00:00 2001 From: nikita Date: Mon, 11 Aug 2025 23:56:27 +0300 Subject: [PATCH 1/2] feat: bottom sheet prototype --- src/components/bottom-sheet/BottomSheet.tsx | 130 ++++++++++++++++++++ src/components/bottom-sheet/index.ts | 0 2 files changed, 130 insertions(+) create mode 100644 src/components/bottom-sheet/BottomSheet.tsx create mode 100644 src/components/bottom-sheet/index.ts diff --git a/src/components/bottom-sheet/BottomSheet.tsx b/src/components/bottom-sheet/BottomSheet.tsx new file mode 100644 index 00000000..bf63205d --- /dev/null +++ b/src/components/bottom-sheet/BottomSheet.tsx @@ -0,0 +1,130 @@ +import Flex from "../flex/Flex"; +import { Component, createSignal, onCleanup, onMount } from "solid-js"; + +interface BottomSheetProps { + isOpen: boolean; + onClose: () => void; + children: any; +} + +export const BottomSheet: Component = (props) => { + const [isDragging, setIsDragging] = createSignal(false); + const [startY, setStartY] = createSignal(0); + const [currentY, setCurrentY] = createSignal(0); + const [isClosing, setIsClosing] = createSignal(false); + + let sheetRef: HTMLDivElement | undefined; + let overlayRef: HTMLDivElement | undefined; + + const handleTouchStart = (e: TouchEvent) => { + const touchY = e.touches[0].clientY; + const sheetTop = sheetRef?.getBoundingClientRect().top || 0; + + if (touchY - sheetTop < 50) { + setIsDragging(true); + setStartY(e.touches[0].clientY); + setCurrentY(e.touches[0].clientY); + e.preventDefault(); + } + }; + + const handleTouchMove = (e: TouchEvent) => { + if (!isDragging()) return; + + const deltaY = e.touches[0].clientY - startY(); + const newY = Math.max(0, deltaY); + setCurrentY(e.touches[0].clientY); + + if (sheetRef) { + sheetRef.style.transform = `translateY(${newY}px)`; + } + + e.preventDefault(); + }; + + const handleTouchEnd = () => { + if (!isDragging()) return; + + setIsDragging(false); + const deltaY = currentY() - startY(); + + if (deltaY > 100) { + handleClose(); + } else { + if (sheetRef) { + sheetRef.style.transform = "translateY(0)"; + } + } + }; + + const handleClose = () => { + setIsClosing(true); + setTimeout(() => { + props.onClose(); + setIsClosing(false); + if (sheetRef) { + sheetRef.style.transform = "translateY(0)"; + } + }, 200); + }; + + const handleOverlayClick = (e: MouseEvent) => { + if (e.target === overlayRef) { + handleClose(); + } + }; + + onMount(() => { + if (sheetRef) { + sheetRef.addEventListener("touchstart", handleTouchStart, { + passive: true, + }); + sheetRef.addEventListener("touchmove", handleTouchMove, { + passive: false, + }); + sheetRef.addEventListener("touchend", handleTouchEnd, { passive: true }); + } + }); + + onCleanup(() => { + if (sheetRef) { + sheetRef.removeEventListener("touchstart", handleTouchStart); + sheetRef.removeEventListener("touchmove", handleTouchMove); + sheetRef.removeEventListener("touchend", handleTouchEnd); + } + }); + + return ( + <> +
+ +
+ +
+ + +
{props.children}
+
+ + ); +}; diff --git a/src/components/bottom-sheet/index.ts b/src/components/bottom-sheet/index.ts new file mode 100644 index 00000000..e69de29b From ea47b0ed479bef0c1b9ed43032987909444c04c2 Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Tue, 12 Aug 2025 00:42:46 -0400 Subject: [PATCH 2/2] feat: add BottomSheet component --- src/components/bottom-sheet/BottomSheet.tsx | 109 +++++++++++--------- src/components/bottom-sheet/index.ts | 2 + src/index.ts | 4 +- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/src/components/bottom-sheet/BottomSheet.tsx b/src/components/bottom-sheet/BottomSheet.tsx index bf63205d..cda7f998 100644 --- a/src/components/bottom-sheet/BottomSheet.tsx +++ b/src/components/bottom-sheet/BottomSheet.tsx @@ -1,81 +1,91 @@ -import Flex from "../flex/Flex"; -import { Component, createSignal, onCleanup, onMount } from "solid-js"; - -interface BottomSheetProps { +import { + Component, + createSignal, + onMount, + onCleanup, + splitProps, + mergeProps, +} from "solid-js"; +import { clsx } from "clsx"; +import type { IComponentBaseProps } from "../types"; + +export interface BottomSheetProps extends IComponentBaseProps { isOpen: boolean; onClose: () => void; - children: any; + children?: any; + closeOnOverlayClick?: boolean; + closeOnSwipeDown?: boolean; } -export const BottomSheet: Component = (props) => { +const BottomSheet: Component = (props) => { + const merged = mergeProps( + { + closeOnOverlayClick: true, + closeOnSwipeDown: true, + }, + props + ); + + const [local, others] = splitProps(merged, [ + "isOpen", + "onClose", + "children", + "dataTheme", + "class", + "className", + "style", + "closeOnOverlayClick", + "closeOnSwipeDown", + ]); + const [isDragging, setIsDragging] = createSignal(false); const [startY, setStartY] = createSignal(0); const [currentY, setCurrentY] = createSignal(0); - const [isClosing, setIsClosing] = createSignal(false); let sheetRef: HTMLDivElement | undefined; let overlayRef: HTMLDivElement | undefined; const handleTouchStart = (e: TouchEvent) => { + if (!local.closeOnSwipeDown) return; const touchY = e.touches[0].clientY; const sheetTop = sheetRef?.getBoundingClientRect().top || 0; - if (touchY - sheetTop < 50) { setIsDragging(true); - setStartY(e.touches[0].clientY); - setCurrentY(e.touches[0].clientY); - e.preventDefault(); + setStartY(touchY); + setCurrentY(touchY); } }; const handleTouchMove = (e: TouchEvent) => { if (!isDragging()) return; - const deltaY = e.touches[0].clientY - startY(); const newY = Math.max(0, deltaY); setCurrentY(e.touches[0].clientY); - if (sheetRef) { sheetRef.style.transform = `translateY(${newY}px)`; } - e.preventDefault(); }; const handleTouchEnd = () => { if (!isDragging()) return; - setIsDragging(false); const deltaY = currentY() - startY(); - if (deltaY > 100) { - handleClose(); + local.onClose(); } else { - if (sheetRef) { - sheetRef.style.transform = "translateY(0)"; - } + if (sheetRef) sheetRef.style.transform = "translateY(0)"; } }; - const handleClose = () => { - setIsClosing(true); - setTimeout(() => { - props.onClose(); - setIsClosing(false); - if (sheetRef) { - sheetRef.style.transform = "translateY(0)"; - } - }, 200); - }; - const handleOverlayClick = (e: MouseEvent) => { - if (e.target === overlayRef) { - handleClose(); + if (local.closeOnOverlayClick && e.target === overlayRef) { + local.onClose(); } }; onMount(() => { - if (sheetRef) { + if (sheetRef && local.closeOnSwipeDown) { sheetRef.addEventListener("touchstart", handleTouchStart, { passive: true, }); @@ -98,33 +108,34 @@ export const BottomSheet: Component = (props) => { <>
- -
- - -
{props.children}
+
+
+ {local.children} +
); }; + +export default BottomSheet; diff --git a/src/components/bottom-sheet/index.ts b/src/components/bottom-sheet/index.ts index e69de29b..a25abd27 100644 --- a/src/components/bottom-sheet/index.ts +++ b/src/components/bottom-sheet/index.ts @@ -0,0 +1,2 @@ +export type { BottomSheetProps } from "./BottomSheet"; +export { default } from "./BottomSheet"; diff --git a/src/index.ts b/src/index.ts index ea411b22..be29ce6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,8 @@ export { default as BrowserMockup, type BrowserMockupProps, } from "./components/browsermockup"; +export { default as BottomSheet } from "./components/bottom-sheet/BottomSheet"; +export type { BottomSheetProps } from "./components/bottom-sheet/BottomSheet"; export { default as Button } from "./components/button"; export { default as Calendar, type CalendarProps } from "./components/calendar"; export { default as Card } from "./components/card"; @@ -132,4 +134,4 @@ export { // Stores export * from "./stores"; -export { default } from "./components/connectionstatus"; \ No newline at end of file +export { default } from "./components/connectionstatus";