kmod-cli 1.0.10
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 +53 -0
- package/bin/gen-components.js +68 -0
- package/bin/index.js +153 -0
- package/component-templates/components/access-denied.tsx +130 -0
- package/component-templates/components/breadcumb.tsx +42 -0
- package/component-templates/components/count-down.tsx +94 -0
- package/component-templates/components/count-input.tsx +221 -0
- package/component-templates/components/date-range-calendar/button.tsx +61 -0
- package/component-templates/components/date-range-calendar/calendar.tsx +132 -0
- package/component-templates/components/date-range-calendar/date-input.tsx +259 -0
- package/component-templates/components/date-range-calendar/date-range-picker.tsx +594 -0
- package/component-templates/components/date-range-calendar/label.tsx +31 -0
- package/component-templates/components/date-range-calendar/popover.tsx +32 -0
- package/component-templates/components/date-range-calendar/select.tsx +125 -0
- package/component-templates/components/date-range-calendar/switch.tsx +30 -0
- package/component-templates/components/datetime-picker/button.tsx +61 -0
- package/component-templates/components/datetime-picker/calendar.tsx +156 -0
- package/component-templates/components/datetime-picker/datetime-picker.tsx +75 -0
- package/component-templates/components/datetime-picker/input.tsx +20 -0
- package/component-templates/components/datetime-picker/label.tsx +18 -0
- package/component-templates/components/datetime-picker/period-input.tsx +62 -0
- package/component-templates/components/datetime-picker/popover.tsx +32 -0
- package/component-templates/components/datetime-picker/select.tsx +125 -0
- package/component-templates/components/datetime-picker/time-picker-input.tsx +131 -0
- package/component-templates/components/datetime-picker/time-picker-utils.tsx +204 -0
- package/component-templates/components/datetime-picker/time-picker.tsx +59 -0
- package/component-templates/components/gradient-outline.tsx +233 -0
- package/component-templates/components/gradient-svg.tsx +157 -0
- package/component-templates/components/grid-layout.tsx +69 -0
- package/component-templates/components/hydrate-guard.tsx +40 -0
- package/component-templates/components/image.tsx +92 -0
- package/component-templates/components/loader-slash-gradient.tsx +85 -0
- package/component-templates/components/masonry-gallery.tsx +221 -0
- package/component-templates/components/modal.tsx +110 -0
- package/component-templates/components/multi-select.tsx +447 -0
- package/component-templates/components/non-hydration.tsx +27 -0
- package/component-templates/components/portal.tsx +34 -0
- package/component-templates/components/segments-circle.tsx +235 -0
- package/component-templates/components/single-select.tsx +248 -0
- package/component-templates/components/stroke-circle.tsx +57 -0
- package/component-templates/components/table/column-table.tsx +15 -0
- package/component-templates/components/table/data-table.tsx +339 -0
- package/component-templates/components/table/readme.tsx +95 -0
- package/component-templates/components/table/table.tsx +60 -0
- package/component-templates/components/text-hover-effect.tsx +120 -0
- package/component-templates/components/timout-loader.tsx +52 -0
- package/component-templates/components/toast.tsx +994 -0
- package/component-templates/configs/config.ts +33 -0
- package/component-templates/configs/feature-config.tsx +432 -0
- package/component-templates/configs/keys.ts +7 -0
- package/component-templates/core/api-service.ts +202 -0
- package/component-templates/core/calculate.ts +18 -0
- package/component-templates/core/idb.ts +166 -0
- package/component-templates/core/storage.ts +213 -0
- package/component-templates/hooks/count-down.ts +38 -0
- package/component-templates/hooks/fade-on-scroll.ts +52 -0
- package/component-templates/hooks/safe-action.ts +59 -0
- package/component-templates/hooks/spam-guard.ts +31 -0
- package/component-templates/lib/utils.ts +6 -0
- package/component-templates/providers/feature-guard.tsx +432 -0
- package/component-templates/queries/query.tsx +775 -0
- package/component-templates/utils/colors/color-by-text.ts +307 -0
- package/component-templates/utils/colors/stripe-effect.ts +100 -0
- package/component-templates/utils/hash/hash-aes.ts +35 -0
- package/components.json +348 -0
- package/package.json +60 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// import {cn} from "@/lib/utils";
|
|
2
|
+
// import React from "react";
|
|
3
|
+
|
|
4
|
+
// type ProgressCircleProps = {
|
|
5
|
+
// dashCount: number;
|
|
6
|
+
// dashColor?: string;
|
|
7
|
+
// dashBorderColor?: string;
|
|
8
|
+
// dashBorderWidth?: number;
|
|
9
|
+
// strokeWidth?: number;
|
|
10
|
+
// radius?: number;
|
|
11
|
+
// gapLength?: number;
|
|
12
|
+
// dashColorList?: string[];
|
|
13
|
+
// angle?: any;
|
|
14
|
+
// className?: string;
|
|
15
|
+
// pathClass?: string;
|
|
16
|
+
// };
|
|
17
|
+
|
|
18
|
+
// export const ProgressCircle: React.FC<ProgressCircleProps> = ({
|
|
19
|
+
// dashCount,
|
|
20
|
+
// dashColor = "black",
|
|
21
|
+
// dashBorderColor = "white",
|
|
22
|
+
// dashBorderWidth = 0,
|
|
23
|
+
// strokeWidth = 4,
|
|
24
|
+
// radius = 24,
|
|
25
|
+
// gapLength = 7,
|
|
26
|
+
// dashColorList = [],
|
|
27
|
+
// angle = -83,
|
|
28
|
+
// className,
|
|
29
|
+
// pathClass,
|
|
30
|
+
// }) => {
|
|
31
|
+
// const circumference = 2 * Math.PI * radius;
|
|
32
|
+
|
|
33
|
+
// const totalGapLength = gapLength * dashCount;
|
|
34
|
+
// const totalDashLength = circumference - totalGapLength;
|
|
35
|
+
|
|
36
|
+
// const dashLength = totalDashLength / dashCount;
|
|
37
|
+
// const anglePerDash = (dashLength / circumference) * 360;
|
|
38
|
+
|
|
39
|
+
// const polarToCartesian = (
|
|
40
|
+
// cx: number,
|
|
41
|
+
// cy: number,
|
|
42
|
+
// r: number,
|
|
43
|
+
// angle: number
|
|
44
|
+
// ) => {
|
|
45
|
+
// const rad = (angle * Math.PI) / 180;
|
|
46
|
+
// return {
|
|
47
|
+
// x: cx + r * Math.cos(rad),
|
|
48
|
+
// y: cy + r * Math.sin(rad),
|
|
49
|
+
// };
|
|
50
|
+
// };
|
|
51
|
+
|
|
52
|
+
// const dashes = [];
|
|
53
|
+
// let currentAngle = angle;
|
|
54
|
+
|
|
55
|
+
// for (let i = 0; i < dashCount; i++) {
|
|
56
|
+
// const startAngle = currentAngle;
|
|
57
|
+
// const endAngle = startAngle + anglePerDash;
|
|
58
|
+
|
|
59
|
+
// const largeArcFlag = endAngle - startAngle > 180 ? 1 : 0;
|
|
60
|
+
|
|
61
|
+
// const start = polarToCartesian(
|
|
62
|
+
// radius + strokeWidth,
|
|
63
|
+
// radius + strokeWidth,
|
|
64
|
+
// radius,
|
|
65
|
+
// startAngle
|
|
66
|
+
// );
|
|
67
|
+
// const end = polarToCartesian(
|
|
68
|
+
// radius + strokeWidth,
|
|
69
|
+
// radius + strokeWidth,
|
|
70
|
+
// radius,
|
|
71
|
+
// endAngle
|
|
72
|
+
// );
|
|
73
|
+
|
|
74
|
+
// dashes.push(
|
|
75
|
+
// <path
|
|
76
|
+
// className={cn(pathClass)}
|
|
77
|
+
// key={i}
|
|
78
|
+
// d={`M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`}
|
|
79
|
+
// fill="none"
|
|
80
|
+
// stroke={dashColorList[i] || dashColor}
|
|
81
|
+
// strokeWidth={strokeWidth}
|
|
82
|
+
// strokeLinecap="round"
|
|
83
|
+
// strokeLinejoin="round"
|
|
84
|
+
// style={{
|
|
85
|
+
// strokeWidth: dashBorderWidth > 0 ? dashBorderWidth : strokeWidth,
|
|
86
|
+
// }}
|
|
87
|
+
// />
|
|
88
|
+
// );
|
|
89
|
+
|
|
90
|
+
// currentAngle = endAngle + (gapLength / circumference) * 360;
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// return (
|
|
94
|
+
// <svg
|
|
95
|
+
// className={cn(className)}
|
|
96
|
+
// height={radius * 2 + strokeWidth * 2}
|
|
97
|
+
// width={radius * 2 + strokeWidth * 2}
|
|
98
|
+
// >
|
|
99
|
+
// {dashes}
|
|
100
|
+
// </svg>
|
|
101
|
+
// );
|
|
102
|
+
// };
|
|
103
|
+
|
|
104
|
+
import React from 'react';
|
|
105
|
+
|
|
106
|
+
import { cn } from '../lib/utils';
|
|
107
|
+
|
|
108
|
+
export type SegmentCircleProps = {
|
|
109
|
+
dashCount: number;
|
|
110
|
+
colors?: string[]; // list of colors for each dash
|
|
111
|
+
color?: string; // fallback color if colors array is not provided
|
|
112
|
+
radius?: number;
|
|
113
|
+
strokeWidth?: number;
|
|
114
|
+
gapLength?: number;
|
|
115
|
+
startAngle?: number;
|
|
116
|
+
borderWidth?: number;
|
|
117
|
+
borderColor?: string;
|
|
118
|
+
className?: string;
|
|
119
|
+
pathClass?: string;
|
|
120
|
+
animate?: boolean; // animation rotation
|
|
121
|
+
speed?: number; // animation speed (s)
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* SegmentCircle component renders a circle with dashes (segments).
|
|
126
|
+
* It accepts various props to customize the appearance and behavior of the circle.
|
|
127
|
+
* @param {SegmentCircleProps} props - Properties for customizing the SegmentCircle component.
|
|
128
|
+
* @returns {JSX.Element} Rendered SegmentCircle component.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* SegmentCircleProps:
|
|
132
|
+
* - dashCount: number of dashes (segments)
|
|
133
|
+
* - colors: list of colors for each dash (fallbacks to color if not provided)
|
|
134
|
+
* - color: fallback color if colors array is not provided
|
|
135
|
+
* - radius: radius of the circle
|
|
136
|
+
* - strokeWidth: stroke width of the dashes
|
|
137
|
+
* - gapLength: length of the gap between dashes
|
|
138
|
+
* - startAngle: starting angle of the first dash
|
|
139
|
+
* - borderWidth: border width (if needed)
|
|
140
|
+
* - borderColor: border color (if needed)
|
|
141
|
+
* - className: class name for the container element
|
|
142
|
+
* - pathClass: class name for each dash path
|
|
143
|
+
* - animate: whether to animate the rotation of the circle
|
|
144
|
+
* - speed: animation speed (seconds)
|
|
145
|
+
*/
|
|
146
|
+
export const SegmentCircle: React.FC<SegmentCircleProps> = ({
|
|
147
|
+
dashCount,
|
|
148
|
+
colors = [],
|
|
149
|
+
color = "black",
|
|
150
|
+
radius = 24,
|
|
151
|
+
strokeWidth = 4,
|
|
152
|
+
gapLength = 7,
|
|
153
|
+
startAngle = -90,
|
|
154
|
+
borderWidth = 0,
|
|
155
|
+
borderColor = "white",
|
|
156
|
+
className,
|
|
157
|
+
pathClass,
|
|
158
|
+
animate = false,
|
|
159
|
+
speed = 2,
|
|
160
|
+
}) => {
|
|
161
|
+
const circumference = 2 * Math.PI * radius;
|
|
162
|
+
const totalGapLength = gapLength * dashCount;
|
|
163
|
+
const totalDashLength = circumference - totalGapLength;
|
|
164
|
+
const dashLength = totalDashLength / dashCount;
|
|
165
|
+
const anglePerDash = (dashLength / circumference) * 360;
|
|
166
|
+
|
|
167
|
+
const polarToCartesian = (cx: number, cy: number, r: number, angle: number) => {
|
|
168
|
+
const rad = (angle * Math.PI) / 180;
|
|
169
|
+
return {
|
|
170
|
+
x: cx + r * Math.cos(rad),
|
|
171
|
+
y: cy + r * Math.sin(rad),
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const dashes: React.ReactNode[] = [];
|
|
176
|
+
let currentAngle = startAngle;
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < dashCount; i++) {
|
|
179
|
+
const start = polarToCartesian(radius, radius, radius, currentAngle);
|
|
180
|
+
const end = polarToCartesian(radius, radius, radius, currentAngle + anglePerDash);
|
|
181
|
+
const largeArcFlag = anglePerDash > 180 ? 1 : 0;
|
|
182
|
+
|
|
183
|
+
// border (if needed)
|
|
184
|
+
if (borderWidth > 0) {
|
|
185
|
+
dashes.push(
|
|
186
|
+
<path
|
|
187
|
+
key={`border-${i}`}
|
|
188
|
+
d={`M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`}
|
|
189
|
+
fill="none"
|
|
190
|
+
stroke={borderColor}
|
|
191
|
+
strokeWidth={strokeWidth + borderWidth * 2}
|
|
192
|
+
strokeLinecap="round"
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// main dash
|
|
198
|
+
dashes.push(
|
|
199
|
+
<path
|
|
200
|
+
className={cn(pathClass)}
|
|
201
|
+
key={i}
|
|
202
|
+
d={`M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`}
|
|
203
|
+
fill="none"
|
|
204
|
+
stroke={colors[i] || color}
|
|
205
|
+
strokeWidth={strokeWidth}
|
|
206
|
+
strokeLinecap="round"
|
|
207
|
+
/>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
currentAngle += anglePerDash + (gapLength / circumference) * 360;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<svg
|
|
215
|
+
className={cn(className)}
|
|
216
|
+
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
|
|
217
|
+
width={radius * 2}
|
|
218
|
+
height={radius * 2}
|
|
219
|
+
style={
|
|
220
|
+
animate
|
|
221
|
+
? { animation: `spin ${speed}s linear infinite` }
|
|
222
|
+
: undefined
|
|
223
|
+
}
|
|
224
|
+
>
|
|
225
|
+
{dashes}
|
|
226
|
+
{animate && (
|
|
227
|
+
<style>{`
|
|
228
|
+
@keyframes spin {
|
|
229
|
+
100% { transform: rotate(360deg); }
|
|
230
|
+
}
|
|
231
|
+
`}</style>
|
|
232
|
+
)}
|
|
233
|
+
</svg>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CheckIcon,
|
|
7
|
+
ChevronDown,
|
|
8
|
+
Loader2,
|
|
9
|
+
Plus,
|
|
10
|
+
WandSparkles,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
import { cn } from '../lib/utils';
|
|
14
|
+
import { Button } from '../ui/button';
|
|
15
|
+
import {
|
|
16
|
+
Command,
|
|
17
|
+
CommandEmpty,
|
|
18
|
+
CommandGroup,
|
|
19
|
+
CommandInput,
|
|
20
|
+
CommandItem,
|
|
21
|
+
CommandList,
|
|
22
|
+
CommandSeparator,
|
|
23
|
+
} from '../ui/command';
|
|
24
|
+
import {
|
|
25
|
+
Popover,
|
|
26
|
+
PopoverContent,
|
|
27
|
+
PopoverTrigger,
|
|
28
|
+
} from '../ui/popover';
|
|
29
|
+
import { Separator } from '../ui/separator';
|
|
30
|
+
|
|
31
|
+
interface SingleSelectProps {
|
|
32
|
+
options: {
|
|
33
|
+
label: string;
|
|
34
|
+
value: string;
|
|
35
|
+
icon?: React.ComponentType<{ className?: string }>;
|
|
36
|
+
}[];
|
|
37
|
+
|
|
38
|
+
value?: string;
|
|
39
|
+
onValueChange: (value?: string) => void;
|
|
40
|
+
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
animation?: number;
|
|
43
|
+
modalPopover?: boolean;
|
|
44
|
+
className?: string;
|
|
45
|
+
|
|
46
|
+
createNewWhenEmpty?: boolean;
|
|
47
|
+
onCreateNewWhenEmpty?: (
|
|
48
|
+
value: string,
|
|
49
|
+
) => Promise<{ label: string; value: string; icon?: React.ComponentType<{ className?: string }> } | void>;
|
|
50
|
+
emptyLabel?: string;
|
|
51
|
+
newLabel?: React.ReactNode;
|
|
52
|
+
contentClassName?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const SingleSelect: React.FC<SingleSelectProps> = ({
|
|
56
|
+
options,
|
|
57
|
+
value,
|
|
58
|
+
onValueChange,
|
|
59
|
+
placeholder = "Select an option",
|
|
60
|
+
animation = 0,
|
|
61
|
+
modalPopover = false,
|
|
62
|
+
className,
|
|
63
|
+
createNewWhenEmpty = false,
|
|
64
|
+
onCreateNewWhenEmpty,
|
|
65
|
+
emptyLabel = "No options available",
|
|
66
|
+
contentClassName,
|
|
67
|
+
newLabel,
|
|
68
|
+
}) => {
|
|
69
|
+
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
|
70
|
+
const [searchInput, setSearchInput] = React.useState("");
|
|
71
|
+
const [isCreating, setIsCreating] = React.useState(false);
|
|
72
|
+
const [isAnimating, setIsAnimating] = React.useState(false);
|
|
73
|
+
|
|
74
|
+
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
|
75
|
+
const [triggerWidth, setTriggerWidth] = React.useState<number | undefined>();
|
|
76
|
+
|
|
77
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
const trigger = triggerRef.current;
|
|
82
|
+
if (!trigger) return;
|
|
83
|
+
|
|
84
|
+
const updateWidth = () => {
|
|
85
|
+
setTriggerWidth(trigger.offsetWidth);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
updateWidth(); // set lần đầu
|
|
89
|
+
|
|
90
|
+
const resizeObserver = new ResizeObserver(updateWidth);
|
|
91
|
+
resizeObserver.observe(trigger);
|
|
92
|
+
|
|
93
|
+
return () => resizeObserver.disconnect();
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
const handleClear = () => {
|
|
99
|
+
onValueChange(undefined);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
103
|
+
if (event.key === "Enter") {
|
|
104
|
+
setIsPopoverOpen(true);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleCreate = async () => {
|
|
109
|
+
try {
|
|
110
|
+
setIsCreating(true);
|
|
111
|
+
const newItem = await onCreateNewWhenEmpty?.(searchInput);
|
|
112
|
+
if (newItem) {
|
|
113
|
+
onValueChange(newItem.value);
|
|
114
|
+
setSearchInput("");
|
|
115
|
+
setIsPopoverOpen(false);
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error("create error:", err);
|
|
119
|
+
} finally {
|
|
120
|
+
setIsCreating(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal={modalPopover}>
|
|
126
|
+
<PopoverTrigger asChild>
|
|
127
|
+
<Button
|
|
128
|
+
ref={triggerRef}
|
|
129
|
+
onClick={() => setIsPopoverOpen(true)}
|
|
130
|
+
className={cn(
|
|
131
|
+
"border-border flex h-auto min-h-10 w-full items-center justify-between rounded-md border bg-inherit p-1 hover:bg-inherit [&_svg]:pointer-events-auto",
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
{selectedOption ? (
|
|
136
|
+
<div className="flex w-full items-center justify-between">
|
|
137
|
+
<div className="flex items-center">
|
|
138
|
+
{selectedOption.icon && <selectedOption.icon className="mr-2 h-4 w-4" />}
|
|
139
|
+
<span className="text-foreground mx-3 text-sm">{selectedOption.label}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="flex items-center justify-between">
|
|
142
|
+
{/* <XIcon
|
|
143
|
+
className="text-muted-foreground mx-2 h-4 cursor-pointer"
|
|
144
|
+
onClick={(event) => {
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
handleClear();
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
<Separator orientation="vertical" className="flex h-full min-h-6" /> */}
|
|
150
|
+
<ChevronDown className="text-muted-foreground mx-2 h-4 cursor-pointer" />
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
) : (
|
|
154
|
+
<div className="mx-auto flex w-full items-center justify-between">
|
|
155
|
+
<span className="text-muted-foreground mx-3 text-sm">{placeholder}</span>
|
|
156
|
+
<ChevronDown className="text-muted-foreground mx-2 h-4 cursor-pointer" />
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</Button>
|
|
160
|
+
</PopoverTrigger>
|
|
161
|
+
|
|
162
|
+
<PopoverContent style={{ width: triggerWidth || "auto" }} className={cn("border-red-500 w-full p-0", contentClassName)} align="start">
|
|
163
|
+
<Command>
|
|
164
|
+
<CommandInput
|
|
165
|
+
placeholder="Search..."
|
|
166
|
+
value={searchInput}
|
|
167
|
+
onValueChange={setSearchInput}
|
|
168
|
+
onKeyDown={handleInputKeyDown}
|
|
169
|
+
/>
|
|
170
|
+
<CommandList className={(createNewWhenEmpty && searchInput.trim() !== "" && "p-1") || ""}>
|
|
171
|
+
<CommandEmpty className={(createNewWhenEmpty && searchInput.trim() !== "" && "py-0") || ""}>
|
|
172
|
+
{createNewWhenEmpty && searchInput.trim() !== "" && onCreateNewWhenEmpty ? (
|
|
173
|
+
<div
|
|
174
|
+
className={cn(
|
|
175
|
+
"text-muted-foreground hover:bg-accent flex h-10 cursor-pointer items-center justify-center rounded-md px-4 text-sm",
|
|
176
|
+
isCreating && "pointer-events-none opacity-50",
|
|
177
|
+
)}
|
|
178
|
+
onClick={() => handleCreate()}
|
|
179
|
+
>
|
|
180
|
+
{isCreating ? (
|
|
181
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
182
|
+
) : (
|
|
183
|
+
<>
|
|
184
|
+
{newLabel ?? <Plus className="mr-2 h-4 w-4" />}
|
|
185
|
+
<span className="font-medium">"{searchInput}"</span>
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
) : (
|
|
190
|
+
<div className="py-2 text-center text-sm text-muted-foreground">{emptyLabel}</div>
|
|
191
|
+
)}
|
|
192
|
+
</CommandEmpty>
|
|
193
|
+
|
|
194
|
+
<CommandGroup>
|
|
195
|
+
{options.map((option) => {
|
|
196
|
+
const isSelected = option.value === value;
|
|
197
|
+
return (
|
|
198
|
+
<CommandItem
|
|
199
|
+
key={option.value}
|
|
200
|
+
onSelect={() => {
|
|
201
|
+
onValueChange(option.value);
|
|
202
|
+
setIsPopoverOpen(false);
|
|
203
|
+
}}
|
|
204
|
+
className="cursor-pointer"
|
|
205
|
+
>
|
|
206
|
+
<div
|
|
207
|
+
className={cn(
|
|
208
|
+
"border-border mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
|
|
209
|
+
isSelected ? "bg-primary text-foreground" : "opacity-50 [&_svg]:invisible",
|
|
210
|
+
)}
|
|
211
|
+
>
|
|
212
|
+
<CheckIcon className="h-4 w-4" />
|
|
213
|
+
</div>
|
|
214
|
+
{option.icon && <option.icon className="text-muted-foreground mr-2 h-4 w-4" />}
|
|
215
|
+
<span>{option.label}</span>
|
|
216
|
+
</CommandItem>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</CommandGroup>
|
|
220
|
+
<CommandSeparator />
|
|
221
|
+
<CommandGroup>
|
|
222
|
+
<div className="flex items-center justify-between">
|
|
223
|
+
{value && (
|
|
224
|
+
<>
|
|
225
|
+
<CommandItem onSelect={handleClear} className="flex-1 cursor-pointer justify-center">
|
|
226
|
+
Clear
|
|
227
|
+
</CommandItem>
|
|
228
|
+
<Separator orientation="vertical" className="flex h-full min-h-6" />
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
231
|
+
<CommandItem onSelect={() => setIsPopoverOpen(false)} className="flex-1 cursor-pointer justify-center">
|
|
232
|
+
Close
|
|
233
|
+
</CommandItem>
|
|
234
|
+
</div>
|
|
235
|
+
</CommandGroup>
|
|
236
|
+
</CommandList>
|
|
237
|
+
</Command>
|
|
238
|
+
</PopoverContent>
|
|
239
|
+
|
|
240
|
+
{animation > 0 && value && (
|
|
241
|
+
<WandSparkles
|
|
242
|
+
className={cn("text-foreground bg-background my-2 h-3 w-3 cursor-pointer", isAnimating ? "" : "text-muted-foreground")}
|
|
243
|
+
onClick={() => setIsAnimating(!isAnimating)}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
</Popover>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export type CircleStrokeProps = {
|
|
4
|
+
radius?: number;
|
|
5
|
+
strokeColor?: string;
|
|
6
|
+
strokeWidth?: number;
|
|
7
|
+
dashCount?: number; // dash count
|
|
8
|
+
gapLength?: number; // dash gap length
|
|
9
|
+
strokeLinecap?: "butt" | "round" | "square";
|
|
10
|
+
strokeLinejoin?: "miter" | "round" | "bevel";
|
|
11
|
+
fill?: string;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
cx?: number;
|
|
15
|
+
cy?: number;
|
|
16
|
+
} & React.HTMLAttributes<SVGElement>;
|
|
17
|
+
|
|
18
|
+
export const CircleStroke: React.FC<CircleStrokeProps> = ({
|
|
19
|
+
radius = 40,
|
|
20
|
+
strokeWidth = 5,
|
|
21
|
+
strokeColor = "black",
|
|
22
|
+
dashCount = 12,
|
|
23
|
+
gapLength = 5,
|
|
24
|
+
strokeLinecap = "round",
|
|
25
|
+
strokeLinejoin = "round",
|
|
26
|
+
fill = "transparent",
|
|
27
|
+
cx = 50,
|
|
28
|
+
cy = 50,
|
|
29
|
+
className,
|
|
30
|
+
style,
|
|
31
|
+
...props
|
|
32
|
+
}) => {
|
|
33
|
+
const circumference = 2 * Math.PI * radius;
|
|
34
|
+
const dashLength = (circumference - dashCount * gapLength) / dashCount;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<svg
|
|
38
|
+
height={(radius + strokeWidth) * 2}
|
|
39
|
+
width={(radius + strokeWidth) * 2}
|
|
40
|
+
className={className}
|
|
41
|
+
style={style}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<circle
|
|
45
|
+
cx={cx}
|
|
46
|
+
cy={cy}
|
|
47
|
+
r={radius}
|
|
48
|
+
stroke={strokeColor}
|
|
49
|
+
strokeWidth={strokeWidth}
|
|
50
|
+
fill={fill}
|
|
51
|
+
strokeDasharray={`${dashLength} ${gapLength}`}
|
|
52
|
+
strokeLinecap={strokeLinecap}
|
|
53
|
+
strokeLinejoin={strokeLinejoin}
|
|
54
|
+
/>
|
|
55
|
+
</svg>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
4
|
+
|
|
5
|
+
export interface DataColumnProps<TData, TValue> {
|
|
6
|
+
arr: ColumnDef<TData, TValue>[];
|
|
7
|
+
onReady?: (columns: ColumnDef<TData, TValue>[]) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DataColumns = <TData, TValue>({ arr, onReady }: DataColumnProps<TData, TValue>): ColumnDef<TData, TValue>[] => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
onReady?.(arr);
|
|
13
|
+
}, [arr]);
|
|
14
|
+
return arr;
|
|
15
|
+
};
|