@hilum/ui 3.4.0 → 3.5.1

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
@@ -72,6 +72,8 @@ const DEFAULT_STATUS_VARIANT: Record<string, StatusBadgeVariant> = {
72
72
 
73
73
  const DEFAULT_DOT_CLASS: Record<StatusBadgeVariant, string> = {
74
74
  default: "bg-background",
75
+ solid: "bg-background",
76
+ dot: "bg-muted-foreground",
75
77
  secondary: "bg-muted-foreground",
76
78
  outline: "bg-muted-foreground",
77
79
  brand: "bg-background",
@@ -1,33 +1,250 @@
1
1
  "use client";
2
2
 
3
- import * as React from "react";
4
- import { Switch } from "radix-ui";
3
+ import { forwardRef, useRef, useState, useEffect, useCallback, type HTMLAttributes } from "react";
4
+ import { motion, useMotionValue, animate, type Transition } from "framer-motion";
5
+ import * as SwitchPrimitive from "@radix-ui/react-switch";
5
6
  import { cn } from "../lib/utils";
7
+ import { spring } from "../lib/springs";
6
8
 
7
- const SwitchRoot = React.forwardRef<
8
- React.ComponentRef<typeof Switch.Root>,
9
- React.ComponentPropsWithoutRef<typeof Switch.Root>
10
- >(({ className, ...props }, ref) => (
11
- <Switch.Root
12
- ref={ref}
13
- className={cn(
14
- "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full",
15
- "border-2 border-transparent transition-[background-color,border-color,box-shadow] duration-150",
16
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
17
- "disabled:cursor-not-allowed disabled:opacity-50",
18
- "data-[state=unchecked]:bg-muted data-[state=checked]:bg-brand-primary",
9
+ interface SwitchProps extends Omit<HTMLAttributes<HTMLDivElement>, "onToggle"> {
10
+ label?: string;
11
+ checked?: boolean;
12
+ defaultChecked?: boolean;
13
+ onCheckedChange?: (checked: boolean) => void;
14
+ onToggle?: (checked: boolean) => void;
15
+ disabled?: boolean;
16
+ thumbTransition?: Transition;
17
+ }
18
+
19
+ const TRACK_WIDTH = 34;
20
+ const TRACK_HEIGHT = 20;
21
+ const THUMB_SIZE = 16;
22
+ const THUMB_OFFSET = 2;
23
+ const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
24
+ const PILL_EXTEND = 2;
25
+ const PRESS_EXTEND = 4;
26
+ const PRESS_SHRINK = 4;
27
+ const DRAG_DEAD_ZONE = 2;
28
+
29
+ const Switch = forwardRef<HTMLDivElement, SwitchProps>(
30
+ (
31
+ {
32
+ label,
33
+ checked: checkedProp,
34
+ defaultChecked = false,
35
+ onCheckedChange,
36
+ onToggle,
37
+ disabled = false,
38
+ thumbTransition,
19
39
  className,
20
- )}
21
- {...props}
22
- >
23
- <Switch.Thumb
24
- className={cn(
25
- "pointer-events-none block size-5 rounded-full bg-card shadow-natural",
26
- "transition-transform data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-5",
27
- )}
28
- />
29
- </Switch.Root>
30
- ));
31
- SwitchRoot.displayName = "Switch";
32
-
33
- export { SwitchRoot as Switch };
40
+ "aria-label": ariaLabel,
41
+ ...props
42
+ },
43
+ ref,
44
+ ) => {
45
+ const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
46
+ const checked = checkedProp ?? uncontrolledChecked;
47
+ const hasMounted = useRef(false);
48
+ const [hovered, setHovered] = useState(false);
49
+ const [pressed, setPressed] = useState(false);
50
+
51
+ // Drag refs (not state to avoid re-renders during drag)
52
+ const dragging = useRef(false);
53
+ const didDrag = useRef(false);
54
+ const pointerStart = useRef<{
55
+ clientX: number;
56
+ originX: number;
57
+ } | null>(null);
58
+
59
+ // Motion value for thumb x-axis
60
+ const motionX = useMotionValue(checked ? THUMB_OFFSET + THUMB_TRAVEL : THUMB_OFFSET);
61
+
62
+ useEffect(() => {
63
+ hasMounted.current = true;
64
+ }, []);
65
+
66
+ // Compute thumb shape
67
+ const thumbWidth = pressed
68
+ ? THUMB_SIZE + PRESS_EXTEND
69
+ : hovered
70
+ ? THUMB_SIZE + PILL_EXTEND
71
+ : THUMB_SIZE;
72
+ const thumbHeight = pressed ? THUMB_SIZE - PRESS_SHRINK : THUMB_SIZE;
73
+ const thumbY = pressed ? THUMB_OFFSET + PRESS_SHRINK / 2 : THUMB_OFFSET;
74
+ const extraWidth = thumbWidth - THUMB_SIZE;
75
+ const thumbX = checked ? THUMB_OFFSET + THUMB_TRAVEL - extraWidth : THUMB_OFFSET;
76
+
77
+ const setChecked = useCallback(
78
+ (nextChecked: boolean) => {
79
+ if (checkedProp === undefined) setUncontrolledChecked(nextChecked);
80
+ onCheckedChange?.(nextChecked);
81
+ onToggle?.(nextChecked);
82
+ },
83
+ [checkedProp, onCheckedChange, onToggle],
84
+ );
85
+
86
+ // Sync motionX when thumbX changes (hover/press/checked) and not dragging
87
+ useEffect(() => {
88
+ if (dragging.current) return;
89
+ if (!hasMounted.current) {
90
+ motionX.set(thumbX);
91
+ } else {
92
+ animate(motionX, thumbX, thumbTransition ?? spring.moderate);
93
+ }
94
+ }, [thumbX, motionX, thumbTransition]);
95
+
96
+ // --- Pointer handlers ---
97
+
98
+ const handlePointerDown = useCallback(
99
+ (e: React.PointerEvent<HTMLDivElement>) => {
100
+ if (disabled) return;
101
+ if (e.pointerType === "mouse" && e.button !== 0) return;
102
+ setPressed(true);
103
+ dragging.current = false;
104
+ didDrag.current = false;
105
+ pointerStart.current = {
106
+ clientX: e.clientX,
107
+ originX: motionX.get(),
108
+ };
109
+ (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
110
+ },
111
+ [disabled, motionX],
112
+ );
113
+
114
+ const handlePointerMove = useCallback(
115
+ (e: React.PointerEvent<HTMLDivElement>) => {
116
+ if (!pointerStart.current) return;
117
+ const delta = e.clientX - pointerStart.current.clientX;
118
+
119
+ if (!dragging.current) {
120
+ if (Math.abs(delta) < DRAG_DEAD_ZONE) return;
121
+ dragging.current = true;
122
+ }
123
+
124
+ const dragMin = THUMB_OFFSET;
125
+ const pressedThumbWidth = THUMB_SIZE + PRESS_EXTEND;
126
+ const dragMax = TRACK_WIDTH - THUMB_OFFSET - pressedThumbWidth;
127
+ const rawX = pointerStart.current.originX + delta;
128
+ motionX.set(Math.max(dragMin, Math.min(dragMax, rawX)));
129
+ },
130
+ [motionX],
131
+ );
132
+
133
+ const handlePointerUp = useCallback(() => {
134
+ if (!pointerStart.current) return;
135
+ setPressed(false);
136
+
137
+ if (dragging.current) {
138
+ didDrag.current = true;
139
+ dragging.current = false;
140
+
141
+ const currentX = motionX.get();
142
+ const dragMin = THUMB_OFFSET;
143
+ const pressedThumbWidth = THUMB_SIZE + PRESS_EXTEND;
144
+ const dragMax = TRACK_WIDTH - THUMB_OFFSET - pressedThumbWidth;
145
+ const midpoint = (dragMin + dragMax) / 2;
146
+
147
+ const shouldBeOn = currentX > midpoint;
148
+
149
+ if (shouldBeOn !== checked) {
150
+ setChecked(shouldBeOn);
151
+ } else {
152
+ // Snap back to current resting position (un-pressed)
153
+ const snapTarget = checked ? THUMB_OFFSET + THUMB_TRAVEL : THUMB_OFFSET;
154
+ animate(motionX, snapTarget, thumbTransition ?? spring.moderate);
155
+ }
156
+
157
+ requestAnimationFrame(() => {
158
+ didDrag.current = false;
159
+ });
160
+ }
161
+
162
+ pointerStart.current = null;
163
+ }, [checked, setChecked, motionX, thumbTransition]);
164
+
165
+ return (
166
+ <div
167
+ ref={ref}
168
+ className={cn(
169
+ "relative z-10 flex items-center gap-2.5 px-3 py-2 cursor-pointer select-none touch-none",
170
+ disabled && "opacity-50 pointer-events-none",
171
+ className,
172
+ )}
173
+ onPointerEnter={(e) => {
174
+ if (e.pointerType === "mouse") setHovered(true);
175
+ }}
176
+ onPointerLeave={() => setHovered(false)}
177
+ onPointerDown={handlePointerDown}
178
+ onPointerMove={handlePointerMove}
179
+ onPointerUp={handlePointerUp}
180
+ onClick={() => {
181
+ if (disabled || didDrag.current) return;
182
+ setChecked(!checked);
183
+ }}
184
+ {...props}
185
+ >
186
+ {/* Switch */}
187
+ <SwitchPrimitive.Root
188
+ checked={checked}
189
+ aria-label={ariaLabel ?? label}
190
+ onCheckedChange={(nextChecked) => {
191
+ if (didDrag.current) return;
192
+ setChecked(nextChecked);
193
+ }}
194
+ disabled={disabled}
195
+ tabIndex={0}
196
+ className={cn(
197
+ "relative shrink-0 rounded-full outline-none cursor-pointer",
198
+ "transition-colors duration-80",
199
+ "focus-visible:ring-1 focus-visible:ring-[#6B97FF] focus-visible:ring-offset-2 focus-visible:ring-offset-background",
200
+ )}
201
+ style={{
202
+ width: TRACK_WIDTH,
203
+ height: TRACK_HEIGHT,
204
+ backgroundColor: checked
205
+ ? hovered
206
+ ? "#5C89F2"
207
+ : "#6B97FF"
208
+ : hovered
209
+ ? "color-mix(in oklab, var(--accent), rgb(var(--overlay)) 10%)"
210
+ : "var(--accent)",
211
+ }}
212
+ onClick={(e) => e.stopPropagation()}
213
+ >
214
+ <SwitchPrimitive.Thumb asChild>
215
+ <motion.span
216
+ className="absolute top-0 left-0 block rounded-full bg-white shadow-sm"
217
+ initial={false}
218
+ style={{ x: motionX }}
219
+ animate={{
220
+ y: thumbY,
221
+ width: thumbWidth,
222
+ height: thumbHeight,
223
+ }}
224
+ transition={
225
+ hasMounted.current ? (thumbTransition ?? spring.moderate) : { duration: 0 }
226
+ }
227
+ />
228
+ </SwitchPrimitive.Thumb>
229
+ </SwitchPrimitive.Root>
230
+
231
+ {/* Label */}
232
+ {label && (
233
+ <span
234
+ className={cn(
235
+ "text-[13px] transition-[color] duration-80",
236
+ checked ? "text-foreground" : "text-muted-foreground",
237
+ )}
238
+ >
239
+ {label}
240
+ </span>
241
+ )}
242
+ </div>
243
+ );
244
+ },
245
+ );
246
+
247
+ Switch.displayName = "Switch";
248
+
249
+ export { Switch };
250
+ export type { SwitchProps };
@@ -1,101 +1,215 @@
1
- import * as React from "react";
1
+ "use client";
2
+
3
+ import {
4
+ useRef,
5
+ useEffect,
6
+ createContext,
7
+ useContext,
8
+ forwardRef,
9
+ type ReactNode,
10
+ type HTMLAttributes,
11
+ type TdHTMLAttributes,
12
+ type ThHTMLAttributes,
13
+ } from "react";
14
+ import { motion, AnimatePresence } from "framer-motion";
2
15
  import { cn } from "../lib/utils";
16
+ import { spring } from "../lib/springs";
17
+ import { fontWeights } from "../lib/font-weight";
18
+ import { useProximityHover } from "../hooks/use-proximity-hover";
19
+
20
+ // ── Context ──────────────────────────────────────────────
21
+
22
+ interface TableContextValue {
23
+ registerItem: (index: number, element: HTMLElement | null) => void;
24
+ activeIndex: number | null;
25
+ }
26
+
27
+ const TableContext = createContext<TableContextValue | null>(null);
28
+
29
+ // ── Table ────────────────────────────────────────────────
30
+
31
+ interface TableProps extends HTMLAttributes<HTMLTableElement> {
32
+ children: ReactNode;
33
+ containerClassName?: string;
34
+ }
35
+
36
+ const Table = forwardRef<HTMLTableElement, TableProps>(
37
+ ({ children, className, containerClassName, ...props }, ref) => {
38
+ const containerRef = useRef<HTMLDivElement>(null);
39
+
40
+ const { activeIndex, itemRects, sessionRef, handlers, registerItem, measureItems } =
41
+ useProximityHover(containerRef);
42
+
43
+ useEffect(() => {
44
+ measureItems();
45
+ }, [measureItems, children]);
46
+
47
+ const activeRect = activeIndex !== null ? itemRects[activeIndex] : null;
48
+
49
+ return (
50
+ <TableContext.Provider value={{ registerItem, activeIndex }}>
51
+ <div
52
+ ref={containerRef}
53
+ className={cn("relative", containerClassName)}
54
+ onMouseEnter={handlers.onMouseEnter}
55
+ onMouseMove={handlers.onMouseMove}
56
+ onMouseLeave={handlers.onMouseLeave}
57
+ >
58
+ {/* Hover background */}
59
+ <AnimatePresence>
60
+ {activeRect && (
61
+ <motion.div
62
+ key={sessionRef.current}
63
+ className="absolute bg-hover pointer-events-none"
64
+ initial={{
65
+ opacity: 0,
66
+ top: activeRect.top,
67
+ left: activeRect.left,
68
+ width: activeRect.width,
69
+ height: activeRect.height,
70
+ }}
71
+ animate={{
72
+ opacity: 1,
73
+ top: activeRect.top,
74
+ left: activeRect.left,
75
+ width: activeRect.width,
76
+ height: activeRect.height,
77
+ }}
78
+ exit={{ opacity: 0, transition: spring.fast.exit }}
79
+ transition={{
80
+ ...spring.fast,
81
+ opacity: { duration: 0.08 },
82
+ }}
83
+ />
84
+ )}
85
+ </AnimatePresence>
86
+
87
+ <table
88
+ ref={ref}
89
+ className={cn("w-full text-[13px] border-collapse", className)}
90
+ {...props}
91
+ >
92
+ {children}
93
+ </table>
94
+ </div>
95
+ </TableContext.Provider>
96
+ );
97
+ },
98
+ );
3
99
 
4
- const Table = React.forwardRef<
5
- HTMLTableElement,
6
- React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }
7
- >(({ className, containerClassName, ...props }, ref) => (
8
- <div
9
- className={cn(
10
- "w-full overflow-hidden rounded-xl border border-border bg-card",
11
- containerClassName,
12
- )}
13
- >
14
- <table
15
- ref={ref}
16
- className={cn("min-w-full caption-bottom divide-y divide-border", className)}
17
- {...props}
18
- />
19
- </div>
20
- ));
21
100
  Table.displayName = "Table";
22
101
 
23
- const TableHeader = React.forwardRef<
24
- HTMLTableSectionElement,
25
- React.HTMLAttributes<HTMLTableSectionElement>
26
- >(({ className, ...props }, ref) => (
27
- <thead ref={ref} className={cn("bg-muted", className)} {...props} />
28
- ));
102
+ // ── TableHeader ──────────────────────────────────────────
103
+
104
+ const TableHeader = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
105
+ ({ className, ...props }, ref) => <thead ref={ref} className={cn("", className)} {...props} />,
106
+ );
107
+
29
108
  TableHeader.displayName = "TableHeader";
30
109
 
31
- const TableBody = React.forwardRef<
32
- HTMLTableSectionElement,
33
- React.HTMLAttributes<HTMLTableSectionElement>
34
- >(({ className, ...props }, ref) => (
35
- <tbody ref={ref} className={cn("divide-y divide-border bg-card", className)} {...props} />
36
- ));
110
+ // ── TableBody ────────────────────────────────────────────
111
+
112
+ const TableBody = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
113
+ ({ className, ...props }, ref) => <tbody ref={ref} className={cn("", className)} {...props} />,
114
+ );
115
+
37
116
  TableBody.displayName = "TableBody";
