@fire-spark/react-ui 0.0.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.
@@ -0,0 +1,89 @@
1
+ "use client";
2
+ import { useEffect, useRef, useState } from "react";
3
+ //#region src/use-active-category.ts
4
+ /** Wait until scroll events stop before trusting the spy again. */
5
+ const SCROLL_SETTLE_MS = 140;
6
+ const useActiveCategory = (options) => {
7
+ const { categoryIds, root = null, offset = 0 } = options;
8
+ const [activeCategoryId, setActiveCategoryId] = useState(() => categoryIds[0] ?? "");
9
+ const lockedIdRef = useRef(null);
10
+ const settleTimerRef = useRef(null);
11
+ const spyTimerRef = useRef(null);
12
+ const categoryIdsKey = categoryIds.join("\0");
13
+ useEffect(() => {
14
+ if (categoryIds.length === 0) {
15
+ setActiveCategoryId("");
16
+ return;
17
+ }
18
+ if (!categoryIds.includes(activeCategoryId)) setActiveCategoryId(categoryIds[0] ?? "");
19
+ }, [
20
+ categoryIds,
21
+ categoryIdsKey,
22
+ activeCategoryId
23
+ ]);
24
+ useEffect(() => {
25
+ const resolveCurrent = () => {
26
+ if (categoryIds.length === 0) return "";
27
+ const threshold = (root ? root.getBoundingClientRect().top : 0) + offset + 1;
28
+ let current = categoryIds[0] ?? "";
29
+ for (const id of categoryIds) {
30
+ const el = document.getElementById(id);
31
+ if (!el) continue;
32
+ if (el.getBoundingClientRect().top <= threshold) current = id;
33
+ }
34
+ return current;
35
+ };
36
+ const applySpy = () => {
37
+ if (lockedIdRef.current != null) return;
38
+ const current = resolveCurrent();
39
+ if (!current) return;
40
+ setActiveCategoryId((prev) => prev === current ? prev : current);
41
+ };
42
+ const armSettleUnlock = () => {
43
+ if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
44
+ settleTimerRef.current = setTimeout(() => {
45
+ lockedIdRef.current = null;
46
+ }, SCROLL_SETTLE_MS);
47
+ };
48
+ const onScroll = () => {
49
+ if (lockedIdRef.current != null) {
50
+ armSettleUnlock();
51
+ return;
52
+ }
53
+ if (spyTimerRef.current) clearTimeout(spyTimerRef.current);
54
+ spyTimerRef.current = setTimeout(applySpy, SCROLL_SETTLE_MS);
55
+ };
56
+ const target = root ?? window;
57
+ target.addEventListener("scroll", onScroll, { passive: true });
58
+ applySpy();
59
+ return () => {
60
+ target.removeEventListener("scroll", onScroll);
61
+ if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
62
+ if (spyTimerRef.current) clearTimeout(spyTimerRef.current);
63
+ };
64
+ }, [
65
+ root,
66
+ offset,
67
+ categoryIds,
68
+ categoryIdsKey
69
+ ]);
70
+ const selectCategory = (categoryId) => {
71
+ lockedIdRef.current = categoryId;
72
+ setActiveCategoryId(categoryId);
73
+ if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
74
+ document.getElementById(categoryId)?.scrollIntoView({
75
+ behavior: "smooth",
76
+ block: "start"
77
+ });
78
+ settleTimerRef.current = setTimeout(() => {
79
+ lockedIdRef.current = null;
80
+ }, 1e3);
81
+ };
82
+ return {
83
+ activeCategoryId,
84
+ setActiveCategoryId,
85
+ selectCategory
86
+ };
87
+ };
88
+ //#endregion
89
+ export { useActiveCategory };
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@fire-spark/react-ui",
3
+ "version": "0.0.1",
4
+ "description": "Fire Spark React UI components",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./src/index.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.ts",
15
+ "development": "./src/index.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./category": {
20
+ "types": "./src/category.tsx",
21
+ "development": "./src/category.tsx",
22
+ "import": "./dist/category.js",
23
+ "default": "./dist/category.js"
24
+ },
25
+ "./menu": {
26
+ "types": "./src/menu.tsx",
27
+ "development": "./src/menu.tsx",
28
+ "import": "./dist/menu.js",
29
+ "default": "./dist/menu.js"
30
+ },
31
+ "./category-nav": {
32
+ "types": "./src/category-nav.tsx",
33
+ "development": "./src/category-nav.tsx",
34
+ "import": "./dist/category-nav.js",
35
+ "default": "./dist/category-nav.js"
36
+ },
37
+ "./use-active-category": {
38
+ "types": "./src/use-active-category.ts",
39
+ "development": "./src/use-active-category.ts",
40
+ "import": "./dist/use-active-category.js",
41
+ "default": "./dist/use-active-category.js"
42
+ },
43
+ "./product-card": {
44
+ "types": "./src/product-card.tsx",
45
+ "development": "./src/product-card.tsx",
46
+ "import": "./dist/product-card.js",
47
+ "default": "./dist/product-card.js"
48
+ },
49
+ "./product-detail": {
50
+ "types": "./src/product-detail.tsx",
51
+ "development": "./src/product-detail.tsx",
52
+ "import": "./dist/product-detail.js",
53
+ "default": "./dist/product-detail.js"
54
+ },
55
+ "./cart": {
56
+ "types": "./src/cart.tsx",
57
+ "development": "./src/cart.tsx",
58
+ "import": "./dist/cart.js",
59
+ "default": "./dist/cart.js"
60
+ },
61
+ "./checkout": {
62
+ "types": "./src/checkout.tsx",
63
+ "development": "./src/checkout.tsx",
64
+ "import": "./dist/checkout.js",
65
+ "default": "./dist/checkout.js"
66
+ }
67
+ },
68
+ "peerDependencies": {
69
+ "react": "^19.0.0",
70
+ "react-dom": "^19.0.0"
71
+ },
72
+ "dependencies": {
73
+ "clsx": "^2.1.1",
74
+ "tailwind-merge": "^3.0.2"
75
+ },
76
+ "devDependencies": {
77
+ "@types/react": "^19.1.13",
78
+ "@types/react-dom": "^19.1.9",
79
+ "react": "19.1.0",
80
+ "react-dom": "19.1.0",
81
+ "tsdown": "^0.22.14",
82
+ "typescript": "^7.0.2"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "tsdown": {
88
+ "entry": [
89
+ "src/index.ts",
90
+ "src/category.tsx",
91
+ "src/category-nav.tsx",
92
+ "src/use-active-category.ts",
93
+ "src/menu.tsx",
94
+ "src/product-card.tsx",
95
+ "src/product-detail.tsx",
96
+ "src/cart.tsx",
97
+ "src/checkout.tsx"
98
+ ],
99
+ "format": [
100
+ "esm"
101
+ ],
102
+ "platform": "neutral",
103
+ "dts": true,
104
+ "clean": true,
105
+ "deps": {
106
+ "neverBundle": [
107
+ "react",
108
+ "react-dom",
109
+ "/^react\\//"
110
+ ]
111
+ }
112
+ },
113
+ "scripts": {
114
+ "build": "tsdown",
115
+ "dev": "tsdown --watch",
116
+ "check-types": "tsc --noEmit",
117
+ "lint": "oxlint ./src --max-warnings=0",
118
+ "format": "oxfmt ./src",
119
+ "check-format": "oxfmt --check ./src"
120
+ }
121
+ }