@vuer-ai/vuer-uikit 0.0.12 → 0.0.13
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/package.json +3 -2
- package/src/highlight-cursor/cursor-context.tsx +23 -0
- package/src/highlight-cursor/cursor-provider.tsx +264 -0
- package/src/highlight-cursor/enhanced-components.tsx +16 -0
- package/src/highlight-cursor/index.ts +21 -0
- package/src/highlight-cursor/tabs-cursor-context.tsx +121 -0
- package/src/highlight-cursor/types.ts +40 -0
- package/src/highlight-cursor/with-cursor.tsx +144 -0
- package/src/index.css +5 -0
- package/src/index.ts +5 -0
- package/src/styles/theme.css +80 -0
- package/src/styles/toast.css +64 -0
- package/src/styles/variables.css +194 -0
- package/src/ui/avatar.tsx +92 -0
- package/src/ui/badge.tsx +68 -0
- package/src/ui/button.tsx +100 -0
- package/src/ui/card.tsx +88 -0
- package/src/ui/checkbox.tsx +76 -0
- package/src/ui/collapsible.tsx +36 -0
- package/src/ui/dropdown.tsx +375 -0
- package/src/ui/index.ts +31 -0
- package/src/ui/input-numbers.tsx +201 -0
- package/src/ui/input.tsx +141 -0
- package/src/ui/layout.tsx +41 -0
- package/src/ui/modal.tsx +198 -0
- package/src/ui/popover.tsx +59 -0
- package/src/ui/radio-group.tsx +56 -0
- package/src/ui/select.tsx +292 -0
- package/src/ui/sheet.tsx +131 -0
- package/src/ui/slider.tsx +189 -0
- package/src/ui/switch.tsx +43 -0
- package/src/ui/tabs.tsx +128 -0
- package/src/ui/textarea.tsx +52 -0
- package/src/ui/theme-context.tsx +80 -0
- package/src/ui/timeline.tsx +717 -0
- package/src/ui/toast.tsx +27 -0
- package/src/ui/toggle-group.tsx +82 -0
- package/src/ui/toggle.tsx +88 -0
- package/src/ui/tooltip.tsx +132 -0
- package/src/ui/tree-view-v2.tsx +300 -0
- package/src/ui/tree-view.tsx +550 -0
- package/src/ui/version-badge.tsx +65 -0
- package/src/utils/cn.ts +21 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/use-local-storage.ts +59 -0
package/src/ui/toast.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Toaster as Sonner, ToasterProps, toast } from "sonner";
|
|
3
|
+
|
|
4
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
5
|
+
const [isClient, setIsClient] = useState(false);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setIsClient(true);
|
|
9
|
+
}, []);
|
|
10
|
+
|
|
11
|
+
// Don't render anything during SSR
|
|
12
|
+
if (!isClient) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Sonner
|
|
18
|
+
toastOptions={{
|
|
19
|
+
className: "vk-toast liquid:liquid-bg",
|
|
20
|
+
descriptionClassName: "vk-toast-description",
|
|
21
|
+
}}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { Toaster, toast };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { ComponentProps, createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
import { toggleVariants } from "./toggle";
|
|
7
|
+
|
|
8
|
+
const ToggleGroupContext = createContext<VariantProps<typeof toggleVariants>>({
|
|
9
|
+
size: "base",
|
|
10
|
+
variant: "primary",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function ToggleGroup({
|
|
14
|
+
className,
|
|
15
|
+
variant,
|
|
16
|
+
size,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: ComponentProps<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof toggleVariants>) {
|
|
20
|
+
return (
|
|
21
|
+
<ToggleGroupPrimitive.Root
|
|
22
|
+
data-slot="toggle-group"
|
|
23
|
+
data-variant={variant}
|
|
24
|
+
data-size={size}
|
|
25
|
+
className={cn(
|
|
26
|
+
"group/toggle-group",
|
|
27
|
+
"flex",
|
|
28
|
+
"w-fit",
|
|
29
|
+
"items-center",
|
|
30
|
+
"rounded-uk-md",
|
|
31
|
+
"data-[variant=outline]:shadow-xs",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<ToggleGroupContext.Provider value={{ variant, size }}>
|
|
37
|
+
{children}
|
|
38
|
+
</ToggleGroupContext.Provider>
|
|
39
|
+
</ToggleGroupPrimitive.Root>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ToggleGroupItem({
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
variant,
|
|
47
|
+
size,
|
|
48
|
+
...props
|
|
49
|
+
}: ComponentProps<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>) {
|
|
50
|
+
const context = useContext(ToggleGroupContext);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<ToggleGroupPrimitive.Item
|
|
54
|
+
data-slot="toggle-group-item"
|
|
55
|
+
data-variant={context.variant || variant}
|
|
56
|
+
data-size={context.size || size}
|
|
57
|
+
className={cn(
|
|
58
|
+
toggleVariants({
|
|
59
|
+
variant: context.variant || variant,
|
|
60
|
+
size: context.size || size,
|
|
61
|
+
}),
|
|
62
|
+
"min-w-0",
|
|
63
|
+
"flex-1",
|
|
64
|
+
"shrink-0",
|
|
65
|
+
"rounded-none",
|
|
66
|
+
"shadow-none",
|
|
67
|
+
"first:rounded-l-md",
|
|
68
|
+
"last:rounded-r-md",
|
|
69
|
+
"focus:z-10",
|
|
70
|
+
"focus-visible:z-10",
|
|
71
|
+
"data-[variant=outline]:border-l-0",
|
|
72
|
+
"data-[variant=outline]:first:border-l",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</ToggleGroupPrimitive.Item>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
|
|
7
|
+
const toggleVariants = cva(
|
|
8
|
+
[
|
|
9
|
+
"inline-flex",
|
|
10
|
+
"items-center",
|
|
11
|
+
"justify-center",
|
|
12
|
+
"shrink-0",
|
|
13
|
+
"whitespace-nowrap",
|
|
14
|
+
"font-normal",
|
|
15
|
+
"transition-all",
|
|
16
|
+
"[&_svg]:shrink-0 ",
|
|
17
|
+
"[&_svg]:pointer-events-none",
|
|
18
|
+
"outline-none",
|
|
19
|
+
"disabled:bg-transparent",
|
|
20
|
+
"disabled:text-bg-tertiary",
|
|
21
|
+
"disabled:cursor-not-allowed",
|
|
22
|
+
],
|
|
23
|
+
{
|
|
24
|
+
variants: {
|
|
25
|
+
variant: {
|
|
26
|
+
primary: [
|
|
27
|
+
"text-text-primary",
|
|
28
|
+
"data-[state=on]:text-text-highlight",
|
|
29
|
+
"data-[state=on]:bg-brand-primary",
|
|
30
|
+
"hover:bg-bg-secondary",
|
|
31
|
+
],
|
|
32
|
+
secondary: [
|
|
33
|
+
"text-text-secondary",
|
|
34
|
+
"data-[state=on]:text-text-highlight",
|
|
35
|
+
"data-[state=on]:bg-bg-secondary",
|
|
36
|
+
"hover:bg-bg-secondary ",
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
size: {
|
|
40
|
+
sm: [
|
|
41
|
+
"text-uk-sm",
|
|
42
|
+
"leading-uk-sm",
|
|
43
|
+
"gap-xxs",
|
|
44
|
+
"p-xs",
|
|
45
|
+
"[&_svg:not([class*='size-'])]:size-3",
|
|
46
|
+
"rounded-uk-xs",
|
|
47
|
+
],
|
|
48
|
+
base: [
|
|
49
|
+
"text-uk-md",
|
|
50
|
+
"leading-uk-md",
|
|
51
|
+
"gap-xs",
|
|
52
|
+
"p-sm",
|
|
53
|
+
"[&_svg:not([class*='size-'])]:size-3.5",
|
|
54
|
+
"rounded-uk-sm",
|
|
55
|
+
],
|
|
56
|
+
lg: [
|
|
57
|
+
"text-uk-lg",
|
|
58
|
+
"leading-uk-lg",
|
|
59
|
+
"gap-xs",
|
|
60
|
+
"p-md",
|
|
61
|
+
"[&_svg:not([class*='size-'])]:size-4",
|
|
62
|
+
"rounded-uk-md",
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
variant: "primary",
|
|
68
|
+
size: "base",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
function Toggle({
|
|
74
|
+
className,
|
|
75
|
+
variant,
|
|
76
|
+
size,
|
|
77
|
+
...props
|
|
78
|
+
}: ComponentProps<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>) {
|
|
79
|
+
return (
|
|
80
|
+
<TogglePrimitive.Root
|
|
81
|
+
data-slot="toggle"
|
|
82
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { Toggle, toggleVariants };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
2
|
+
import { ComponentProps, useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
function TooltipProvider({
|
|
7
|
+
delayDuration = 0,
|
|
8
|
+
children,
|
|
9
|
+
...props
|
|
10
|
+
}: ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
11
|
+
const [isClient, setIsClient] = useState(false);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setIsClient(true);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
// Don't render anything during SSR
|
|
18
|
+
if (!isClient) {
|
|
19
|
+
return <>{children}</>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<TooltipPrimitive.Provider
|
|
24
|
+
data-slot="tooltip-provider"
|
|
25
|
+
delayDuration={delayDuration}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</TooltipPrimitive.Provider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Tooltip({ children, ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
34
|
+
const [isClient, setIsClient] = useState(false);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setIsClient(true);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
// During SSR, just render children without tooltip functionality
|
|
41
|
+
if (!isClient) {
|
|
42
|
+
return <>{children}</>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<TooltipProvider>
|
|
47
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props}>
|
|
48
|
+
{children}
|
|
49
|
+
</TooltipPrimitive.Root>
|
|
50
|
+
</TooltipProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
55
|
+
const [isClient, setIsClient] = useState(false);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setIsClient(true);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
// During SSR, just render children without tooltip functionality
|
|
62
|
+
if (!isClient) {
|
|
63
|
+
return <>{props.children}</>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TooltipContent({
|
|
70
|
+
className,
|
|
71
|
+
sideOffset = 0,
|
|
72
|
+
children,
|
|
73
|
+
...props
|
|
74
|
+
}: ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
75
|
+
const [isClient, setIsClient] = useState(false);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setIsClient(true);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
// During SSR, don't render tooltip content
|
|
82
|
+
if (!isClient) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<TooltipPrimitive.Portal>
|
|
88
|
+
<TooltipPrimitive.Content
|
|
89
|
+
data-slot="tooltip-content"
|
|
90
|
+
sideOffset={sideOffset}
|
|
91
|
+
className={cn(
|
|
92
|
+
"bg-bg-secondary",
|
|
93
|
+
"text-text-primary",
|
|
94
|
+
"animate-in",
|
|
95
|
+
"text-uk-sm",
|
|
96
|
+
"leading-uk-sm",
|
|
97
|
+
"rounded-uk-md",
|
|
98
|
+
"px-lg",
|
|
99
|
+
"py-md",
|
|
100
|
+
"shadow-[0_8px_24px_0_var(--color-shadow-secondary)]",
|
|
101
|
+
"fade-in-0",
|
|
102
|
+
"zoom-in-95",
|
|
103
|
+
"data-[state=closed]:animate-out",
|
|
104
|
+
"data-[state=closed]:fade-out-0",
|
|
105
|
+
"data-[state=closed]:zoom-out-95",
|
|
106
|
+
"data-[side=bottom]:slide-in-from-top-2",
|
|
107
|
+
"data-[side=left]:slide-in-from-right-2",
|
|
108
|
+
"data-[side=right]:slide-in-from-left-2",
|
|
109
|
+
"data-[side=top]:slide-in-from-bottom-2",
|
|
110
|
+
"z-50",
|
|
111
|
+
"w-fit",
|
|
112
|
+
"origin-(--radix-tooltip-content-transform-origin)",
|
|
113
|
+
"text-balance",
|
|
114
|
+
"liquid:liquid-bg",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
<TooltipPrimitive.Arrow
|
|
121
|
+
className="fill-bg-secondary liquid:liquid-bg z-50 h-[9px] w-[20px] border-none"
|
|
122
|
+
style={{
|
|
123
|
+
clipPath: "polygon(50% 100%, 0% 0%, 100% 0%)",
|
|
124
|
+
borderRadius: "1px 1px 0 0",
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</TooltipPrimitive.Content>
|
|
128
|
+
</TooltipPrimitive.Portal>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { CaseSensitive, ChevronDown, Regex, Search } from "lucide-react";
|
|
2
|
+
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
5
|
+
import { cn } from "../utils/cn";
|
|
6
|
+
|
|
7
|
+
type TreeDataItem = {
|
|
8
|
+
id: string;
|
|
9
|
+
parentId: string | null;
|
|
10
|
+
label: string;
|
|
11
|
+
isCollapsible?: boolean;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type TreeViewProps<T extends TreeDataItem> = {
|
|
16
|
+
data: T[];
|
|
17
|
+
getIcon: (item: T) => ReactNode;
|
|
18
|
+
onVisibleDataChange?: (data: (T & { indent: number; isLast: boolean; ancestors: T[] })[]) => void;
|
|
19
|
+
onItemHover?: (id: string | null) => void;
|
|
20
|
+
className?: string;
|
|
21
|
+
hoveredId?: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function TreeView<T extends TreeDataItem>({
|
|
25
|
+
data,
|
|
26
|
+
getIcon,
|
|
27
|
+
onVisibleDataChange,
|
|
28
|
+
onItemHover,
|
|
29
|
+
className,
|
|
30
|
+
hoveredId,
|
|
31
|
+
}: TreeViewProps<T>) {
|
|
32
|
+
const [expandedItems, setExpandedItems] = useState(() => {
|
|
33
|
+
const initial = new Set<string>();
|
|
34
|
+
data.forEach((item) => {
|
|
35
|
+
if (item.isCollapsible) initial.add(item.id);
|
|
36
|
+
});
|
|
37
|
+
return initial;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
41
|
+
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
|
|
42
|
+
const [isRegex, setIsRegex] = useState(false);
|
|
43
|
+
const [isRegexValid, setIsRegexValid] = useState(true);
|
|
44
|
+
|
|
45
|
+
const toggleItem = (id: string) => {
|
|
46
|
+
setExpandedItems((prev) => {
|
|
47
|
+
const newSet = new Set(prev);
|
|
48
|
+
if (newSet.has(id)) {
|
|
49
|
+
newSet.delete(id);
|
|
50
|
+
} else {
|
|
51
|
+
newSet.add(id);
|
|
52
|
+
}
|
|
53
|
+
return newSet;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const childrenMap = useMemo(() => {
|
|
58
|
+
const map = new Map<string | null, T[]>();
|
|
59
|
+
data.forEach((item) => {
|
|
60
|
+
if (!map.has(item.parentId)) {
|
|
61
|
+
map.set(item.parentId, []);
|
|
62
|
+
}
|
|
63
|
+
map.get(item.parentId)!.push(item);
|
|
64
|
+
});
|
|
65
|
+
return map;
|
|
66
|
+
}, [data]);
|
|
67
|
+
|
|
68
|
+
const dataWithMeta = useMemo(() => {
|
|
69
|
+
const dataMap = new Map(data.map((item) => [item.id, item]));
|
|
70
|
+
|
|
71
|
+
const getAncestors = (item: T) => {
|
|
72
|
+
const ancestors: T[] = [];
|
|
73
|
+
let current = item.parentId;
|
|
74
|
+
while (current) {
|
|
75
|
+
const parent = dataMap.get(current);
|
|
76
|
+
if (parent) {
|
|
77
|
+
ancestors.unshift(parent);
|
|
78
|
+
current = parent.parentId;
|
|
79
|
+
} else {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ancestors;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return data.map((item) => {
|
|
87
|
+
const siblings = childrenMap.get(item.parentId) || [];
|
|
88
|
+
const isLast = siblings.length > 0 && siblings[siblings.length - 1].id === item.id;
|
|
89
|
+
const ancestors = getAncestors(item);
|
|
90
|
+
const indent = ancestors.length;
|
|
91
|
+
return { ...item, indent, isLast, ancestors };
|
|
92
|
+
});
|
|
93
|
+
}, [data, childrenMap]);
|
|
94
|
+
|
|
95
|
+
const visibleData = useMemo(() => {
|
|
96
|
+
const dataMap = new Map(data.map((item) => [item.id, item]));
|
|
97
|
+
|
|
98
|
+
if (!searchQuery) {
|
|
99
|
+
const isVisible = (item: { ancestors: T[] }) => {
|
|
100
|
+
return item.ancestors.every((ancestor) => expandedItems.has(ancestor.id));
|
|
101
|
+
};
|
|
102
|
+
return dataWithMeta.filter(isVisible);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const matchingAndAncestorIds = new Set<string>();
|
|
106
|
+
let regex: RegExp | null = null;
|
|
107
|
+
|
|
108
|
+
if (isRegex) {
|
|
109
|
+
try {
|
|
110
|
+
regex = new RegExp(searchQuery, isCaseSensitive ? "" : "i");
|
|
111
|
+
if (!isRegexValid) setIsRegexValid(true);
|
|
112
|
+
} catch {
|
|
113
|
+
if (isRegexValid) setIsRegexValid(false);
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
data.forEach((item) => {
|
|
119
|
+
let labelMatches = false;
|
|
120
|
+
if (regex) {
|
|
121
|
+
labelMatches = regex.test(item.label);
|
|
122
|
+
} else {
|
|
123
|
+
const source = isCaseSensitive ? item.label : item.label.toLowerCase();
|
|
124
|
+
const query = isCaseSensitive ? searchQuery : searchQuery.toLowerCase();
|
|
125
|
+
labelMatches = source.includes(query);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (labelMatches) {
|
|
129
|
+
matchingAndAncestorIds.add(item.id);
|
|
130
|
+
let current = item.parentId;
|
|
131
|
+
while (current) {
|
|
132
|
+
const parent = dataMap.get(current);
|
|
133
|
+
if (parent) {
|
|
134
|
+
matchingAndAncestorIds.add(parent.id);
|
|
135
|
+
current = parent.parentId;
|
|
136
|
+
} else {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return dataWithMeta.filter((item) => matchingAndAncestorIds.has(item.id));
|
|
144
|
+
}, [searchQuery, dataWithMeta, expandedItems, isCaseSensitive, isRegex, isRegexValid, data]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
onVisibleDataChange?.(visibleData);
|
|
148
|
+
}, [visibleData, onVisibleDataChange]);
|
|
149
|
+
|
|
150
|
+
const renderLabel = (label: string) => {
|
|
151
|
+
if (!searchQuery || !isRegexValid) {
|
|
152
|
+
return label;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let regex;
|
|
156
|
+
try {
|
|
157
|
+
regex = new RegExp(`(${searchQuery})`, isCaseSensitive ? "g" : "gi");
|
|
158
|
+
} catch {
|
|
159
|
+
return label;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const parts = label.split(regex);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
{parts.map((part, i) =>
|
|
167
|
+
i % 2 === 1 ? (
|
|
168
|
+
<span key={i} className="rounded-uk-xs bg-yellow-300 dark:bg-yellow-700">
|
|
169
|
+
{part}
|
|
170
|
+
</span>
|
|
171
|
+
) : (
|
|
172
|
+
part
|
|
173
|
+
),
|
|
174
|
+
)}
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div className={cn("border-line-secondary/20 flex flex-col", className)}>
|
|
181
|
+
<div className="border-line-secondary/20 flex h-[32px] shrink-0 items-center gap-1 border-b px-2">
|
|
182
|
+
<Search className="text-text-secondary size-4" />
|
|
183
|
+
<input
|
|
184
|
+
type="text"
|
|
185
|
+
placeholder="Search..."
|
|
186
|
+
className={cn(
|
|
187
|
+
"text-uk-md w-full bg-transparent focus:outline-none",
|
|
188
|
+
!isRegexValid && "text-red-500",
|
|
189
|
+
)}
|
|
190
|
+
value={searchQuery}
|
|
191
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
192
|
+
/>
|
|
193
|
+
<TooltipProvider delayDuration={200}>
|
|
194
|
+
<Tooltip>
|
|
195
|
+
<TooltipTrigger asChild>
|
|
196
|
+
<button
|
|
197
|
+
onClick={() => setIsCaseSensitive((prev) => !prev)}
|
|
198
|
+
className={cn("rounded-uk-sm p-1", isCaseSensitive && "bg-bg-tertiary")}
|
|
199
|
+
>
|
|
200
|
+
<CaseSensitive className="size-4" />
|
|
201
|
+
</button>
|
|
202
|
+
</TooltipTrigger>
|
|
203
|
+
<TooltipContent>
|
|
204
|
+
<p>Case Sensitive</p>
|
|
205
|
+
</TooltipContent>
|
|
206
|
+
</Tooltip>
|
|
207
|
+
<Tooltip>
|
|
208
|
+
<TooltipTrigger asChild>
|
|
209
|
+
<button
|
|
210
|
+
onClick={() => setIsRegex((prev) => !prev)}
|
|
211
|
+
className={cn("rounded-uk-sm p-1", isRegex && "bg-bg-tertiary")}
|
|
212
|
+
>
|
|
213
|
+
<Regex className="size-4" />
|
|
214
|
+
</button>
|
|
215
|
+
</TooltipTrigger>
|
|
216
|
+
<TooltipContent>
|
|
217
|
+
<p>Use Regular Expression</p>
|
|
218
|
+
</TooltipContent>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
</TooltipProvider>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="overflow-y-auto">
|
|
223
|
+
{visibleData.map((item) => (
|
|
224
|
+
<div
|
|
225
|
+
key={item.id}
|
|
226
|
+
className={cn(
|
|
227
|
+
"group relative flex h-[32px] cursor-pointer items-center",
|
|
228
|
+
hoveredId === item.id && "bg-bg-secondary/80",
|
|
229
|
+
)}
|
|
230
|
+
onMouseEnter={() => onItemHover?.(item.id)}
|
|
231
|
+
onMouseLeave={() => onItemHover?.(null)}
|
|
232
|
+
>
|
|
233
|
+
{/* Guide Lines */}
|
|
234
|
+
<div className="absolute top-0 left-[-0.28rem] z-0 flex h-full items-center">
|
|
235
|
+
{item.ancestors.map((ancestor, index) => {
|
|
236
|
+
const parentIsLast = dataWithMeta.find((d) => d.id === ancestor.id)?.isLast;
|
|
237
|
+
return (
|
|
238
|
+
<div
|
|
239
|
+
key={index}
|
|
240
|
+
className={cn(
|
|
241
|
+
"h-full w-[1.25rem]",
|
|
242
|
+
parentIsLast ? "" : "border-l",
|
|
243
|
+
"border-line-secondary/20",
|
|
244
|
+
)}
|
|
245
|
+
/>
|
|
246
|
+
);
|
|
247
|
+
})}
|
|
248
|
+
{item.indent > 0 && (
|
|
249
|
+
<div className="relative h-full w-[1.24rem]">
|
|
250
|
+
<div
|
|
251
|
+
className={cn(
|
|
252
|
+
"absolute top-0 left-0 h-1/2 w-1/2 border-b border-l",
|
|
253
|
+
item.isLast ? "rounded-bl-md" : "",
|
|
254
|
+
"border-line-secondary/20",
|
|
255
|
+
)}
|
|
256
|
+
/>
|
|
257
|
+
{!item.isLast && (
|
|
258
|
+
<div className="border-line-secondary/20 absolute top-1/2 left-0 h-1/2 w-1/2 border-l" />
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div
|
|
265
|
+
className="text-uk-md z-10 flex w-full items-center gap-2 px-2 whitespace-nowrap"
|
|
266
|
+
style={{ paddingLeft: `${item.indent * 1.25 + 0.5}rem` }}
|
|
267
|
+
>
|
|
268
|
+
<div className="relative flex size-4 items-center justify-center">
|
|
269
|
+
{item.isCollapsible && (
|
|
270
|
+
<button
|
|
271
|
+
onClick={() => toggleItem(item.id)}
|
|
272
|
+
className="absolute z-20 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100"
|
|
273
|
+
>
|
|
274
|
+
<ChevronDown
|
|
275
|
+
className={cn(
|
|
276
|
+
"size-4 transition-transform",
|
|
277
|
+
!expandedItems.has(item.id) && "-rotate-90",
|
|
278
|
+
)}
|
|
279
|
+
/>
|
|
280
|
+
</button>
|
|
281
|
+
)}
|
|
282
|
+
<div
|
|
283
|
+
className={cn(
|
|
284
|
+
"transition-opacity",
|
|
285
|
+
item.isCollapsible && "group-hover:opacity-0",
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
{getIcon(item)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<span className="truncate">{renderLabel(item.label)}</span>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
))}
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { TreeView as TreeViewV2, type TreeDataItem as TreeDataItemV2 };
|