38
117
 
39
- const TableFooter = React.forwardRef<
40
- HTMLTableSectionElement,
41
- React.HTMLAttributes<HTMLTableSectionElement>
42
- >(({ className, ...props }, ref) => (
43
- <tfoot ref={ref} className={cn("bg-muted font-medium", className)} {...props} />
44
- ));
45
- TableFooter.displayName = "TableFooter";
118
+ // ── TableRow ─────────────────────────────────────────────
119
+
120
+ interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {
121
+ index?: number;
122
+ }
123
+
124
+ const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(
125
+ ({ index, className, style, ...props }, ref) => {
126
+ const internalRef = useRef<HTMLTableRowElement>(null);
127
+ const ctx = useContext(TableContext);
128
+
129
+ useEffect(() => {
130
+ if (index === undefined || !ctx) return;
131
+ ctx.registerItem(index, internalRef.current);
132
+ return () => ctx.registerItem(index, null);
133
+ }, [index, ctx]);
134
+
135
+ const isBodyRow = index !== undefined;
136
+ const activeIdx = ctx?.activeIndex ?? null;
137
+ const hideBorder =
138
+ activeIdx !== null &&
139
+ ((isBodyRow && (index === activeIdx || index === activeIdx - 1)) ||
140
+ (!isBodyRow && activeIdx === 0));
46
141
 
47
- const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
142
+ return (
143
+ <tr
144
+ ref={(node) => {
145
+ (internalRef as React.MutableRefObject<HTMLTableRowElement | null>).current = node;
146
+ if (typeof ref === "function") ref(node);
147
+ else if (ref) (ref as React.MutableRefObject<HTMLTableRowElement | null>).current = node;
148
+ }}
149
+ data-proximity-index={index}
150
+ className={cn(
151
+ "group/row relative z-10 border-b transition-[border-color] duration-80",
152
+ hideBorder ? "border-transparent" : "border-accent/40",
153
+ isBodyRow && activeIdx === index && "is-active",
154
+ className,
155
+ )}
156
+ style={{
157
+ ...style,
158
+ fontVariationSettings: isBodyRow ? fontWeights.normal : fontWeights.semibold,
159
+ }}
160
+ {...props}
161
+ />
162
+ );
163
+ },
164
+ );
165
+
166
+ TableRow.displayName = "TableRow";
167
+
168
+ // ── TableHead ────────────────────────────────────────────
169
+
170
+ const TableHead = forwardRef<HTMLTableCellElement, ThHTMLAttributes<HTMLTableCellElement>>(
171
+ ({ className, ...props }, ref) => (
172
+ <th ref={ref} className={cn("px-3 py-2 text-left text-foreground", className)} {...props} />
173
+ ),
174
+ );
175
+
176
+ TableHead.displayName = "TableHead";
177
+
178
+ // ── TableCell ────────────────────────────────────────────
179
+
180
+ const TableCell = forwardRef<HTMLTableCellElement, TdHTMLAttributes<HTMLTableCellElement>>(
48
181
  ({ className, ...props }, ref) => (
49
- <tr
182
+ <td
50
183
  ref={ref}
51
184
  className={cn(
52
- "transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
185
+ "px-3 py-2 text-muted-foreground transition-colors duration-80 group-[.is-active]/row:text-foreground",
53
186
  className,
54
187
  )}
55
188
  {...props}
56
189
  />
57
190
  ),
58
191
  );
