@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useGesture } from "@use-gesture/react";
|
|
2
|
+
import { ChangeEvent, forwardRef, ReactNode, useCallback, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
import { InputRoot, InputRootProps, InputSlot } from "./input";
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
|
|
7
|
+
const getHoverElementAndIndex = (
|
|
8
|
+
element: Element | null,
|
|
9
|
+
inputs: Map<string, HTMLInputElement | null>,
|
|
10
|
+
) => {
|
|
11
|
+
if (!element) return null;
|
|
12
|
+
const container = element?.closest("[data-input]");
|
|
13
|
+
|
|
14
|
+
const currentInput = container?.querySelector("input");
|
|
15
|
+
|
|
16
|
+
if (!currentInput) return null;
|
|
17
|
+
|
|
18
|
+
return Array.from(inputs.values()).indexOf(currentInput);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// down y x none
|
|
22
|
+
const changeElementsHoverState = (
|
|
23
|
+
inputs: Map<string, HTMLInputElement | null>,
|
|
24
|
+
range: [number | null, number | null],
|
|
25
|
+
changed: "x" | "y" | "none",
|
|
26
|
+
) => {
|
|
27
|
+
const [first, last] = range;
|
|
28
|
+
Array.from(inputs.values()).forEach((input, i) => {
|
|
29
|
+
const container = input?.closest("[data-input]");
|
|
30
|
+
if (first === null || last === null) {
|
|
31
|
+
container?.setAttribute("data-hover", "none");
|
|
32
|
+
} else if (i >= Math.min(first, last) && i <= Math.max(first, last)) {
|
|
33
|
+
container?.setAttribute("data-hover", changed !== "none" ? changed : "down");
|
|
34
|
+
} else {
|
|
35
|
+
container?.setAttribute("data-hover", "none");
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface InputNumbersProps extends Pick<InputRootProps, "size"> {
|
|
41
|
+
step?: number;
|
|
42
|
+
value: number[];
|
|
43
|
+
prefix?: ReactNode[];
|
|
44
|
+
onValuesChange?: (value: Array<number>) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const InputNumbers = forwardRef<HTMLDivElement, InputNumbersProps>(function InputNumbers(
|
|
48
|
+
{ size, value, onValuesChange, prefix, step = 0.1 },
|
|
49
|
+
ref,
|
|
50
|
+
) {
|
|
51
|
+
const inputs = useRef<Map<string, HTMLInputElement | null>>(new Map());
|
|
52
|
+
const selectRange = useRef<[number | null, number | null]>([null, null]);
|
|
53
|
+
const inputChange = useRef(false);
|
|
54
|
+
|
|
55
|
+
const stopClick = useCallback(() => {
|
|
56
|
+
if (typeof document === "undefined") return;
|
|
57
|
+
|
|
58
|
+
const stopClick = (e: MouseEvent) => {
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
document.removeEventListener("click", stopClick, true);
|
|
62
|
+
};
|
|
63
|
+
document.addEventListener("click", stopClick, true);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
const bind = useGesture({
|
|
67
|
+
onDrag: ({ delta: [dx], event, initial, distance: [, oy], xy, memo = 0 }) => {
|
|
68
|
+
event.stopPropagation();
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
|
|
71
|
+
if (selectRange.current[0] === null) {
|
|
72
|
+
selectRange.current[0] = getHoverElementAndIndex(
|
|
73
|
+
typeof document !== "undefined" ? document.elementFromPoint(...initial) : null,
|
|
74
|
+
inputs.current,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let newMX: number = memo + dx / 2;
|
|
79
|
+
|
|
80
|
+
if (Math.abs(newMX) >= (inputChange.current ? 1 : 5)) {
|
|
81
|
+
const multiplyStep = event.shiftKey ? 5 : event.altKey ? 1 / 5 : 1;
|
|
82
|
+
|
|
83
|
+
const newValue = value.map((v, i) => {
|
|
84
|
+
const [first, last] = selectRange.current;
|
|
85
|
+
if (first === null || last === null) {
|
|
86
|
+
return v;
|
|
87
|
+
} else if (i <= Math.max(first, last) && i >= Math.min(first, last)) {
|
|
88
|
+
return Number(((v || 0) + Math.floor(newMX) * step * multiplyStep).toPrecision(6));
|
|
89
|
+
} else {
|
|
90
|
+
return v;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
onValuesChange?.(newValue);
|
|
94
|
+
newMX = 0;
|
|
95
|
+
inputChange.current = true;
|
|
96
|
+
|
|
97
|
+
changeElementsHoverState(inputs.current, selectRange.current, "x");
|
|
98
|
+
|
|
99
|
+
stopClick();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!inputChange.current) {
|
|
103
|
+
const currentElement =
|
|
104
|
+
typeof document !== "undefined" ? document.elementFromPoint(...xy) : null;
|
|
105
|
+
|
|
106
|
+
const currentIndex = getHoverElementAndIndex(currentElement, inputs.current);
|
|
107
|
+
selectRange.current[1] = currentIndex ?? selectRange.current[1];
|
|
108
|
+
|
|
109
|
+
changeElementsHoverState(inputs.current, selectRange.current, oy !== 0 ? "y" : "none");
|
|
110
|
+
|
|
111
|
+
stopClick();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return newMX;
|
|
115
|
+
},
|
|
116
|
+
onDragEnd: () => {
|
|
117
|
+
selectRange.current = [null, null];
|
|
118
|
+
inputChange.current = false;
|
|
119
|
+
changeElementsHoverState(inputs.current, selectRange.current, "none");
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const valueChangeHandler = useCallback(
|
|
124
|
+
(index: number) => {
|
|
125
|
+
return (e: ChangeEvent<HTMLInputElement>) => {
|
|
126
|
+
const inputValue = e.target.value;
|
|
127
|
+
if (inputValue === "" || !isNaN(Number(inputValue))) {
|
|
128
|
+
const newValue = value.map((v, i) => {
|
|
129
|
+
if (inputValue === "") {
|
|
130
|
+
return "" as unknown as number;
|
|
131
|
+
}
|
|
132
|
+
return i === index ? Number(inputValue) : v;
|
|
133
|
+
});
|
|
134
|
+
onValuesChange?.(newValue);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
[onValuesChange, value],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (value?.length === 0) {
|
|
142
|
+
throw new Error("`value` cannot be an empty array");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
ref={ref}
|
|
148
|
+
{...bind()}
|
|
149
|
+
className={cn("gap-xs flex cursor-crosshair touch-none flex-col select-none")}
|
|
150
|
+
>
|
|
151
|
+
{value.map((item, index) => (
|
|
152
|
+
<InputRoot
|
|
153
|
+
key={index}
|
|
154
|
+
ref={(r) => {
|
|
155
|
+
inputs.current.set(index.toString(), r);
|
|
156
|
+
return () => {
|
|
157
|
+
inputs.current.delete(index.toString());
|
|
158
|
+
};
|
|
159
|
+
}}
|
|
160
|
+
size={size}
|
|
161
|
+
value={item}
|
|
162
|
+
onClick={() => {
|
|
163
|
+
inputs.current.get(index.toString())?.focus();
|
|
164
|
+
}}
|
|
165
|
+
onChange={valueChangeHandler(index)}
|
|
166
|
+
data-hover="none"
|
|
167
|
+
className={cn([
|
|
168
|
+
"group/number-input",
|
|
169
|
+
"cursor-crosshair",
|
|
170
|
+
"data-[hover=down]:bg-bg-tertiary",
|
|
171
|
+
"data-[hover=x]:bg-bg-tertiary",
|
|
172
|
+
"data-[hover=y]:bg-bg-tertiary",
|
|
173
|
+
"data-[hover=down]:cursor-move",
|
|
174
|
+
"data-[hover=x]:cursor-col-resize",
|
|
175
|
+
"data-[hover=y]:cursor-ns-resize",
|
|
176
|
+
])}
|
|
177
|
+
inputClassName={cn([
|
|
178
|
+
"cursor-crosshair",
|
|
179
|
+
"group-data-[hover=down]/number-input:cursor-move",
|
|
180
|
+
"group-data-[hover=x]/number-input:cursor-col-resize",
|
|
181
|
+
"group-data-[hover=y]/number-input:cursor-ns-resize",
|
|
182
|
+
])}
|
|
183
|
+
>
|
|
184
|
+
<InputSlot
|
|
185
|
+
className={cn(
|
|
186
|
+
"cursor-crosshair",
|
|
187
|
+
"group-data-[hover=down]/number-input:cursor-move",
|
|
188
|
+
"group-data-[hover=x]/number-input:cursor-col-resize",
|
|
189
|
+
"group-data-[hover=y]/number-input:cursor-ns-resize",
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
{prefix?.[index]}
|
|
193
|
+
</InputSlot>
|
|
194
|
+
</InputRoot>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
export { InputNumbers };
|
|
201
|
+
export type { InputNumbersProps };
|
package/src/ui/input.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { type ComponentProps, forwardRef, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
|
|
7
|
+
const inputRootVariants = cva(
|
|
8
|
+
[
|
|
9
|
+
"px-1.5",
|
|
10
|
+
"flex",
|
|
11
|
+
"gap-md",
|
|
12
|
+
"items-center",
|
|
13
|
+
"rounded-uk-sm",
|
|
14
|
+
"overflow-hidden",
|
|
15
|
+
"bg-bg-secondary",
|
|
16
|
+
],
|
|
17
|
+
{
|
|
18
|
+
variants: {
|
|
19
|
+
state: {
|
|
20
|
+
default: ["hover:bg-bg-tertiary", "has-[input:focus]:bg-bg-quaternary"],
|
|
21
|
+
disabled: ["disabled:cursor-not-allowed", "bg-bg-tertiary", "text-text-tertiary"],
|
|
22
|
+
error: ["hover:bg-danger-primary/10", "bg-danger-primary/20"],
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
sm: ["h-5.5", "text-uk-sm", "[&_.input-slot>svg:not([class*='size-'])]:size-lg"],
|
|
26
|
+
base: ["h-7", "text-uk-md", "[&_.input-slot>svg:not([class*='size-'])]:size-[14px]"],
|
|
27
|
+
lg: ["h-8", "text-uk-lg", "[&_.input-slot>svg:not([class*='size-'])]:size-xl"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
state: "default",
|
|
32
|
+
size: "base",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
interface InputRootProps
|
|
38
|
+
extends Omit<ComponentProps<"input">, "size">,
|
|
39
|
+
VariantProps<typeof inputRootVariants> {
|
|
40
|
+
state?: "default" | "error";
|
|
41
|
+
side?: "left" | "right";
|
|
42
|
+
asChild?: boolean;
|
|
43
|
+
inputClassName?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const InputRoot = forwardRef<HTMLInputElement, InputRootProps>(function InputRoot(
|
|
47
|
+
{ style, className, side, children, disabled, state, size, type, inputClassName, ...props },
|
|
48
|
+
ref,
|
|
49
|
+
) {
|
|
50
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
style={style}
|
|
55
|
+
data-input
|
|
56
|
+
className={cn(inputRootVariants({ size, state: disabled ? "disabled" : state }), className)}
|
|
57
|
+
onClick={(event) => {
|
|
58
|
+
const target = event.target as HTMLElement;
|
|
59
|
+
if (target.closest("input, button, a")) return;
|
|
60
|
+
|
|
61
|
+
const input = inputRef.current;
|
|
62
|
+
if (!input) return;
|
|
63
|
+
|
|
64
|
+
// Same selector as in the CSS to find the right slot
|
|
65
|
+
const isRightSlot = target.closest(`
|
|
66
|
+
.input-slot[data-side='right'],
|
|
67
|
+
.input-slot:not([data-side='right']) ~ .input-slot:not([data-side='left'])
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
const cursorPosition = isRightSlot ? input.value.length : 0;
|
|
71
|
+
|
|
72
|
+
requestAnimationFrame(() => {
|
|
73
|
+
// Only some input types support this, browsers will throw an error if not supported
|
|
74
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange#:~:text=Note%20that%20according,not%20support%20selection%22.
|
|
75
|
+
try {
|
|
76
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
77
|
+
} catch {
|
|
78
|
+
/* empty */
|
|
79
|
+
}
|
|
80
|
+
input.focus();
|
|
81
|
+
});
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<input
|
|
85
|
+
spellCheck="false"
|
|
86
|
+
{...props}
|
|
87
|
+
type={type}
|
|
88
|
+
data-side={side}
|
|
89
|
+
disabled={disabled}
|
|
90
|
+
name={type ?? "text"}
|
|
91
|
+
ref={composeRefs(inputRef, ref)}
|
|
92
|
+
className={cn(
|
|
93
|
+
[
|
|
94
|
+
"w-full",
|
|
95
|
+
"h-full",
|
|
96
|
+
"outline-none",
|
|
97
|
+
"data-[side=right]:text-right",
|
|
98
|
+
"[input[type=number]]:font-number-input",
|
|
99
|
+
"[&::-webkit-inner-spin-button]:appearance-none",
|
|
100
|
+
"[&::-webkit-outer-spin-button]:appearance-none",
|
|
101
|
+
],
|
|
102
|
+
inputClassName,
|
|
103
|
+
)}
|
|
104
|
+
/>
|
|
105
|
+
{children}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
interface InputSlotProps extends ComponentProps<"div"> {
|
|
111
|
+
side?: "left" | "right";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const InputSlot = forwardRef<HTMLDivElement, InputSlotProps>(function (
|
|
115
|
+
{ className, children, side = "left", ...props },
|
|
116
|
+
ref,
|
|
117
|
+
) {
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
data-side={side}
|
|
121
|
+
{...props}
|
|
122
|
+
ref={ref}
|
|
123
|
+
className={cn(
|
|
124
|
+
[
|
|
125
|
+
"input-slot",
|
|
126
|
+
"cursor-text",
|
|
127
|
+
"shrink-0",
|
|
128
|
+
"order-[0]",
|
|
129
|
+
"text-text-tertiary",
|
|
130
|
+
"data-[side=left]:-order-1",
|
|
131
|
+
],
|
|
132
|
+
className,
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
{children}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export { InputRoot, InputSlot };
|
|
141
|
+
export type { InputRootProps, InputSlotProps };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { PropsWithChildren } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
interface Props extends PropsWithChildren {
|
|
7
|
+
align?: "start" | "center" | "end";
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
orientation?: "horizontal" | "vertical";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Layout({
|
|
14
|
+
asChild,
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
align = "start",
|
|
18
|
+
orientation = "horizontal",
|
|
19
|
+
}: Props) {
|
|
20
|
+
const Com = asChild ? Slot : "div";
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Com
|
|
24
|
+
className={cn(
|
|
25
|
+
{
|
|
26
|
+
"gap-xs flex flex-col": orientation === "horizontal",
|
|
27
|
+
"gap-xs grid grid-cols-[1fr_2fr] text-center": orientation === "vertical",
|
|
28
|
+
"items-baseline": align === "start",
|
|
29
|
+
"items-center": align === "center",
|
|
30
|
+
"items-end": align === "end",
|
|
31
|
+
},
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</Com>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Layout };
|
|
41
|
+
export type { Props as LayoutProps };
|
package/src/ui/modal.tsx
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { XIcon } from "lucide-react";
|
|
4
|
+
import { type ComponentProps } from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../utils";
|
|
7
|
+
|
|
8
|
+
type ModalProps = ComponentProps<typeof DialogPrimitive.Root>;
|
|
9
|
+
|
|
10
|
+
function Modal({ ...props }: ModalProps) {
|
|
11
|
+
return <DialogPrimitive.Root data-slot="modal" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ModalTriggerProps = ComponentProps<typeof DialogPrimitive.Trigger>;
|
|
15
|
+
|
|
16
|
+
function ModalTrigger({ ...props }: ModalTriggerProps) {
|
|
17
|
+
return <DialogPrimitive.Trigger data-slot="modal-trigger" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ModalPortalProps = ComponentProps<typeof DialogPrimitive.Portal>;
|
|
21
|
+
|
|
22
|
+
function ModalPortal({ ...props }: ModalPortalProps) {
|
|
23
|
+
return <DialogPrimitive.Portal data-slot="modal-portal" {...props} />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type ModalCloseProps = ComponentProps<typeof DialogPrimitive.Close>;
|
|
27
|
+
|
|
28
|
+
function ModalClose({ ...props }: ModalCloseProps) {
|
|
29
|
+
return <DialogPrimitive.Close data-slot="modal-close" {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const modalOverlayVariants = cva([
|
|
33
|
+
"fixed",
|
|
34
|
+
"inset-0",
|
|
35
|
+
"z-100",
|
|
36
|
+
"bg-bg-mask",
|
|
37
|
+
"transition-all",
|
|
38
|
+
"duration-200",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
type ModalOverlayProps = ComponentProps<typeof DialogPrimitive.Overlay>;
|
|
42
|
+
|
|
43
|
+
function ModalOverlay({ className, ...props }: ModalOverlayProps) {
|
|
44
|
+
return (
|
|
45
|
+
<DialogPrimitive.Overlay
|
|
46
|
+
data-slot="modal-overlay"
|
|
47
|
+
className={cn(modalOverlayVariants(), className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const modalContentVariants = cva([
|
|
54
|
+
"pb-xl",
|
|
55
|
+
"px-xl",
|
|
56
|
+
"bg-bg-primary",
|
|
57
|
+
"rounded-uk-xl",
|
|
58
|
+
"shadow-[0_4px_16px_0_var(--color-shadow-secondary)]",
|
|
59
|
+
"min-w-[280px]",
|
|
60
|
+
"duration-200",
|
|
61
|
+
"transition-all",
|
|
62
|
+
"fixed",
|
|
63
|
+
"translate-x-[-50%]",
|
|
64
|
+
"translate-y-[-50%]",
|
|
65
|
+
"z-101",
|
|
66
|
+
"left-1/2",
|
|
67
|
+
"top-1/2",
|
|
68
|
+
"data-[state=open]:animate-in",
|
|
69
|
+
"data-[state=closed]:animate-out",
|
|
70
|
+
"data-[state=closed]:fade-out-0",
|
|
71
|
+
"data-[state=open]:fade-in-0",
|
|
72
|
+
"data-[state=closed]:zoom-out-95",
|
|
73
|
+
"data-[state=open]:zoom-in-95",
|
|
74
|
+
"liquid:liquid-bg",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
interface ModalContentProps extends ComponentProps<typeof DialogPrimitive.Content> {
|
|
78
|
+
showCloseButton?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ModalContent({
|
|
82
|
+
className,
|
|
83
|
+
children,
|
|
84
|
+
showCloseButton = true,
|
|
85
|
+
...props
|
|
86
|
+
}: ModalContentProps) {
|
|
87
|
+
return (
|
|
88
|
+
<ModalPortal>
|
|
89
|
+
<ModalOverlay />
|
|
90
|
+
<DialogPrimitive.Content
|
|
91
|
+
data-slot="modal-content"
|
|
92
|
+
className={cn(modalContentVariants(), className)}
|
|
93
|
+
{...props}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
{showCloseButton && (
|
|
97
|
+
<DialogPrimitive.Close
|
|
98
|
+
data-slot="modal-close"
|
|
99
|
+
className={cn(
|
|
100
|
+
"absolute",
|
|
101
|
+
"right-[16px]",
|
|
102
|
+
"top-[16px]",
|
|
103
|
+
"w-[16px]",
|
|
104
|
+
"h-[16px]",
|
|
105
|
+
"rounded-uk-sm",
|
|
106
|
+
"cursor-pointer",
|
|
107
|
+
"hover:text-icon-highlight",
|
|
108
|
+
"text-icon-primary",
|
|
109
|
+
"flex",
|
|
110
|
+
"items-center",
|
|
111
|
+
"justify-center",
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
<XIcon />
|
|
115
|
+
<span className="sr-only">Close</span>
|
|
116
|
+
</DialogPrimitive.Close>
|
|
117
|
+
)}
|
|
118
|
+
</DialogPrimitive.Content>
|
|
119
|
+
</ModalPortal>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type ModalHeaderProps = ComponentProps<"div">;
|
|
124
|
+
|
|
125
|
+
function ModalHeader({ className, ...props }: ModalHeaderProps) {
|
|
126
|
+
return <div data-slot="modal-header" className={cn("flex flex-col", className)} {...props} />;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type ModalTitleProps = ComponentProps<typeof DialogPrimitive.Title>;
|
|
130
|
+
|
|
131
|
+
function ModalTitle({ className, ...props }: ModalTitleProps) {
|
|
132
|
+
return (
|
|
133
|
+
<DialogPrimitive.Title
|
|
134
|
+
data-slot="modal-title"
|
|
135
|
+
className={cn(
|
|
136
|
+
"text-text-highlight",
|
|
137
|
+
"text-uk-lg",
|
|
138
|
+
"leading-uk-lg",
|
|
139
|
+
"font-medium",
|
|
140
|
+
"py-lg",
|
|
141
|
+
"px-xl",
|
|
142
|
+
"text-center",
|
|
143
|
+
className,
|
|
144
|
+
)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type ModalDescriptionProps = ComponentProps<typeof DialogPrimitive.Description>;
|
|
151
|
+
|
|
152
|
+
function ModalDescription({ className, ...props }: ModalDescriptionProps) {
|
|
153
|
+
return (
|
|
154
|
+
<DialogPrimitive.Description
|
|
155
|
+
data-slot="modal-description"
|
|
156
|
+
className={cn("text-text-primary", "text-uk-md", "leading-uk-md", "text-start", className)}
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type ModalFooterProps = ComponentProps<"div">;
|
|
163
|
+
|
|
164
|
+
function ModalFooter({ className, ...props }: ModalFooterProps) {
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
data-slot="modal-footer"
|
|
168
|
+
className={cn("w-full", "flex", "justify-end", "gap-xs", className)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
Modal,
|
|
176
|
+
ModalTrigger,
|
|
177
|
+
ModalPortal,
|
|
178
|
+
ModalOverlay,
|
|
179
|
+
ModalContent,
|
|
180
|
+
ModalHeader,
|
|
181
|
+
ModalTitle,
|
|
182
|
+
ModalDescription,
|
|
183
|
+
ModalFooter,
|
|
184
|
+
ModalClose,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export type {
|
|
188
|
+
ModalProps,
|
|
189
|
+
ModalTriggerProps,
|
|
190
|
+
ModalPortalProps,
|
|
191
|
+
ModalOverlayProps,
|
|
192
|
+
ModalContentProps,
|
|
193
|
+
ModalHeaderProps,
|
|
194
|
+
ModalTitleProps,
|
|
195
|
+
ModalDescriptionProps,
|
|
196
|
+
ModalFooterProps,
|
|
197
|
+
ModalCloseProps,
|
|
198
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
function Popover({ ...props }: ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
7
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function PopoverTrigger({ ...props }: ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
11
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function PopoverContent({
|
|
15
|
+
className,
|
|
16
|
+
align = "center",
|
|
17
|
+
sideOffset = 4,
|
|
18
|
+
...props
|
|
19
|
+
}: ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
20
|
+
return (
|
|
21
|
+
<PopoverPrimitive.Portal>
|
|
22
|
+
<PopoverPrimitive.Content
|
|
23
|
+
data-slot="popover-content"
|
|
24
|
+
align={align}
|
|
25
|
+
sideOffset={sideOffset}
|
|
26
|
+
className={cn(
|
|
27
|
+
"bg-bg-primary",
|
|
28
|
+
"rounded-uk-lg",
|
|
29
|
+
"p-xl",
|
|
30
|
+
"w-[320px]",
|
|
31
|
+
"shadow-[0_4px_16px_0_var(--color-shadow-secondary)]",
|
|
32
|
+
"data-[state=open]:animate-in",
|
|
33
|
+
"data-[state=closed]:animate-out",
|
|
34
|
+
"data-[state=closed]:fade-out-0",
|
|
35
|
+
"data-[state=open]:fade-in-0",
|
|
36
|
+
"data-[state=closed]:zoom-out-95",
|
|
37
|
+
"data-[state=open]:zoom-in-95",
|
|
38
|
+
"data-[side=bottom]:slide-in-from-top-2",
|
|
39
|
+
"data-[side=left]:slide-in-from-right-2",
|
|
40
|
+
"data-[side=right]:slide-in-from-left-2",
|
|
41
|
+
"data-[side=top]:slide-in-from-bottom-2",
|
|
42
|
+
"z-50",
|
|
43
|
+
"origin-(--radix-popover-content-transform-origin)",
|
|
44
|
+
"border-none",
|
|
45
|
+
"outline-hidden",
|
|
46
|
+
"liquid:liquid-bg",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
</PopoverPrimitive.Portal>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function PopoverAnchor({ ...props }: ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
56
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
2
|
+
import { type ComponentProps } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
|
|
6
|
+
function RadioGroup({ className, ...props }: ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
|
7
|
+
return (
|
|
8
|
+
<RadioGroupPrimitive.Root
|
|
9
|
+
data-slot="radio-group"
|
|
10
|
+
className={cn("grid gap-2", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function RadioGroupItem({ className, ...props }: ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
|
17
|
+
return (
|
|
18
|
+
<RadioGroupPrimitive.Item
|
|
19
|
+
data-slot="radio-group-item"
|
|
20
|
+
className={cn(
|
|
21
|
+
"group",
|
|
22
|
+
"w-[16px]",
|
|
23
|
+
"h-[16px]",
|
|
24
|
+
"rounded-full",
|
|
25
|
+
"border-[1.5px]",
|
|
26
|
+
"border-solid",
|
|
27
|
+
"border-bg-tertiary",
|
|
28
|
+
"data-[state=checked]:border-brand-primary",
|
|
29
|
+
"disabled:cursor-not-allowed",
|
|
30
|
+
"disabled:bg-bg-secondary",
|
|
31
|
+
"disabled:data-[state=checked]:border-bg-tertiary",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<RadioGroupPrimitive.Indicator
|
|
37
|
+
data-slot="radio-group-indicator"
|
|
38
|
+
className="relative flex items-center justify-center"
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
className={cn(
|
|
42
|
+
"absolute",
|
|
43
|
+
"inset-0",
|
|
44
|
+
"m-auto",
|
|
45
|
+
"size-[8px]",
|
|
46
|
+
"rounded-full",
|
|
47
|
+
"bg-brand-primary",
|
|
48
|
+
"group-disabled:bg-bg-tertiary",
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
</RadioGroupPrimitive.Indicator>
|
|
52
|
+
</RadioGroupPrimitive.Item>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { RadioGroup, RadioGroupItem };
|