@vllnt/ui 0.2.0 → 0.2.1-canary.73a93ee
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/CHANGELOG.md +12 -1
- package/README.md +27 -12
- package/dist/components/activity-log/activity-log.js +1 -0
- package/dist/components/anchor-port/anchor-port.js +51 -0
- package/dist/components/anchor-port/index.js +4 -0
- package/dist/components/animated-text/animated-text.js +1 -0
- package/dist/components/bottom-bar/bottom-bar.js +25 -0
- package/dist/components/bottom-bar/index.js +4 -0
- package/dist/components/canvas-shell/canvas-foundation-demo.js +183 -0
- package/dist/components/canvas-shell/canvas-shell-route-config.js +0 -0
- package/dist/components/canvas-shell/canvas-shell.js +261 -0
- package/dist/components/canvas-shell/index.js +4 -0
- package/dist/components/canvas-view/canvas-view.js +461 -0
- package/dist/components/canvas-view/index.js +6 -0
- package/dist/components/chart/area-chart.js +1 -0
- package/dist/components/chart/line-chart.js +1 -0
- package/dist/components/chat-dock-section/chat-dock-section.js +56 -0
- package/dist/components/chat-dock-section/index.js +6 -0
- package/dist/components/checklist/checklist.js +7 -0
- package/dist/components/checklist/index.js +3 -1
- package/dist/components/comment-pin/comment-pin.js +104 -0
- package/dist/components/comment-pin/index.js +6 -0
- package/dist/components/connector-edge/connector-edge.js +66 -0
- package/dist/components/connector-edge/index.js +6 -0
- package/dist/components/conversation-thread/conversation-thread.js +348 -0
- package/dist/components/conversation-thread/index.js +20 -0
- package/dist/components/curriculum/curriculum.js +349 -0
- package/dist/components/curriculum/index.js +10 -0
- package/dist/components/data-list/data-list.js +1 -0
- package/dist/components/edge-label/edge-label.js +26 -0
- package/dist/components/edge-label/index.js +4 -0
- package/dist/components/form/form.js +432 -0
- package/dist/components/form/index.js +20 -0
- package/dist/components/glass-panel/glass-panel.js +21 -0
- package/dist/components/glass-panel/index.js +4 -0
- package/dist/components/group-hull/group-hull.js +29 -0
- package/dist/components/group-hull/index.js +4 -0
- package/dist/components/index.js +176 -0
- package/dist/components/infinite-plane/index.js +6 -0
- package/dist/components/infinite-plane/infinite-plane.js +75 -0
- package/dist/components/left-rail/index.js +4 -0
- package/dist/components/left-rail/left-rail.js +25 -0
- package/dist/components/live-cursor/index.js +6 -0
- package/dist/components/live-cursor/live-cursor.js +62 -0
- package/dist/components/mini-map-panel/index.js +6 -0
- package/dist/components/mini-map-panel/mini-map-panel.js +74 -0
- package/dist/components/multi-select/index.js +6 -0
- package/dist/components/multi-select/multi-select.js +258 -0
- package/dist/components/object-card/index.js +6 -0
- package/dist/components/object-card/object-card.js +126 -0
- package/dist/components/object-handle/index.js +4 -0
- package/dist/components/object-handle/object-handle.js +38 -0
- package/dist/components/overview-board/index.js +8 -0
- package/dist/components/overview-board/overview-board.js +127 -0
- package/dist/components/presence-stack/index.js +6 -0
- package/dist/components/presence-stack/presence-stack.js +108 -0
- package/dist/components/presence-sync-indicator/index.js +6 -0
- package/dist/components/presence-sync-indicator/presence-sync-indicator.js +73 -0
- package/dist/components/progress-tracker/index.js +20 -0
- package/dist/components/progress-tracker/progress-tracker.js +527 -0
- package/dist/components/right-dock/index.js +4 -0
- package/dist/components/right-dock/right-dock.js +28 -0
- package/dist/components/run-timeline/index.js +6 -0
- package/dist/components/run-timeline/run-timeline.js +221 -0
- package/dist/components/segmented-control/index.js +12 -0
- package/dist/components/segmented-control/segmented-control.js +61 -0
- package/dist/components/selection-presence/index.js +6 -0
- package/dist/components/selection-presence/selection-presence.js +50 -0
- package/dist/components/spinner/unicode-spinner.js +1 -0
- package/dist/components/tags-input/index.js +4 -0
- package/dist/components/tags-input/tags-input.js +178 -0
- package/dist/components/thread-bubble/index.js +6 -0
- package/dist/components/thread-bubble/thread-bubble.js +85 -0
- package/dist/components/top-bar/index.js +4 -0
- package/dist/components/top-bar/top-bar.js +31 -0
- package/dist/components/usage-breakdown/usage-breakdown.js +1 -0
- package/dist/components/viewport-bookmarks/index.js +6 -0
- package/dist/components/viewport-bookmarks/viewport-bookmarks.js +116 -0
- package/dist/components/workspace-switcher/index.js +6 -0
- package/dist/components/workspace-switcher/workspace-switcher.js +61 -0
- package/dist/components/world-breadcrumbs/index.js +6 -0
- package/dist/components/world-breadcrumbs/world-breadcrumbs.js +114 -0
- package/dist/components/zoom-hud/index.js +4 -0
- package/dist/components/zoom-hud/zoom-hud.js +61 -0
- package/dist/index.d.ts +1468 -6
- package/package.json +7 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
forwardRef
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const STATE_FILL = {
|
|
8
|
+
complete: "bg-emerald-500/70",
|
|
9
|
+
failed: "bg-red-500/70",
|
|
10
|
+
queued: "bg-amber-500/70",
|
|
11
|
+
running: "bg-blue-500/70",
|
|
12
|
+
stopped: "bg-muted-foreground/40"
|
|
13
|
+
};
|
|
14
|
+
const STATE_LABEL = {
|
|
15
|
+
complete: "Complete",
|
|
16
|
+
failed: "Failed",
|
|
17
|
+
queued: "Queued",
|
|
18
|
+
running: "Running",
|
|
19
|
+
stopped: "Stopped"
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_LABELS = {
|
|
22
|
+
empty: "No phases",
|
|
23
|
+
region: "Run timeline"
|
|
24
|
+
};
|
|
25
|
+
const clamp = (v, min, max) => {
|
|
26
|
+
if (v < min) {
|
|
27
|
+
return min;
|
|
28
|
+
}
|
|
29
|
+
if (v > max) {
|
|
30
|
+
return max;
|
|
31
|
+
}
|
|
32
|
+
return v;
|
|
33
|
+
};
|
|
34
|
+
const Endpoints = (props) => {
|
|
35
|
+
const fmt = props.format;
|
|
36
|
+
const showCursor = typeof props.cursor === "number";
|
|
37
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[11px] text-muted-foreground", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("span", { "data-run-timeline-start": true, children: fmt ? fmt(props.start) : props.start }),
|
|
39
|
+
showCursor ? /* @__PURE__ */ jsx(
|
|
40
|
+
"span",
|
|
41
|
+
{
|
|
42
|
+
className: "font-semibold text-foreground",
|
|
43
|
+
"data-run-timeline-cursor-label": true,
|
|
44
|
+
children: fmt ? fmt(props.cursor ?? 0) : props.cursor
|
|
45
|
+
}
|
|
46
|
+
) : null,
|
|
47
|
+
/* @__PURE__ */ jsx("span", { "data-run-timeline-end": true, children: fmt ? fmt(props.end) : props.end })
|
|
48
|
+
] });
|
|
49
|
+
};
|
|
50
|
+
const PhaseBar = (props) => {
|
|
51
|
+
const { laneIndex, laneTotal, phase, span, start } = props;
|
|
52
|
+
const left = clamp((phase.start - start) / span, 0, 1) * 100;
|
|
53
|
+
const right = clamp((phase.end - start) / span, 0, 1) * 100;
|
|
54
|
+
const width = Math.max(right - left, 0.5);
|
|
55
|
+
const top = laneIndex / laneTotal * 100;
|
|
56
|
+
const height = 100 / laneTotal;
|
|
57
|
+
const state = phase.state ?? "running";
|
|
58
|
+
const sharedStyle = {
|
|
59
|
+
height: `${height}%`,
|
|
60
|
+
left: `${left}%`,
|
|
61
|
+
top: `${top}%`,
|
|
62
|
+
width: `${width}%`
|
|
63
|
+
};
|
|
64
|
+
const ariaLabel = `${STATE_LABEL[state]} ${phase.start} \u2192 ${phase.end}`;
|
|
65
|
+
if (phase.onActivate) {
|
|
66
|
+
const handleClick = () => {
|
|
67
|
+
phase.onActivate?.();
|
|
68
|
+
};
|
|
69
|
+
return /* @__PURE__ */ jsx(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
"aria-label": ariaLabel,
|
|
73
|
+
className: cn(
|
|
74
|
+
"absolute flex items-center justify-start overflow-hidden truncate rounded-sm border border-border/50 px-1 text-left text-[10px] text-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
75
|
+
STATE_FILL[state]
|
|
76
|
+
),
|
|
77
|
+
"data-run-phase": phase.id,
|
|
78
|
+
"data-run-phase-state": state,
|
|
79
|
+
onClick: handleClick,
|
|
80
|
+
style: sharedStyle,
|
|
81
|
+
type: "button",
|
|
82
|
+
children: phase.label
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return /* @__PURE__ */ jsx(
|
|
87
|
+
"span",
|
|
88
|
+
{
|
|
89
|
+
"aria-label": ariaLabel,
|
|
90
|
+
className: cn(
|
|
91
|
+
"absolute flex items-center justify-start overflow-hidden truncate rounded-sm border border-border/50 px-1 text-[10px] text-foreground",
|
|
92
|
+
STATE_FILL[state]
|
|
93
|
+
),
|
|
94
|
+
"data-run-phase": phase.id,
|
|
95
|
+
"data-run-phase-state": state,
|
|
96
|
+
role: "img",
|
|
97
|
+
style: sharedStyle,
|
|
98
|
+
children: phase.label
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
const TrackBody = (props) => {
|
|
103
|
+
const { cursor, end, lanes, phases, start } = props;
|
|
104
|
+
const span = end - start;
|
|
105
|
+
const cursorRatio = typeof cursor === "number" ? clamp((cursor - start) / span, 0, 1) : null;
|
|
106
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
|
|
107
|
+
/* @__PURE__ */ jsx("div", { className: "flex w-24 flex-col gap-px text-[10px] uppercase tracking-wide text-muted-foreground", children: lanes.map((lane) => /* @__PURE__ */ jsx(
|
|
108
|
+
"div",
|
|
109
|
+
{
|
|
110
|
+
className: "flex h-7 items-center px-2",
|
|
111
|
+
"data-run-timeline-lane": lane.id,
|
|
112
|
+
children: lane.label
|
|
113
|
+
},
|
|
114
|
+
lane.id
|
|
115
|
+
)) }),
|
|
116
|
+
/* @__PURE__ */ jsxs(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
className: "relative flex-1 overflow-hidden rounded-md border border-border bg-muted/20",
|
|
120
|
+
"data-run-timeline-track": true,
|
|
121
|
+
style: { height: `${lanes.length * 28}px` },
|
|
122
|
+
children: [
|
|
123
|
+
phases.map((phase) => {
|
|
124
|
+
const index = lanes.findIndex(
|
|
125
|
+
(lane) => lane.id === (phase.laneId ?? "default")
|
|
126
|
+
);
|
|
127
|
+
if (index === -1) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
PhaseBar,
|
|
132
|
+
{
|
|
133
|
+
laneIndex: index,
|
|
134
|
+
laneTotal: lanes.length,
|
|
135
|
+
phase,
|
|
136
|
+
span,
|
|
137
|
+
start
|
|
138
|
+
},
|
|
139
|
+
phase.id
|
|
140
|
+
);
|
|
141
|
+
}),
|
|
142
|
+
cursorRatio === null ? null : /* @__PURE__ */ jsx(
|
|
143
|
+
"span",
|
|
144
|
+
{
|
|
145
|
+
"aria-hidden": "true",
|
|
146
|
+
className: "absolute top-0 bottom-0 w-px bg-foreground",
|
|
147
|
+
"data-run-timeline-cursor": true,
|
|
148
|
+
style: { left: `${cursorRatio * 100}%` }
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] });
|
|
155
|
+
};
|
|
156
|
+
const resolveLanes = (explicit) => {
|
|
157
|
+
if (explicit && explicit.length > 0) {
|
|
158
|
+
return explicit;
|
|
159
|
+
}
|
|
160
|
+
return [{ id: "default", label: "Run" }];
|
|
161
|
+
};
|
|
162
|
+
const RunTimeline = forwardRef(
|
|
163
|
+
(props, ref) => {
|
|
164
|
+
const {
|
|
165
|
+
className,
|
|
166
|
+
cursor,
|
|
167
|
+
end,
|
|
168
|
+
formatValue,
|
|
169
|
+
labels,
|
|
170
|
+
lanes,
|
|
171
|
+
phases,
|
|
172
|
+
start,
|
|
173
|
+
...rest
|
|
174
|
+
} = props;
|
|
175
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
176
|
+
const safeEnd = end <= start ? start + 1 : end;
|
|
177
|
+
const resolvedLanes = resolveLanes(lanes);
|
|
178
|
+
return /* @__PURE__ */ jsxs(
|
|
179
|
+
"section",
|
|
180
|
+
{
|
|
181
|
+
"aria-label": resolvedLabels.region,
|
|
182
|
+
className: cn("flex w-full flex-col gap-2", className),
|
|
183
|
+
"data-run-timeline": true,
|
|
184
|
+
ref,
|
|
185
|
+
...rest,
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ jsx(
|
|
188
|
+
Endpoints,
|
|
189
|
+
{
|
|
190
|
+
cursor,
|
|
191
|
+
end: safeEnd,
|
|
192
|
+
format: formatValue,
|
|
193
|
+
start
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
phases.length === 0 ? /* @__PURE__ */ jsx(
|
|
197
|
+
"p",
|
|
198
|
+
{
|
|
199
|
+
className: "rounded-md border border-border bg-muted/20 px-2 py-3 text-center text-[11px] text-muted-foreground",
|
|
200
|
+
"data-run-timeline-state": "empty",
|
|
201
|
+
children: resolvedLabels.empty
|
|
202
|
+
}
|
|
203
|
+
) : /* @__PURE__ */ jsx(
|
|
204
|
+
TrackBody,
|
|
205
|
+
{
|
|
206
|
+
cursor,
|
|
207
|
+
end: safeEnd,
|
|
208
|
+
lanes: resolvedLanes,
|
|
209
|
+
phases,
|
|
210
|
+
start
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
RunTimeline.displayName = "RunTimeline";
|
|
219
|
+
export {
|
|
220
|
+
RunTimeline
|
|
221
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SegmentedControl,
|
|
3
|
+
SegmentedControlItem,
|
|
4
|
+
segmentedControlItemVariants,
|
|
5
|
+
segmentedControlVariants
|
|
6
|
+
} from "./segmented-control";
|
|
7
|
+
export {
|
|
8
|
+
SegmentedControl,
|
|
9
|
+
SegmentedControlItem,
|
|
10
|
+
segmentedControlItemVariants,
|
|
11
|
+
segmentedControlVariants
|
|
12
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const segmentedControlVariants = cva(
|
|
8
|
+
"inline-flex w-full items-center rounded-lg bg-muted p-1 text-muted-foreground",
|
|
9
|
+
{
|
|
10
|
+
defaultVariants: {
|
|
11
|
+
size: "default"
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
default: "min-h-10",
|
|
16
|
+
lg: "min-h-11",
|
|
17
|
+
sm: "min-h-9"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
const segmentedControlItemVariants = cva(
|
|
23
|
+
"inline-flex flex-1 items-center justify-center rounded-md px-3 text-sm font-medium whitespace-nowrap transition-all outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm",
|
|
24
|
+
{
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
size: "default"
|
|
27
|
+
},
|
|
28
|
+
variants: {
|
|
29
|
+
size: {
|
|
30
|
+
default: "min-h-8",
|
|
31
|
+
lg: "min-h-9 px-4",
|
|
32
|
+
sm: "min-h-7 px-2.5 text-xs"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
const SegmentedControl = React.forwardRef(({ className, size, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
38
|
+
ToggleGroupPrimitive.Root,
|
|
39
|
+
{
|
|
40
|
+
className: cn(segmentedControlVariants({ size }), className),
|
|
41
|
+
ref,
|
|
42
|
+
type: "single",
|
|
43
|
+
...props
|
|
44
|
+
}
|
|
45
|
+
));
|
|
46
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
47
|
+
const SegmentedControlItem = React.forwardRef(({ className, size, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
48
|
+
ToggleGroupPrimitive.Item,
|
|
49
|
+
{
|
|
50
|
+
className: cn(segmentedControlItemVariants({ size }), className),
|
|
51
|
+
ref,
|
|
52
|
+
...props
|
|
53
|
+
}
|
|
54
|
+
));
|
|
55
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
56
|
+
export {
|
|
57
|
+
SegmentedControl,
|
|
58
|
+
SegmentedControlItem,
|
|
59
|
+
segmentedControlItemVariants,
|
|
60
|
+
segmentedControlVariants
|
|
61
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
forwardRef
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const DEFAULT_LABELS = {
|
|
8
|
+
region: "Selection presence"
|
|
9
|
+
};
|
|
10
|
+
const SelectionPresence = forwardRef((props, ref) => {
|
|
11
|
+
const { className, color, height, labels, name, width, x, y, ...rest } = props;
|
|
12
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
13
|
+
const accent = color ?? "var(--foreground)";
|
|
14
|
+
const ariaLabel = typeof name === "string" ? `${resolvedLabels.region}: ${name}` : resolvedLabels.region;
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
"aria-label": ariaLabel,
|
|
19
|
+
className: cn(
|
|
20
|
+
"pointer-events-none absolute z-20 rounded-md border-2 border-dashed",
|
|
21
|
+
className
|
|
22
|
+
),
|
|
23
|
+
"data-selection-presence": true,
|
|
24
|
+
ref,
|
|
25
|
+
role: "img",
|
|
26
|
+
style: {
|
|
27
|
+
backgroundColor: `color-mix(in srgb, ${accent} 10%, transparent)`,
|
|
28
|
+
borderColor: accent,
|
|
29
|
+
height,
|
|
30
|
+
left: x,
|
|
31
|
+
top: y,
|
|
32
|
+
width
|
|
33
|
+
},
|
|
34
|
+
...rest,
|
|
35
|
+
children: name === null || name === void 0 ? null : /* @__PURE__ */ jsx(
|
|
36
|
+
"span",
|
|
37
|
+
{
|
|
38
|
+
className: "absolute -top-5 left-0 inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium text-white shadow-sm",
|
|
39
|
+
"data-selection-presence-chip": true,
|
|
40
|
+
style: { backgroundColor: accent },
|
|
41
|
+
children: name
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
SelectionPresence.displayName = "SelectionPresence";
|
|
48
|
+
export {
|
|
49
|
+
SelectionPresence
|
|
50
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
function normalizeTag(tag) {
|
|
7
|
+
return tag.trim();
|
|
8
|
+
}
|
|
9
|
+
function getNormalizedTags(tags) {
|
|
10
|
+
return tags.map(normalizeTag).filter(
|
|
11
|
+
(tag, index, values) => tag.length > 0 && values.indexOf(tag) === index
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function shouldAddTagFromKey(key) {
|
|
15
|
+
return key === "Enter" || key === ",";
|
|
16
|
+
}
|
|
17
|
+
function useTagsInputState({
|
|
18
|
+
defaultValue,
|
|
19
|
+
onValueChange,
|
|
20
|
+
value
|
|
21
|
+
}) {
|
|
22
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(
|
|
23
|
+
() => getNormalizedTags(defaultValue)
|
|
24
|
+
);
|
|
25
|
+
const isControlled = value !== void 0;
|
|
26
|
+
const tags = React.useMemo(
|
|
27
|
+
() => getNormalizedTags(value ?? uncontrolledValue),
|
|
28
|
+
[uncontrolledValue, value]
|
|
29
|
+
);
|
|
30
|
+
const updateTags = React.useCallback(
|
|
31
|
+
(nextTags) => {
|
|
32
|
+
const normalizedTags = getNormalizedTags(nextTags);
|
|
33
|
+
if (!isControlled) {
|
|
34
|
+
setUncontrolledValue(normalizedTags);
|
|
35
|
+
}
|
|
36
|
+
onValueChange?.(normalizedTags);
|
|
37
|
+
},
|
|
38
|
+
[isControlled, onValueChange]
|
|
39
|
+
);
|
|
40
|
+
return { tags, updateTags };
|
|
41
|
+
}
|
|
42
|
+
function useTagsInputHandlers({
|
|
43
|
+
disabled,
|
|
44
|
+
inputValue,
|
|
45
|
+
onKeyDown,
|
|
46
|
+
setInputValue,
|
|
47
|
+
tags,
|
|
48
|
+
updateTags
|
|
49
|
+
}) {
|
|
50
|
+
const removeTag = React.useCallback(
|
|
51
|
+
(tagToRemove) => {
|
|
52
|
+
updateTags(tags.filter((tag) => tag !== tagToRemove));
|
|
53
|
+
},
|
|
54
|
+
[tags, updateTags]
|
|
55
|
+
);
|
|
56
|
+
const commitTag = React.useCallback(() => {
|
|
57
|
+
const nextTag = normalizeTag(inputValue);
|
|
58
|
+
if (nextTag.length === 0 || tags.includes(nextTag)) {
|
|
59
|
+
setInputValue("");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
updateTags([...tags, nextTag]);
|
|
63
|
+
setInputValue("");
|
|
64
|
+
}, [inputValue, setInputValue, tags, updateTags]);
|
|
65
|
+
const handleKeyDown = React.useCallback(
|
|
66
|
+
(event) => {
|
|
67
|
+
onKeyDown?.(event);
|
|
68
|
+
if (event.defaultPrevented || disabled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (shouldAddTagFromKey(event.key)) {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
commitTag();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if ((event.key === "Backspace" || event.key === "Delete") && inputValue.length === 0) {
|
|
77
|
+
const lastTag = tags.at(-1);
|
|
78
|
+
if (lastTag) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
removeTag(lastTag);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[commitTag, disabled, inputValue.length, onKeyDown, removeTag, tags]
|
|
85
|
+
);
|
|
86
|
+
return { handleKeyDown, removeTag };
|
|
87
|
+
}
|
|
88
|
+
function TagList({ disabled, onRemove, tags }) {
|
|
89
|
+
return /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap items-center gap-2", children: tags.map((tag) => /* @__PURE__ */ jsxs(
|
|
90
|
+
"li",
|
|
91
|
+
{
|
|
92
|
+
className: "flex items-center gap-1 rounded-md border bg-muted px-2 py-1 text-sm text-foreground",
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx("span", { children: tag }),
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
"aria-label": `Remove ${tag}`,
|
|
99
|
+
className: "rounded-sm text-muted-foreground outline-none ring-offset-background transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
100
|
+
disabled,
|
|
101
|
+
onClick: (event) => {
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
onRemove(tag);
|
|
104
|
+
},
|
|
105
|
+
type: "button",
|
|
106
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
tag
|
|
112
|
+
)) });
|
|
113
|
+
}
|
|
114
|
+
const TagsInput = React.forwardRef(
|
|
115
|
+
({
|
|
116
|
+
className,
|
|
117
|
+
defaultValue = [],
|
|
118
|
+
disabled = false,
|
|
119
|
+
onBlur,
|
|
120
|
+
onKeyDown,
|
|
121
|
+
onValueChange,
|
|
122
|
+
placeholder = "Add a tag",
|
|
123
|
+
value,
|
|
124
|
+
...props
|
|
125
|
+
}, ref) => {
|
|
126
|
+
const [inputValue, setInputValue] = React.useState("");
|
|
127
|
+
const { tags, updateTags } = useTagsInputState({
|
|
128
|
+
defaultValue,
|
|
129
|
+
onValueChange,
|
|
130
|
+
value
|
|
131
|
+
});
|
|
132
|
+
const { handleKeyDown, removeTag } = useTagsInputHandlers({
|
|
133
|
+
disabled,
|
|
134
|
+
inputValue,
|
|
135
|
+
onKeyDown,
|
|
136
|
+
setInputValue,
|
|
137
|
+
tags,
|
|
138
|
+
updateTags
|
|
139
|
+
});
|
|
140
|
+
return /* @__PURE__ */ jsxs(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
"aria-disabled": disabled || void 0,
|
|
144
|
+
className: cn(
|
|
145
|
+
"flex min-h-10 w-full flex-wrap items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background transition-colors focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
|
|
146
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
147
|
+
className
|
|
148
|
+
),
|
|
149
|
+
"data-disabled": disabled ? "true" : void 0,
|
|
150
|
+
role: "group",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ jsx(TagList, { disabled, onRemove: removeTag, tags }),
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
"input",
|
|
155
|
+
{
|
|
156
|
+
...props,
|
|
157
|
+
className: "min-w-[8rem] flex-1 border-0 bg-transparent p-0 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed",
|
|
158
|
+
disabled,
|
|
159
|
+
onBlur,
|
|
160
|
+
onChange: (event) => {
|
|
161
|
+
setInputValue(event.target.value);
|
|
162
|
+
},
|
|
163
|
+
onKeyDown: handleKeyDown,
|
|
164
|
+
placeholder,
|
|
165
|
+
ref,
|
|
166
|
+
type: "text",
|
|
167
|
+
value: inputValue
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
TagsInput.displayName = "TagsInput";
|
|
176
|
+
export {
|
|
177
|
+
TagsInput
|
|
178
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
forwardRef
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const DEFAULT_LABELS = {
|
|
8
|
+
empty: "No replies yet",
|
|
9
|
+
region: "Comment thread"
|
|
10
|
+
};
|
|
11
|
+
const Message = (props) => {
|
|
12
|
+
const { message } = props;
|
|
13
|
+
return /* @__PURE__ */ jsxs("li", { className: "space-y-0.5", "data-thread-bubble-message": message.id, children: [
|
|
14
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-baseline justify-between gap-2 text-[10px]", children: [
|
|
15
|
+
/* @__PURE__ */ jsx(
|
|
16
|
+
"span",
|
|
17
|
+
{
|
|
18
|
+
className: "font-semibold",
|
|
19
|
+
"data-thread-bubble-author": true,
|
|
20
|
+
style: message.authorColor ? { color: message.authorColor } : void 0,
|
|
21
|
+
children: message.author
|
|
22
|
+
}
|
|
23
|
+
),
|
|
24
|
+
message.ts ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", "data-thread-bubble-ts": true, children: message.ts }) : null
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-foreground", "data-thread-bubble-body": true, children: message.body })
|
|
27
|
+
] });
|
|
28
|
+
};
|
|
29
|
+
const ThreadBubble = forwardRef(
|
|
30
|
+
(props, ref) => {
|
|
31
|
+
const { className, footer, labels, messages, onResolve, title, ...rest } = props;
|
|
32
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
33
|
+
const handleResolve = () => {
|
|
34
|
+
onResolve?.();
|
|
35
|
+
};
|
|
36
|
+
return /* @__PURE__ */ jsxs(
|
|
37
|
+
"section",
|
|
38
|
+
{
|
|
39
|
+
"aria-label": resolvedLabels.region,
|
|
40
|
+
className: cn(
|
|
41
|
+
"flex w-72 flex-col gap-2 rounded-lg border border-border bg-background p-3 text-foreground shadow-md",
|
|
42
|
+
className
|
|
43
|
+
),
|
|
44
|
+
"data-thread-bubble": true,
|
|
45
|
+
ref,
|
|
46
|
+
...rest,
|
|
47
|
+
children: [
|
|
48
|
+
title || onResolve ? /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-2 text-[11px] uppercase tracking-wide text-muted-foreground", children: [
|
|
49
|
+
title ? /* @__PURE__ */ jsx("span", { className: "truncate font-semibold", "data-thread-bubble-title": true, children: title }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
|
|
50
|
+
onResolve ? /* @__PURE__ */ jsx(
|
|
51
|
+
"button",
|
|
52
|
+
{
|
|
53
|
+
className: "rounded-full border border-border px-2 py-0.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
54
|
+
"data-thread-bubble-resolve": true,
|
|
55
|
+
onClick: handleResolve,
|
|
56
|
+
type: "button",
|
|
57
|
+
children: "Resolve"
|
|
58
|
+
}
|
|
59
|
+
) : null
|
|
60
|
+
] }) : null,
|
|
61
|
+
messages.length === 0 ? /* @__PURE__ */ jsx(
|
|
62
|
+
"p",
|
|
63
|
+
{
|
|
64
|
+
className: "px-1 py-2 text-center text-[11px] text-muted-foreground",
|
|
65
|
+
"data-thread-bubble-state": "empty",
|
|
66
|
+
children: resolvedLabels.empty
|
|
67
|
+
}
|
|
68
|
+
) : /* @__PURE__ */ jsx("ul", { className: "space-y-2 overflow-y-auto", "data-thread-bubble-list": true, children: messages.map((message) => /* @__PURE__ */ jsx(Message, { message }, message.id)) }),
|
|
69
|
+
footer ? /* @__PURE__ */ jsx(
|
|
70
|
+
"footer",
|
|
71
|
+
{
|
|
72
|
+
className: "border-t border-border pt-2",
|
|
73
|
+
"data-thread-bubble-footer": true,
|
|
74
|
+
children: footer
|
|
75
|
+
}
|
|
76
|
+
) : null
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
ThreadBubble.displayName = "ThreadBubble";
|
|
83
|
+
export {
|
|
84
|
+
ThreadBubble
|
|
85
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
const TopBar = forwardRef(
|
|
5
|
+
({ children, className, leading, subtitle, title, trailing, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
6
|
+
"header",
|
|
7
|
+
{
|
|
8
|
+
className: cn(
|
|
9
|
+
"flex min-h-14 items-center justify-between gap-3 border-b border-border bg-background px-4 font-mono",
|
|
10
|
+
className
|
|
11
|
+
),
|
|
12
|
+
ref,
|
|
13
|
+
...props,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
|
|
16
|
+
leading ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: leading }) : null,
|
|
17
|
+
title || subtitle ? /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
18
|
+
title ? /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium uppercase tracking-[0.18em] text-foreground", children: title }) : null,
|
|
19
|
+
subtitle ? /* @__PURE__ */ jsx("div", { className: "truncate text-[11px] uppercase tracking-[0.18em] text-muted-foreground", children: subtitle }) : null
|
|
20
|
+
] }) : null
|
|
21
|
+
] }),
|
|
22
|
+
children ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children }) : null,
|
|
23
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center justify-end gap-2", children: trailing })
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
TopBar.displayName = "TopBar";
|
|
29
|
+
export {
|
|
30
|
+
TopBar
|
|
31
|
+
};
|