kmod-cli 1.7.8 → 1.8.0
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
|
+
|
|
@@ -230,31 +230,27 @@ export type PaginationProps<TData, TValue> = {
|
|
|
230
230
|
fns: DataTablePaginationFns<TData>;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
export type
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
export type ToolbarDataTableProps<TData, TValue> = ({
|
|
233
|
+
export type DataTableProps<TData, TValue> = {
|
|
234
|
+
columns: ColumnDef<TData, TValue>[];
|
|
235
|
+
data: TData[];
|
|
236
|
+
toolbarTable?: ({
|
|
238
237
|
table,
|
|
239
238
|
fns,
|
|
240
239
|
}: ToolbarProps<TData>) => ReactNode | ReactNode[];
|
|
241
|
-
|
|
240
|
+
paginationTable?: ({
|
|
242
241
|
table,
|
|
243
242
|
fns,
|
|
244
243
|
}: PaginationProps<TData, TValue>) => ReactNode | ReactNode[];
|
|
245
|
-
|
|
246
|
-
export type DataTableProps<TData, TValue> = {
|
|
247
|
-
columns: ColumnDef<TData, TValue>[];
|
|
248
|
-
data: TData[];
|
|
249
|
-
toolbarTable?: ToolbarDataTableProps<TData, TValue>;
|
|
250
|
-
paginationTable?: PaginationDataTableProps<TData, TValue>;
|
|
251
244
|
isLoading?: boolean;
|
|
252
245
|
classNames?: TableClassNames;
|
|
253
246
|
alternate?: "even" | "odd";
|
|
254
247
|
alternateColor?: string;
|
|
255
248
|
emptyLabel?: string;
|
|
256
249
|
showSortIconHeader?: boolean;
|
|
257
|
-
surfix?:
|
|
250
|
+
surfix?: ({
|
|
251
|
+
header,
|
|
252
|
+
showSortIconHeader,
|
|
253
|
+
}: SurfixProps<TData, TValue>) => ReactNode | ReactNode[];
|
|
258
254
|
enableSort?: boolean;
|
|
259
255
|
useTableProps?: UseTableProps<TData, TValue>;
|
|
260
256
|
initialState?: InitialTableState;
|
package/components.json
CHANGED
|
@@ -193,6 +193,11 @@
|
|
|
193
193
|
],
|
|
194
194
|
"devDependencies": []
|
|
195
195
|
},
|
|
196
|
+
"scroll-spy": {
|
|
197
|
+
"path": "component-templates/components/scroll-spy.tsx",
|
|
198
|
+
"dependencies": [],
|
|
199
|
+
"devDependencies": []
|
|
200
|
+
},
|
|
196
201
|
"segments-circle": {
|
|
197
202
|
"path": "component-templates/components/segments-circle.tsx",
|
|
198
203
|
"dependencies": [],
|