Skip to content

Commit 25daa00

Browse files
committed
feat: refactor everything
1 parent 7860ab7 commit 25daa00

7 files changed

Lines changed: 559 additions & 345 deletions

File tree

.eslintrc.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,6 @@
100100
},
101101
"import/resolver": {
102102
"typescript": {}
103-
// "alias": {
104-
// "extensions": [".js", ".jsx", ".ts", ".tsx"]
105-
// // "map": [
106-
// // ["^jotai$", "./src/index.ts"],
107-
// // ["jotai", "./src"]
108-
// // ]
109-
// }
110103
}
111104
},
112105
"overrides": [

README.md

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ pnpm install slick-bottom-sheet
2222
"use client";
2323

2424
import React from "react";
25-
26-
import { SlickBottomSheet } from "slick-bottom-sheet";
25+
import { SlickBottomSheet, SlickBottomSheetControl } from "slick-bottom-sheet";
2726

2827
export default function Home() {
2928
const [isOpen, setIsOpen] = React.useState(false);
30-
const ref = React.useRef<any>(null);
29+
const ref = React.useRef<SlickBottomSheetControl>(null);
30+
31+
React.useEffect(() => {
32+
setIsOpen(true);
33+
}, []);
3134

3235
return (
3336
<div className="w-screen h-[100dvh] grid place-items-center relative container max-w-3xl">
@@ -44,41 +47,37 @@ export default function Home() {
4447
</div>
4548
<SlickBottomSheet
4649
isOpen={isOpen}
47-
onClose={() => {
48-
console.log("close");
50+
onCloseStart={() => {
4951
setIsOpen(false);
5052
}}
5153
ref={ref}
5254
snaps={[300, 0.5, 0.9]}
5355
defaultSnap={0}
54-
onSnap={(snap) => {
55-
console.log(snap);
56-
}}
56+
onSnap={console.log}
5757
className="bg-white rounded-t-2xl overflow-hidden shadow-xl z-10 isolate"
58+
header={
59+
<div className="p-4 border-b border-zinc-300 bg-zinc-100">header</div>
60+
}
61+
footer={
62+
<div className="p-4 border-t border-zinc-300 bg-zinc-100">footer</div>
63+
}
64+
backdropClassName="backdrop-blur-sm"
5865
>
59-
<div className="sticky top-0 p-4 border-b border-zinc-300 bg-zinc-100">
60-
header
61-
</div>
6266
<div className="p-4">
63-
<div className="">
64-
<input
65-
type="text"
66-
className="w-full px-4 py-2 bg-white border border-zinc-300 rounded-md"
67-
placeholder="input"
68-
/>
69-
<div className="mt-3">
70-
{Array.from({ length: 35 }).map((_, i) => (
71-
<React.Fragment key={i}>
72-
scrollable body
73-
<br />
74-
</React.Fragment>
75-
))}
76-
</div>
67+
<input
68+
type="text"
69+
className="w-full px-4 py-2 bg-white border border-zinc-300 rounded-md"
70+
placeholder="input"
71+
/>
72+
<div className="mt-3">
73+
{Array.from({ length: 35 }).map((_, i) => (
74+
<React.Fragment key={i}>
75+
scrollable body
76+
<br />
77+
</React.Fragment>
78+
))}
7779
</div>
7880
</div>
79-
<div className="sticky bottom-0 p-4 border-t border-zinc-300 bg-zinc-100">
80-
footer
81-
</div>
8281
</SlickBottomSheet>
8382
</div>
8483
);

example/app/page.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
"use client";
22

33
import React from "react";
4-
import { SlickBottomSheet } from "slick-bottom-sheet";
4+
import { SlickBottomSheet, SlickBottomSheetControl } from "slick-bottom-sheet";
55

66
export default function Home() {
77
const [isOpen, setIsOpen] = React.useState(false);
8-
const ref = React.useRef<any>(null);
8+
const ref = React.useRef<SlickBottomSheetControl>(null);
9+
10+
React.useEffect(() => {
11+
setIsOpen(true);
12+
}, []);
913

1014
return (
1115
<div className="w-screen h-[100dvh] grid place-items-center relative container max-w-3xl">
@@ -22,41 +26,37 @@ export default function Home() {
2226
</div>
2327
<SlickBottomSheet
2428
isOpen={isOpen}
25-
onClose={() => {
26-
console.log("close");
29+
onCloseStart={() => {
2730
setIsOpen(false);
2831
}}
2932
ref={ref}
3033
snaps={[300, 0.5, 0.9]}
3134
defaultSnap={0}
32-
onSnap={(snap) => {
33-
console.log(snap);
34-
}}
35+
onSnap={console.log}
3536
className="bg-white rounded-t-2xl overflow-hidden shadow-xl z-10 isolate"
37+
header={
38+
<div className="p-4 border-b border-zinc-300 bg-zinc-100">header</div>
39+
}
40+
footer={
41+
<div className="p-4 border-t border-zinc-300 bg-zinc-100">footer</div>
42+
}
43+
backdropClassName="backdrop-blur-sm"
3644
>
37-
<div className="sticky top-0 p-4 border-b border-zinc-300 bg-zinc-100">
38-
header
39-
</div>
4045
<div className="p-4">
41-
<div className="">
42-
<input
43-
type="text"
44-
className="w-full px-4 py-2 bg-white border border-zinc-300 rounded-md"
45-
placeholder="input"
46-
/>
47-
<div className="mt-3">
48-
{Array.from({ length: 35 }).map((_, i) => (
49-
<React.Fragment key={i}>
50-
scrollable body
51-
<br />
52-
</React.Fragment>
53-
))}
54-
</div>
46+
<input
47+
type="text"
48+
className="w-full px-4 py-2 bg-white border border-zinc-300 rounded-md"
49+
placeholder="input"
50+
/>
51+
<div className="mt-3">
52+
{Array.from({ length: 35 }).map((_, i) => (
53+
<React.Fragment key={i}>
54+
scrollable body
55+
<br />
56+
</React.Fragment>
57+
))}
5558
</div>
5659
</div>
57-
<div className="sticky bottom-0 p-4 border-t border-zinc-300 bg-zinc-100">
58-
footer
59-
</div>
6060
</SlickBottomSheet>
6161
</div>
6262
);

src/hooks/useRect.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ const getRect = (element: HTMLElement) => {
44
return element.getBoundingClientRect();
55
};
66

7-
export function useRect<T extends HTMLElement>(params?: {
8-
onResize?: (rect: DOMRect) => void;
9-
onMount?: (rect: DOMRect) => void;
10-
}): [DOMRect | null, React.MutableRefObject<T | null>] {
7+
export function useRect<T extends HTMLElement>(
8+
ref: React.RefObject<HTMLElement>,
9+
params?: {
10+
onResize?: (rect: DOMRect) => void;
11+
onMount?: (rect: DOMRect) => void;
12+
},
13+
): DOMRect | null {
1114
const { onResize, onMount } = params || {};
1215

13-
const ref = React.useRef<T | null>(null);
1416
const [rect, setRect] = React.useState(
1517
ref.current ? getRect(ref.current) : null,
1618
);
@@ -48,7 +50,7 @@ export function useRect<T extends HTMLElement>(params?: {
4850

4951
window.addEventListener("resize", handleResize);
5052
return () => window.removeEventListener("resize", handleResize);
51-
}, [handleResize]);
53+
}, [handleResize, ref]);
5254

53-
return [rect, ref];
55+
return rect;
5456
}

src/hooks/useSnapPoint.tsx

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { SnapConfig } from "src";
33
import { getClosestIndexAndValue } from "../utils";
4+
import { useRect } from "./useRect";
45

56
export interface SnapPoint {
67
value: number;
@@ -12,17 +13,20 @@ export type Snap = {
1213
getNearestByCoord: (coord: number) => SnapPoint;
1314
getNearestByIndex: (index: string | number) => SnapPoint;
1415
defaultSnap: SnapPoint;
15-
autoSnap: SnapPoint;
16+
// autoSnap: SnapPoint;
1617
minSnap: SnapPoint;
1718
minSnapExceptClose: SnapPoint;
1819
maxSnap: SnapPoint;
1920
originalMap: Record<string | number, number>;
2021
processedMap: Record<string | number, number>;
22+
isScroll: boolean;
2123
};
2224

2325
export function useSnapPoint<T extends HTMLElement>({
24-
containerRect,
26+
containerRef,
2527
contentRef,
28+
headerRef,
29+
footerRef,
2630
config: {
2731
snaps = [],
2832
defaultSnap = "auto",
@@ -33,49 +37,66 @@ export function useSnapPoint<T extends HTMLElement>({
3337
onMount,
3438
onChange,
3539
}: {
36-
containerRect: DOMRect | null;
40+
containerRef: React.RefObject<T>;
3741
contentRef: React.RefObject<T>;
42+
headerRef: React.RefObject<T>;
43+
footerRef: React.RefObject<T>;
3844
config: SnapConfig;
3945
onMount?: (snapPoints: NonNullable<ReturnType<typeof useSnapPoint>>) => void;
4046
onChange?: (snapPoints: NonNullable<ReturnType<typeof useSnapPoint>>) => void;
4147
}): null | Snap {
42-
const mount = React.useRef(false);
48+
const mount = React.useRef<null | Record<string, number>>(null);
49+
const containerRect = useRect(containerRef);
50+
useRect(contentRef);
51+
useRect(headerRef);
52+
useRect(footerRef);
4353

4454
const snapPoints = React.useMemo(() => {
4555
if (!containerRect || !contentRef.current) return null;
4656

4757
const containerHeight = containerRect.height;
48-
const scrollHeight = contentRef.current.scrollHeight;
58+
const contentTotalHeight =
59+
contentRef.current.scrollHeight +
60+
(headerRef?.current?.offsetHeight ?? 0) +
61+
(footerRef?.current?.offsetHeight ?? 0);
4962

50-
const originalMap: Record<string | number, number> = {};
63+
const originalMap: Record<string, number> = {};
64+
65+
// create original map
5166

5267
snaps.forEach((value, key) => {
5368
if (value <= 1) originalMap[key] = -value * containerHeight;
5469
else originalMap[key] = -value;
5570
});
56-
if (useCloseSnap || useCloseSnap === 0) {
57-
if (useCloseSnap === true) {
58-
originalMap["close"] = 0;
59-
} else {
60-
originalMap["close"] = -useCloseSnap;
61-
}
71+
if (useCloseSnap === true) {
72+
originalMap["close"] = 0;
6273
}
63-
if (useAutoSnap) {
64-
originalMap["auto"] = -scrollHeight;
74+
if (typeof useCloseSnap === "number") {
75+
originalMap["close"] = -useCloseSnap;
6576
}
77+
if (useAutoSnap === true) {
78+
originalMap["auto"] = -contentTotalHeight;
79+
}
80+
81+
// pick processed keys that are in range
6682

6783
let processedKeys = Object.keys(originalMap);
68-
if (autoSnapAsMax) {
84+
if (autoSnapAsMax === true) {
6985
processedKeys = processedKeys.filter((key) => {
7086
const value = originalMap[key];
71-
return value >= -scrollHeight;
87+
return value >= -contentTotalHeight;
7288
});
7389
}
7490
processedKeys = processedKeys.filter(
7591
(key) => originalMap[key] >= -containerHeight,
7692
);
93+
if (useCloseSnap !== undefined) {
94+
processedKeys = processedKeys.filter(
95+
(key) => key === "close" || originalMap[key] < originalMap["close"],
96+
);
97+
}
7798

78-
const processedMap = processedKeys.reduce<Record<string | number, number>>(
99+
const processedMap = processedKeys.reduce<Record<string, number>>(
79100
(prev, curr) => {
80101
prev[curr] = originalMap[curr];
81102
return prev;
@@ -93,7 +114,6 @@ export function useSnapPoint<T extends HTMLElement>({
93114
index,
94115
};
95116
}
96-
97117
return null;
98118
}
99119

@@ -128,7 +148,7 @@ export function useSnapPoint<T extends HTMLElement>({
128148
}
129149
})();
130150

131-
const autoSnap: SnapPoint = getNearestByIndex("auto");
151+
// const autoSnap: SnapPoint = getNearestByIndex("auto");
132152
const minSnap: SnapPoint = getNearestByCoord(Math.max(...processedValues));
133153
const maxSnap: SnapPoint = getNearestByCoord(Math.min(...processedValues));
134154
const minSnapExceptClose: SnapPoint = (() => {
@@ -140,25 +160,37 @@ export function useSnapPoint<T extends HTMLElement>({
140160
return minSnap;
141161
})();
142162

163+
const isScroll =
164+
originalMap["auto"] !== undefined
165+
? maxSnap.value > originalMap["auto"]
166+
: false;
167+
143168
const result = {
144169
getByIndex,
145170
getNearestByCoord,
146171
getNearestByIndex,
147172
defaultSnap: _defaultSnap,
148-
autoSnap,
173+
// autoSnap,
149174
minSnap,
150175
minSnapExceptClose,
151176
maxSnap,
152177
originalMap,
153178
processedMap,
179+
isScroll,
154180
};
155181

156182
if (!mount.current) {
157-
mount.current = true;
158183
if (onMount) onMount(result);
159184
} else {
160-
if (onChange) onChange(result);
185+
if (
186+
onChange &&
187+
Object.entries(processedMap).toString() !==
188+
Object.entries(mount.current).toString()
189+
) {
190+
onChange(result);
191+
}
161192
}
193+
mount.current = processedMap;
162194

163195
return result;
164196
}, [

0 commit comments

Comments
 (0)