59
- TableRow.displayName = "TableRow";
60
192
 
61
- const TableHead = React.forwardRef<
62
- HTMLTableCellElement,
63
- React.ThHTMLAttributes<HTMLTableCellElement>
64
- >(({ className, ...props }, ref) => (
65
- <th
66
- ref={ref}
67
- className={cn(
68
- "px-6 py-3 text-left align-middle label text-muted-foreground",
69
- "[&:has([role=checkbox])]:pr-0",
70
- className,
71
- )}
72
- {...props}
73
- />
74
- ));
75
- TableHead.displayName = "TableHead";
76
-
77
- const TableCell = React.forwardRef<
78
- HTMLTableCellElement,
79
- React.TdHTMLAttributes<HTMLTableCellElement>
80
- >(({ className, ...props }, ref) => (
81
- <td
82
- ref={ref}
83
- className={cn(
84
- "px-6 py-4 align-middle body text-muted-foreground",
85
- "[&:has([role=checkbox])]:pr-0",
86
- className,
87
- )}
88
- {...props}
89
- />
90
- ));
91
193
  TableCell.displayName = "TableCell";
92
194
 
93
- const TableCaption = React.forwardRef<
94
- HTMLTableCaptionElement,
95
- React.HTMLAttributes<HTMLTableCaptionElement>
96
- >(({ className, ...props }, ref) => (
97
- <caption ref={ref} className={cn("mt-4 caption text-muted-foreground", className)} {...props} />
98
- ));
195
+ const TableFooter = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
196
+ ({ className, ...props }, ref) => <tfoot ref={ref} className={cn("", className)} {...props} />,
197
+ );
198
+
199
+ TableFooter.displayName = "TableFooter";
200
+
201
+ const TableCaption = forwardRef<HTMLTableCaptionElement, HTMLAttributes<HTMLTableCaptionElement>>(
202
+ ({ className, ...props }, ref) => (
203
+ <caption
204
+ ref={ref}
205
+ className={cn("mt-4 text-[13px] text-muted-foreground", className)}
206
+ {...props}
207
+ />
208
+ ),
209
+ );
210
+
99
211
  TableCaption.displayName = "TableCaption";
100
212
 
213
+ // ── Exports ──────────────────────────────────────────────
214
+
101
215
  export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption };