kmod-cli 1.7.9 → 1.8.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/README.md CHANGED
@@ -38,70 +38,70 @@ npx kumod add --all # add all component
38
38
  npx kumod add button # add button component
39
39
  #...any other command
40
40
  ```
41
- ## Components
42
-
43
- - access-denied
44
- - api-service
45
- - breadcumb
46
- - button
47
- - calculate
48
- - calendar
49
- - color-by-text
50
- - column-table
51
- - config
52
- - count-down
53
- - count-input
54
- - data-table
55
- - date-input
56
- - date-range-picker
57
- - datetime-picker
58
- - fade-on-scroll
59
- - feature-config
60
- - feature-guard
61
- - gradient-outline
62
- - gradient-svg
63
- - grid-layout
64
- - hash-aes
65
- - hydrate-guard
66
- - idb
67
- - image
68
- - input
69
- - keys
70
- - kookies
71
- - label
72
- - lib
73
- - list-map
74
- - loader-slash-gradient
75
- - masonry-gallery
76
- - modal
77
- - multi-select
78
- - non-hydration
79
- - period-input
80
- - popover
81
- - portal
82
- - query
83
- - rbac
84
- - readme
85
- - refine-provider
86
- - safe-action
87
- - segments-circle
88
- - select
89
- - simple-validate
90
- - single-select
91
- - spam-guard
92
- - storage
93
- - stripe-effect
94
- - stroke-circle
95
- - switch
96
- - table
97
- - text-hover-effect
98
- - time-picker
99
- - time-picker-input
100
- - time-picker-utils
101
- - timout-loader
102
- - toast
103
- - utils
104
-
41
+ ## Components
42
+
43
+ - access-denied
44
+ - api-service
45
+ - breadcumb
46
+ - button
47
+ - calculate
48
+ - calendar
49
+ - color-by-text
50
+ - column-table
51
+ - config
52
+ - count-down
53
+ - count-input
54
+ - data-table
55
+ - date-input
56
+ - date-range-picker
57
+ - datetime-picker
58
+ - fade-on-scroll
59
+ - feature-config
60
+ - feature-guard
61
+ - gradient-outline
62
+ - gradient-svg
63
+ - grid-layout
64
+ - hash-aes
65
+ - hydrate-guard
66
+ - idb
67
+ - image
68
+ - input
69
+ - keys
70
+ - kookies
71
+ - label
72
+ - lib
73
+ - list-map
74
+ - loader-slash-gradient
75
+ - masonry-gallery
76
+ - modal
77
+ - multi-select
78
+ - non-hydration
79
+ - period-input
80
+ - popover
81
+ - portal
82
+ - query
83
+ - rbac
84
+ - readme
85
+ - refine-provider
86
+ - safe-action
87
+ - scroll-spy
88
+ - segments-circle
89
+ - select
90
+ - simple-validate
91
+ - single-select
92
+ - spam-guard
93
+ - storage
94
+ - stripe-effect
95
+ - stroke-circle
96
+ - switch
97
+ - table
98
+ - text-hover-effect
99
+ - time-picker
100
+ - time-picker-input
101
+ - time-picker-utils
102
+ - timout-loader
103
+ - toast
104
+ - utils
105
105
  ## Contributing
106
106
 
107
107
  Contributions are welcome! Please open issues or pull requests.
@@ -0,0 +1,268 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+
11
+ interface ScrollSpyContextValue {
12
+ activeId: string | null;
13
+ register: (id: string, el: HTMLElement) => void;
14
+ unregister: (id: string) => void;
15
+ setUpdatedActiveId: (id: string | null) => void;
16
+ }
17
+
18
+ const ScrollSpyContext = createContext<ScrollSpyContextValue | null>(null);
19
+
20
+ export interface ScrollSpyProviderProps {
21
+ children: React.ReactNode;
22
+ /**
23
+ * container/viewport to scroll, default = viewport
24
+ * how to container to scroll? - set root is 'containerRef.current'
25
+ */
26
+ root?: HTMLElement | null;
27
+ /**
28
+ * active area (default is middle of viewport)
29
+ * ex: center - "-50% 0px -50% 0px"
30
+ * ex: top - "0% 0px -50% 0px"
31
+ * ex: bottom - "100% 0px -50% 0px"
32
+ */
33
+ rootMargin?: string;
34
+ scrollOptions?: ScrollOptions;
35
+ }
36
+
37
+ /**
38
+ * @param behavior default = smooth
39
+ * @param block default = start
40
+ * @param inline default = center
41
+ * @param distanceDeviation default = 80 - The distance the user has to scroll before the element is considered in view
42
+ */
43
+ export interface ScrollOptions {
44
+ behavior?: ScrollBehavior;
45
+ block?: ScrollLogicalPosition;
46
+ inline?: ScrollLogicalPosition;
47
+ distanceDeviation?: number;
48
+ }
49
+
50
+ export function ScrollSpyProvider({
51
+ children,
52
+ root = null,
53
+ rootMargin = "-50% 0px -50% 0px",
54
+ scrollOptions = {
55
+ behavior: "smooth",
56
+ block: "start",
57
+ inline: "center",
58
+ distanceDeviation: 80,
59
+ },
60
+ }: ScrollSpyProviderProps) {
61
+ const [activeId, setActiveId] = useState<string | null>(null);
62
+ const observerRef = useRef<IntersectionObserver | null>(null);
63
+ const elementsRef = useRef<Map<string, HTMLElement>>(new Map());
64
+ const isManualScrollRef = useRef(false);
65
+
66
+ useEffect(() => {
67
+ observerRef.current = new IntersectionObserver(
68
+ (entries) => {
69
+ if (isManualScrollRef.current) return;
70
+ entries.forEach((entry) => {
71
+ if (entry.isIntersecting) {
72
+ const id = entry.target.getAttribute("data-scrollspy-id");
73
+ if (id) setActiveId(id);
74
+ }
75
+ });
76
+ },
77
+ {
78
+ root,
79
+ rootMargin,
80
+ threshold: 0,
81
+ }
82
+ );
83
+
84
+ elementsRef.current.forEach((el) => {
85
+ observerRef.current?.observe(el);
86
+ });
87
+
88
+ return () => observerRef.current?.disconnect();
89
+ }, [root, rootMargin]);
90
+
91
+ const register = (id: string, el: HTMLElement) => {
92
+ elementsRef.current.set(id, el);
93
+ observerRef.current?.observe(el);
94
+ };
95
+
96
+ const unregister = (id: string) => {
97
+ const el = elementsRef.current.get(id);
98
+ if (el) observerRef.current?.unobserve(el);
99
+ elementsRef.current.delete(id);
100
+ };
101
+
102
+ const setUpdatedActiveId = (id: string | null) => {
103
+ if (!id) return;
104
+
105
+
106
+ const el = elementsRef.current.get(id);
107
+ if (!el) return;
108
+
109
+
110
+ // turn on manual mode
111
+ isManualScrollRef.current = true;
112
+
113
+
114
+ // set active when clicking
115
+ setActiveId(id);
116
+
117
+
118
+ // scroll to section (center viewport)
119
+ const deviation = scrollOptions?.distanceDeviation ?? 0;
120
+ // ===== WINDOW SCROLL =====
121
+ if (!root) {
122
+ const rect = el.getBoundingClientRect();
123
+
124
+
125
+ const y =
126
+ rect.top +
127
+ window.scrollY -
128
+ deviation;
129
+
130
+
131
+ window.scrollTo({
132
+ top: y,
133
+ behavior: scrollOptions?.behavior ?? "smooth",
134
+ });
135
+ }
136
+ // ===== CONTAINER SCROLL =====
137
+ else {
138
+ const container = root;
139
+ const rect = el.getBoundingClientRect();
140
+ const containerRect = container.getBoundingClientRect();
141
+
142
+
143
+ const y =
144
+ rect.top -
145
+ containerRect.top +
146
+ container.scrollTop -
147
+ deviation;
148
+
149
+
150
+ container.scrollTo({
151
+ top: y,
152
+ behavior: scrollOptions?.behavior ?? "smooth",
153
+ });
154
+ }
155
+
156
+ // after scroll settle → open observer again
157
+ // (300–500ms at any smooth scroll)
158
+ window.setTimeout(() => {
159
+ isManualScrollRef.current = false;
160
+ }, 500);
161
+ };
162
+
163
+ return (
164
+ <ScrollSpyContext.Provider
165
+ value={{ activeId, register, unregister, setUpdatedActiveId }}
166
+ >
167
+ {children}
168
+ </ScrollSpyContext.Provider>
169
+ );
170
+ }
171
+
172
+ export function useScrollSpy() {
173
+ const ctx = useContext(ScrollSpyContext);
174
+ if (!ctx) {
175
+ throw new Error("useScrollSpy must be used inside ScrollSpyProvider");
176
+ }
177
+ return ctx;
178
+ }
179
+
180
+ interface ScrollSpySectionProps extends React.HTMLAttributes<HTMLDivElement> {
181
+ id: string;
182
+ children: React.ReactNode;
183
+ className?: string;
184
+ }
185
+
186
+
187
+ export function ScrollSpySection({
188
+ id,
189
+ children,
190
+ className,
191
+ ...props
192
+ }: ScrollSpySectionProps) {
193
+ const ref = useRef<HTMLDivElement | null>(null);
194
+ const { register, unregister } = useScrollSpy();
195
+
196
+
197
+ useEffect(() => {
198
+ if (!ref.current) return;
199
+
200
+
201
+ const el = ref.current;
202
+ el.setAttribute("data-scrollspy-id", id);
203
+
204
+
205
+ register(id, el);
206
+ return () => unregister(id);
207
+ }, [id, register, unregister]);
208
+
209
+
210
+ return (
211
+ <div ref={ref} id={id} className={className} {...props} >
212
+ {children}
213
+ </div>
214
+ );
215
+ }
216
+
217
+ // USAGE:
218
+ // #wrap your layout to ScrollSpyProvider
219
+ // <ScrollSpyProvider>
220
+ // <YourLayout />
221
+ // </ScrollSpyProvider>
222
+ //
223
+ // #inside <ScrollSpyProvider> use hook useScrollSpy to get 'activeId' to show active
224
+ //
225
+ // const { activeId, setUpdatedActiveId } = useScrollSpy();
226
+ //
227
+ // <div>
228
+ // items.map((item) => (
229
+ // <button key={item.id} onClick={() => setUpdatedActiveId(item.id)} className={`${activeId === item.id && "bg-gray-100"}`}>
230
+ // <Section title={item.title} />
231
+ // </button>
232
+ // ))
233
+ // </div>
234
+ // <div>
235
+ // <ScrollSpySection id="id-1">
236
+ // <Section title="General settings" />
237
+ // </ScrollSpySection>
238
+ //
239
+ // <ScrollSpySection id="id-2">
240
+ // <Section title="Security" />
241
+ // </ScrollSpySection>
242
+ // </div>
243
+ //
244
+ // =========================================================================================
245
+ //
246
+ // CUSTOM USAGE SECTION:
247
+ // const ref = useRef<HTMLDivElement>(null);
248
+ //
249
+ // const { register, unregister } = useScrollSpy();
250
+ //
251
+ // useEffect(() => {
252
+ // if (!ref.current) return;
253
+ //
254
+ // this id is unique for each section
255
+ // ref.current.setAttribute("data-scrollspy-id", id);
256
+ //
257
+ // // register section to ScrollSpy
258
+ // register(id, ref.current);
259
+ //
260
+ // // cleanup when unmount
261
+ // return () => unregister(id);
262
+ // }, [id, register, unregister]);
263
+ //
264
+ // <ScrollSpySection id="id-1">
265
+ // <Section title="General settings" />
266
+ // </ScrollSpySection>
267
+
268
+