@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.
- package/README.md +4 -0
- package/dist/{chunk-MAHPAP65.js → chunk-RI6QJR2L.js} +6 -3
- package/dist/chunk-RI6QJR2L.js.map +1 -0
- package/dist/index.d.ts +789 -115
- package/dist/index.js +10404 -1099
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +17 -0
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.js +1 -1
- package/llms.txt +18 -4
- package/package.json +19 -1
- package/src/components/accordion.tsx +746 -58
- package/src/components/alert-dialog.tsx +11 -5
- package/src/components/ask-user-questions.tsx +1366 -0
- package/src/components/badge.tsx +105 -24
- package/src/components/button.tsx +207 -38
- package/src/components/card.tsx +3 -2
- package/src/components/chat-message.tsx +116 -0
- package/src/components/checkbox-group.tsx +403 -0
- package/src/components/checkbox.tsx +6 -3
- package/src/components/color-input.tsx +3 -1
- package/src/components/color-picker.tsx +1992 -80
- package/src/components/dialog.tsx +141 -81
- package/src/components/dropdown-menu.tsx +21 -13
- package/src/components/dropdown.tsx +245 -0
- package/src/components/file-thumbnail.tsx +156 -0
- package/src/components/hover-card.tsx +2 -0
- package/src/components/input-copy.tsx +307 -0
- package/src/components/input-group.tsx +303 -108
- package/src/components/input-message.tsx +883 -0
- package/src/components/input.tsx +6 -3
- package/src/components/menu-item.tsx +156 -0
- package/src/components/mobile-drawer.tsx +50 -0
- package/src/components/nav-item.tsx +43 -0
- package/src/components/nav-menu.tsx +47 -0
- package/src/components/popover.tsx +3 -1
- package/src/components/radio-group.tsx +381 -35
- package/src/components/scroll-area.tsx +9 -2
- package/src/components/select.tsx +708 -121
- package/src/components/sheet.tsx +15 -6
- package/src/components/slider.tsx +1583 -20
- package/src/components/status-badge.tsx +2 -0
- package/src/components/switch.tsx +245 -28
- package/src/components/table.tsx +191 -77
- package/src/components/tabs-subtle.tsx +411 -0
- package/src/components/tabs.tsx +459 -49
- package/src/components/textarea.tsx +7 -3
- package/src/components/thinking-indicator.tsx +106 -0
- package/src/components/thinking-steps.tsx +382 -0
- package/src/components/tooltip.tsx +158 -33
- package/src/hooks/use-merge-split.tsx +359 -0
- package/src/hooks/use-proximity-hover.ts +208 -0
- package/src/index.ts +14 -0
- package/src/lib/elevated.tsx +52 -0
- package/src/lib/font-weight.ts +18 -0
- package/src/lib/icon-context.tsx +98 -0
- package/src/lib/icon-map.tsx +669 -0
- package/src/lib/interaction.ts +102 -0
- package/src/lib/scroll-fade.tsx +241 -0
- package/src/lib/shape-context.tsx +121 -0
- package/src/lib/springs.ts +30 -0
- package/src/lib/surface-classes.ts +43 -0
- package/src/lib/surface-context.tsx +17 -0
- package/src/tokens/tokens.ts +3 -0
- 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
|
|
4
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 };
|
package/src/components/table.tsx
CHANGED
|
@@ -1,101 +1,215 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
182
|
+
<td
|
|
50
183
|
ref={ref}
|
|
51
184
|
className={cn(
|
|
52
|
-
"transition-colors
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 };
|