@viliha/vui-ui 1.0.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.
@@ -0,0 +1,123 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { cn } from "./utils";
6
+ import { Checkbox } from "./checkbox";
7
+
8
+ interface DropdownProps {
9
+ label: string;
10
+ icon?: React.ReactNode;
11
+ align?: "start" | "end";
12
+ children: React.ReactNode;
13
+ /** Accessible name for icon-only triggers (when label is empty). */
14
+ ariaLabel?: string;
15
+ /** Render the trigger as a compact toolbar button (default) or a plain one. */
16
+ active?: boolean;
17
+ }
18
+
19
+ /** Minimal click-to-open menu with outside-click + Escape to close. */
20
+ export function Dropdown({
21
+ label,
22
+ icon,
23
+ align = "start",
24
+ children,
25
+ ariaLabel,
26
+ active,
27
+ }: DropdownProps) {
28
+ const [open, setOpen] = React.useState(false);
29
+ const ref = React.useRef<HTMLDivElement>(null);
30
+
31
+ React.useEffect(() => {
32
+ if (!open) return;
33
+ function onDocMouseDown(event: MouseEvent) {
34
+ if (ref.current && !ref.current.contains(event.target as Node)) {
35
+ setOpen(false);
36
+ }
37
+ }
38
+ function onKey(event: KeyboardEvent) {
39
+ if (event.key === "Escape") setOpen(false);
40
+ }
41
+ document.addEventListener("mousedown", onDocMouseDown);
42
+ document.addEventListener("keydown", onKey);
43
+ return () => {
44
+ document.removeEventListener("mousedown", onDocMouseDown);
45
+ document.removeEventListener("keydown", onKey);
46
+ };
47
+ }, [open]);
48
+
49
+ return (
50
+ <div className="relative" ref={ref}>
51
+ <button
52
+ type="button"
53
+ onClick={() => setOpen((value) => !value)}
54
+ aria-haspopup="menu"
55
+ aria-expanded={open}
56
+ aria-label={ariaLabel ?? label}
57
+ className={cn(
58
+ "inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md px-2 font-medium transition-colors",
59
+ active
60
+ ? "bg-accent text-accent-foreground"
61
+ : "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
62
+ )}
63
+ >
64
+ {icon}
65
+ {label}
66
+ </button>
67
+ {open && (
68
+ <div
69
+ className={cn(
70
+ "vui-pop-in absolute z-40 mt-1 min-w-52 overflow-hidden rounded-md border border-border bg-popover text-left text-sm font-normal text-popover-foreground shadow-md",
71
+ align === "end" ? "right-0" : "left-0",
72
+ )}
73
+ >
74
+ {children}
75
+ </div>
76
+ )}
77
+ </div>
78
+ );
79
+ }
80
+
81
+ interface DropdownItemProps {
82
+ children: React.ReactNode;
83
+ onSelect?: () => void;
84
+ checked?: boolean;
85
+ icon?: React.ReactNode;
86
+ }
87
+
88
+ export function DropdownItem({
89
+ children,
90
+ onSelect,
91
+ checked,
92
+ icon,
93
+ }: DropdownItemProps) {
94
+ return (
95
+ <button
96
+ type="button"
97
+ role="menuitemcheckbox"
98
+ aria-checked={checked}
99
+ onClick={onSelect}
100
+ className="flex w-full cursor-pointer items-center gap-2 border-b border-border px-3 py-2 text-left last:border-b-0 hover:bg-accent hover:text-accent-foreground"
101
+ >
102
+ <span className="flex-1 truncate">{children}</span>
103
+ {icon}
104
+ {checked !== undefined && (
105
+ <Checkbox
106
+ checked={checked}
107
+ readOnly
108
+ tabIndex={-1}
109
+ aria-hidden="true"
110
+ className="pointer-events-none"
111
+ />
112
+ )}
113
+ </button>
114
+ );
115
+ }
116
+
117
+ export function DropdownLabel({ children }: { children: React.ReactNode }) {
118
+ return (
119
+ <p className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
120
+ {children}
121
+ </p>
122
+ );
123
+ }
package/src/input.tsx ADDED
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "./utils";
4
+
5
+ export const Input = React.forwardRef<
6
+ HTMLInputElement,
7
+ React.ComponentProps<"input">
8
+ >(({ className, type, ...props }, ref) => (
9
+ <input
10
+ type={type}
11
+ ref={ref}
12
+ className={cn(
13
+ "flex h-8 w-full rounded-md border border-input bg-background px-2.5 py-1 transition-colors",
14
+ "placeholder:text-muted-foreground",
15
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
16
+ "disabled:cursor-not-allowed disabled:opacity-50",
17
+ "file:border-0 file:bg-transparent file:file:font-medium",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ ));
23
+ Input.displayName = "Input";
package/src/menu.tsx ADDED
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { cn } from "./utils";
6
+
7
+ /**
8
+ * Bordered list primitives — the theme standard for menus and record lists.
9
+ * Each row draws its own bottom divider (the last one is omitted), so any list
10
+ * built from these can't miss the border. Use `Menu` as the rounded, clipped
11
+ * container and `MenuItem` for each row; `MenuLabel` for section headers.
12
+ */
13
+
14
+ /** Shared row styling — exported for the rare case you must style a bespoke
15
+ * element (e.g. a framework `<Link>`) yet still match the standard. */
16
+ export const menuItemClass =
17
+ "flex w-full items-center gap-2.5 border-b border-border px-3 py-2 text-left transition-colors last:border-b-0 hover:bg-accent";
18
+
19
+ export function Menu({
20
+ className,
21
+ children,
22
+ ...props
23
+ }: React.HTMLAttributes<HTMLDivElement>) {
24
+ return (
25
+ <div
26
+ role="menu"
27
+ className={cn(
28
+ "overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground",
29
+ className,
30
+ )}
31
+ {...props}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export interface MenuItemProps
39
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
40
+ /** Render as a different element (e.g. a framework `<Link>`); default button. */
41
+ as?: React.ElementType;
42
+ href?: string;
43
+ }
44
+
45
+ /** A bordered list row. Renders a `<button>` by default; pass `as={Link}` +
46
+ * `href` to render a navigation link. Divider + hover come baked in. */
47
+ export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
48
+ function MenuItem({ as: Comp = "button", className, children, ...props }, ref) {
49
+ return (
50
+ <Comp
51
+ ref={ref}
52
+ role="menuitem"
53
+ className={cn(menuItemClass, className)}
54
+ {...(Comp === "button" ? { type: "button" } : {})}
55
+ {...props}
56
+ >
57
+ {children}
58
+ </Comp>
59
+ );
60
+ },
61
+ );
62
+
63
+ export function MenuLabel({
64
+ className,
65
+ children,
66
+ }: {
67
+ className?: string;
68
+ children: React.ReactNode;
69
+ }) {
70
+ return (
71
+ <p
72
+ className={cn(
73
+ "border-b border-border px-3 py-2 font-medium text-muted-foreground",
74
+ className,
75
+ )}
76
+ >
77
+ {children}
78
+ </p>
79
+ );
80
+ }