@hilum/ui 3.4.0 → 3.5.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.
Files changed (65) hide show
  1. package/README.md +4 -0
  2. package/dist/{chunk-MAHPAP65.js → chunk-RI6QJR2L.js} +6 -3
  3. package/dist/chunk-RI6QJR2L.js.map +1 -0
  4. package/dist/index.d.ts +789 -115
  5. package/dist/index.js +10404 -1099
  6. package/dist/index.js.map +1 -1
  7. package/dist/tokens.css +17 -0
  8. package/dist/tokens.d.ts +3 -0
  9. package/dist/tokens.js +1 -1
  10. package/llms.txt +18 -4
  11. package/package.json +19 -1
  12. package/src/components/accordion.tsx +746 -58
  13. package/src/components/alert-dialog.tsx +11 -5
  14. package/src/components/ask-user-questions.tsx +1366 -0
  15. package/src/components/badge.tsx +105 -24
  16. package/src/components/button.tsx +207 -38
  17. package/src/components/card.tsx +3 -2
  18. package/src/components/chat-message.tsx +116 -0
  19. package/src/components/checkbox-group.tsx +403 -0
  20. package/src/components/checkbox.tsx +6 -3
  21. package/src/components/color-input.tsx +3 -1
  22. package/src/components/color-picker.tsx +1992 -80
  23. package/src/components/dialog.tsx +141 -81
  24. package/src/components/dropdown-menu.tsx +21 -13
  25. package/src/components/dropdown.tsx +245 -0
  26. package/src/components/file-thumbnail.tsx +156 -0
  27. package/src/components/hover-card.tsx +2 -0
  28. package/src/components/input-copy.tsx +307 -0
  29. package/src/components/input-group.tsx +303 -108
  30. package/src/components/input-message.tsx +883 -0
  31. package/src/components/input.tsx +6 -3
  32. package/src/components/menu-item.tsx +156 -0
  33. package/src/components/mobile-drawer.tsx +50 -0
  34. package/src/components/nav-item.tsx +43 -0
  35. package/src/components/nav-menu.tsx +47 -0
  36. package/src/components/popover.tsx +3 -1
  37. package/src/components/radio-group.tsx +381 -35
  38. package/src/components/scroll-area.tsx +9 -2
  39. package/src/components/select.tsx +708 -121
  40. package/src/components/sheet.tsx +15 -6
  41. package/src/components/slider.tsx +1583 -20
  42. package/src/components/status-badge.tsx +2 -0
  43. package/src/components/switch.tsx +245 -28
  44. package/src/components/table.tsx +191 -77
  45. package/src/components/tabs-subtle.tsx +411 -0
  46. package/src/components/tabs.tsx +459 -49
  47. package/src/components/textarea.tsx +7 -3
  48. package/src/components/thinking-indicator.tsx +106 -0
  49. package/src/components/thinking-steps.tsx +382 -0
  50. package/src/components/tooltip.tsx +158 -33
  51. package/src/hooks/use-merge-split.tsx +359 -0
  52. package/src/hooks/use-proximity-hover.ts +208 -0
  53. package/src/index.ts +14 -0
  54. package/src/lib/elevated.tsx +52 -0
  55. package/src/lib/font-weight.ts +18 -0
  56. package/src/lib/icon-context.tsx +98 -0
  57. package/src/lib/icon-map.tsx +669 -0
  58. package/src/lib/interaction.ts +102 -0
  59. package/src/lib/scroll-fade.tsx +241 -0
  60. package/src/lib/shape-context.tsx +121 -0
  61. package/src/lib/springs.ts +30 -0
  62. package/src/lib/surface-classes.ts +43 -0
  63. package/src/lib/surface-context.tsx +17 -0
  64. package/src/tokens/tokens.ts +3 -0
  65. package/dist/chunk-MAHPAP65.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { cn } from "../lib/utils";
3
+ import { controlSurfaceClasses, focusRingClasses, motionClasses } from "../lib/interaction";
3
4
 
4
5
  function Input({ className, type, ...props }: React.ComponentProps<"input">) {
5
6
  return (
@@ -7,10 +8,12 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
7
8
  type={type}
8
9
  data-slot="input"
9
10
  className={cn(
10
- "flex h-10 w-full rounded-md border border-border bg-card px-3 py-1",
11
+ "flex h-10 w-full rounded-md px-3 py-1",
11
12
  "body text-foreground placeholder:text-muted-foreground",
12
- "transition-colors duration-150",
13
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/20 focus-visible:border-brand-primary",
13
+ controlSurfaceClasses,
14
+ motionClasses,
15
+ focusRingClasses,
16
+ "focus-visible:border-brand-primary",
14
17
  "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted",
15
18
  "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
16
19
  className,
@@ -0,0 +1,156 @@
1
+ "use client";
2
+
3
+ import {
4
+ useRef,
5
+ useEffect,
6
+ forwardRef,
7
+ isValidElement,
8
+ type HTMLAttributes,
9
+ type ReactNode,
10
+ } from "react";
11
+ import type { IconComponent } from "../lib/icon-context";
12
+ import { motion, AnimatePresence } from "framer-motion";
13
+ import { useDropdown } from "./dropdown";
14
+ import { cn } from "../lib/utils";
15
+ import { fontWeights } from "../lib/font-weight";
16
+ import { shapeMap } from "../lib/shape-context";
17
+
18
+ // MenuItem is only used inside Dropdown, which opts out of the global pill
19
+ // shape — see dropdown.tsx for the rationale.
20
+ const shape = shapeMap.rounded;
21
+
22
+ interface MenuItemProps extends HTMLAttributes<HTMLDivElement> {
23
+ /** Optional leading icon. When omitted, the row renders text-only with no
24
+ * reserved icon column. */
25
+ icon?: IconComponent | ReactNode;
26
+ label?: string;
27
+ index?: number;
28
+ checked?: boolean;
29
+ onSelect?: () => void;
30
+ trailing?: ReactNode;
31
+ }
32
+
33
+ const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(
34
+ (
35
+ { icon: Icon, label, index = 0, checked, onSelect, trailing, className, children, ...props },
36
+ ref,
37
+ ) => {
38
+ const internalRef = useRef<HTMLDivElement>(null);
39
+ const hasMounted = useRef(false);
40
+ const { registerItem, activeIndex, checkedIndex } = useDropdown();
41
+
42
+ useEffect(() => {
43
+ registerItem(index, internalRef.current);
44
+ return () => registerItem(index, null);
45
+ }, [index, registerItem]);
46
+
47
+ useEffect(() => {
48
+ hasMounted.current = true;
49
+ }, []);
50
+
51
+ const isActive = activeIndex === index;
52
+ const skipAnimation = !hasMounted.current;
53
+
54
+ return (
55
+ <div
56
+ ref={(node) => {
57
+ (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
58
+ if (typeof ref === "function") ref(node);
59
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
60
+ }}
61
+ data-proximity-index={index}
62
+ tabIndex={index === (checkedIndex ?? 0) ? 0 : -1}
63
+ role="menuitemradio"
64
+ aria-checked={!!checked}
65
+ aria-label={label ?? (typeof children === "string" ? children : undefined)}
66
+ onClick={onSelect}
67
+ onKeyDown={(e) => {
68
+ if (e.key === " " || e.key === "Enter") {
69
+ e.preventDefault();
70
+ onSelect?.();
71
+ }
72
+ }}
73
+ className={cn(
74
+ `relative z-10 flex items-center gap-2 ${shape.item} px-2 py-2 cursor-pointer outline-none`,
75
+ className,
76
+ )}
77
+ {...props}
78
+ >
79
+ {Icon && typeof Icon === "function" && (
80
+ <span className="inline-grid">
81
+ <span className="col-start-1 row-start-1 invisible">
82
+ <Icon size={16} strokeWidth={2} />
83
+ </span>
84
+ <Icon
85
+ size={16}
86
+ strokeWidth={isActive || checked ? 2 : 1.5}
87
+ className={cn(
88
+ "col-start-1 row-start-1 transition-[color,stroke-width] duration-80",
89
+ isActive || checked ? "text-foreground" : "text-muted-foreground",
90
+ )}
91
+ />
92
+ </span>
93
+ )}
94
+ {Icon && isValidElement(Icon) && <span className="text-muted-foreground">{Icon}</span>}
95
+ <span className="inline-grid flex-1 text-[13px]">
96
+ <span
97
+ className="col-start-1 row-start-1 invisible"
98
+ style={{ fontVariationSettings: fontWeights.semibold }}
99
+ aria-hidden="true"
100
+ >
101
+ {label ?? children}
102
+ </span>
103
+ <span
104
+ className={cn(
105
+ "col-start-1 row-start-1 transition-[color,font-variation-settings] duration-80",
106
+ isActive || checked ? "text-foreground" : "text-muted-foreground",
107
+ )}
108
+ style={{
109
+ fontVariationSettings: checked ? fontWeights.semibold : fontWeights.normal,
110
+ }}
111
+ >
112
+ {label ?? children}
113
+ </span>
114
+ </span>
115
+ {trailing && <span className="text-[12px] text-muted-foreground">{trailing}</span>}
116
+ <AnimatePresence>
117
+ {checked && (
118
+ <motion.svg
119
+ key="check"
120
+ width={16}
121
+ height={16}
122
+ viewBox="0 0 24 24"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ strokeWidth={2}
126
+ strokeLinecap="round"
127
+ strokeLinejoin="round"
128
+ className="text-foreground shrink-0"
129
+ initial={{ opacity: 1 }}
130
+ animate={{ opacity: 1 }}
131
+ exit={{ opacity: 1 }}
132
+ >
133
+ <motion.path
134
+ d="M4 12L9 17L20 6"
135
+ initial={{ pathLength: skipAnimation ? 1 : 0 }}
136
+ animate={{
137
+ pathLength: 1,
138
+ transition: { duration: 0.08, ease: "easeOut" },
139
+ }}
140
+ exit={{
141
+ pathLength: 0,
142
+ transition: { duration: 0.04, ease: "easeIn" },
143
+ }}
144
+ />
145
+ </motion.svg>
146
+ )}
147
+ </AnimatePresence>
148
+ </div>
149
+ );
150
+ },
151
+ );
152
+
153
+ MenuItem.displayName = "MenuItem";
154
+
155
+ export { MenuItem };
156
+ export default MenuItem;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Menu } from "lucide-react";
5
+ import { Button } from "./button";
6
+ import {
7
+ Sheet,
8
+ SheetContent,
9
+ SheetDescription,
10
+ SheetHeader,
11
+ SheetTitle,
12
+ SheetTrigger,
13
+ } from "./sheet";
14
+
15
+ interface MobileDrawerProps extends React.ComponentProps<typeof Sheet> {
16
+ title: React.ReactNode;
17
+ description?: React.ReactNode;
18
+ triggerLabel?: string;
19
+ children: React.ReactNode;
20
+ }
21
+
22
+ function MobileDrawer({
23
+ title,
24
+ description,
25
+ triggerLabel = "Open menu",
26
+ children,
27
+ ...props
28
+ }: MobileDrawerProps) {
29
+ return (
30
+ <Sheet {...props}>
31
+ <SheetTrigger asChild>
32
+ <Button type="button" variant="ghost" size="icon" aria-label={triggerLabel}>
33
+ <Menu />
34
+ </Button>
35
+ </SheetTrigger>
36
+ <SheetContent side="left" className="w-[min(22rem,calc(100vw-2rem))] p-4">
37
+ <SheetHeader>
38
+ <SheetTitle>{title}</SheetTitle>
39
+ {description && <SheetDescription>{description}</SheetDescription>}
40
+ </SheetHeader>
41
+ {children}
42
+ </SheetContent>
43
+ </Sheet>
44
+ );
45
+ }
46
+
47
+ MobileDrawer.displayName = "MobileDrawer";
48
+
49
+ export { MobileDrawer };
50
+ export type { MobileDrawerProps };
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import {
4
+ focusRingClasses,
5
+ iconStrokeClasses,
6
+ pressClasses,
7
+ springMotionClasses,
8
+ } from "../lib/interaction";
9
+
10
+ interface NavItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
11
+ icon?: React.ReactNode;
12
+ active?: boolean;
13
+ trailing?: React.ReactNode;
14
+ }
15
+
16
+ const NavItem = React.forwardRef<HTMLAnchorElement, NavItemProps>(
17
+ ({ icon, active, trailing, className, children, ...props }, ref) => (
18
+ <a
19
+ ref={ref}
20
+ aria-current={active ? "page" : undefined}
21
+ className={cn(
22
+ "group flex min-h-10 items-center gap-2 rounded-xl px-3 body font-medium",
23
+ active
24
+ ? "bg-brand-secondary/35 text-foreground"
25
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
26
+ springMotionClasses,
27
+ pressClasses,
28
+ focusRingClasses,
29
+ iconStrokeClasses,
30
+ className,
31
+ )}
32
+ {...props}
33
+ >
34
+ {icon && <span className="flex size-4 shrink-0 items-center justify-center">{icon}</span>}
35
+ <span className="min-w-0 flex-1 truncate">{children}</span>
36
+ {trailing && <span className="shrink-0 text-muted-foreground">{trailing}</span>}
37
+ </a>
38
+ ),
39
+ );
40
+ NavItem.displayName = "NavItem";
41
+
42
+ export { NavItem };
43
+ export type { NavItemProps };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import { useProximityIndex } from "../lib/interaction";
4
+ import { NavItem } from "./nav-item";
5
+
6
+ export interface NavMenuItem {
7
+ href: string;
8
+ label: React.ReactNode;
9
+ icon?: React.ReactNode;
10
+ active?: boolean;
11
+ trailing?: React.ReactNode;
12
+ }
13
+
14
+ interface NavMenuProps extends React.HTMLAttributes<HTMLElement> {
15
+ items: NavMenuItem[];
16
+ }
17
+
18
+ function NavMenu({ items, className, ...props }: NavMenuProps) {
19
+ const proximity = useProximityIndex<HTMLElement>("y");
20
+
21
+ return (
22
+ <nav
23
+ ref={proximity.containerRef}
24
+ className={cn("grid gap-1", className)}
25
+ {...proximity.handlers}
26
+ {...props}
27
+ >
28
+ {items.map((item, index) => (
29
+ <NavItem
30
+ key={item.href}
31
+ ref={(node) => proximity.registerItem(index, node)}
32
+ href={item.href}
33
+ icon={item.icon}
34
+ active={item.active || proximity.activeIndex === index}
35
+ trailing={item.trailing}
36
+ >
37
+ {item.label}
38
+ </NavItem>
39
+ ))}
40
+ </nav>
41
+ );
42
+ }
43
+
44
+ NavMenu.displayName = "NavMenu";
45
+
46
+ export { NavMenu };
47
+ export type { NavMenuProps };
@@ -3,6 +3,7 @@
3
3
  import * as React from "react";
4
4
  import { Popover } from "radix-ui";
5
5
  import { cn } from "../lib/utils";
6
+ import { motionClasses } from "../lib/interaction";
6
7
  import {
7
8
  mobilePopperSheetMotionClassName,
8
9
  mobilePopperSheetPositionClassName,
@@ -27,7 +28,8 @@ const PopoverContent = React.forwardRef<
27
28
  align={align}
28
29
  sideOffset={sideOffset}
29
30
  className={cn(
30
- "relative z-50 w-72 rounded-xl bg-card p-4 shadow-natural outline-none",
31
+ "relative z-50 w-72 rounded-xl border border-border bg-card p-4 shadow-elevated outline-none",
32
+ motionClasses,
31
33
  mobilePopperSheetPositionClassName,
32
34
  mobilePopperSheetSurfaceClassName,
33
35
  "max-sm:overflow-y-auto max-sm:px-4 max-sm:pb-4 max-sm:pt-6",