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
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
- time-picker
|
|
100
|
-
- time-picker-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
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
|
+
|