@vvedantb/ui 0.17.0 → 0.18.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 +253 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/compat-shadcn-rgb.css +6 -0
- package/tokens/src/css/markdown.css +318 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { useCn } from "../ui/ui-config";
|
|
3
|
+
|
|
4
|
+
interface ProviderIconProps extends Omit<ComponentProps<"svg">, "children"> {
|
|
5
|
+
provider: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders the brand icon for an AI provider.
|
|
11
|
+
* Supports "claude" (Claude sparkle mark), "codex" (OpenAI mark), "opencode" (OpenCode mark), and "cursor" (Cursor mark).
|
|
12
|
+
*/
|
|
13
|
+
export function ProviderIcon({
|
|
14
|
+
provider,
|
|
15
|
+
size = 14,
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: ProviderIconProps) {
|
|
19
|
+
const shared = { size, className, ...props };
|
|
20
|
+
switch (provider) {
|
|
21
|
+
case "claude":
|
|
22
|
+
return <ClaudeMark {...shared} />;
|
|
23
|
+
case "codex":
|
|
24
|
+
return <OpenAIMark {...shared} />;
|
|
25
|
+
case "opencode":
|
|
26
|
+
return <OpencodeMark {...shared} />;
|
|
27
|
+
case "cursor":
|
|
28
|
+
return <CursorMark {...shared} />;
|
|
29
|
+
default:
|
|
30
|
+
return <ClaudeMark {...shared} />;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ClaudeMark({
|
|
35
|
+
size = 14,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: Omit<ProviderIconProps, "provider">) {
|
|
39
|
+
const merge = useCn();
|
|
40
|
+
return (
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
width={size}
|
|
44
|
+
height={size}
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
fill="#D97757"
|
|
47
|
+
className={merge("shrink-0", className)}
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
|
|
52
|
+
</svg>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** OpenAI mark — path from svgl `openai.svg` / `openai_dark.svg` (current B&W brand). */
|
|
57
|
+
function OpenAIMark({
|
|
58
|
+
size = 14,
|
|
59
|
+
className,
|
|
60
|
+
...props
|
|
61
|
+
}: Omit<ProviderIconProps, "provider">) {
|
|
62
|
+
const merge = useCn();
|
|
63
|
+
return (
|
|
64
|
+
<svg
|
|
65
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
66
|
+
width={size}
|
|
67
|
+
height={size}
|
|
68
|
+
viewBox="0 0 256 260"
|
|
69
|
+
className={merge("shrink-0 text-black dark:text-white", className)}
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
<path
|
|
74
|
+
fill="currentColor"
|
|
75
|
+
d="M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z"
|
|
76
|
+
/>
|
|
77
|
+
</svg>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function OpencodeMark({
|
|
82
|
+
size = 14,
|
|
83
|
+
className,
|
|
84
|
+
...props
|
|
85
|
+
}: Omit<ProviderIconProps, "provider">) {
|
|
86
|
+
const merge = useCn();
|
|
87
|
+
return (
|
|
88
|
+
<svg
|
|
89
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
90
|
+
width={size}
|
|
91
|
+
height={size}
|
|
92
|
+
viewBox="0 0 512 512"
|
|
93
|
+
fill="none"
|
|
94
|
+
className={merge("shrink-0", className)}
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
{...props}
|
|
97
|
+
>
|
|
98
|
+
<rect width="512" height="512" fill="#FDFCFC" />
|
|
99
|
+
<path d="M320 224V352H192V224H320Z" fill="#E6E5E6" />
|
|
100
|
+
<path
|
|
101
|
+
fillRule="evenodd"
|
|
102
|
+
clipRule="evenodd"
|
|
103
|
+
d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z"
|
|
104
|
+
fill="#17181C"
|
|
105
|
+
/>
|
|
106
|
+
</svg>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Cursor glyph — path from svgl `cursor_light.svg` / `cursor_dark.svg`. */
|
|
111
|
+
function CursorMark({
|
|
112
|
+
size = 14,
|
|
113
|
+
className,
|
|
114
|
+
...props
|
|
115
|
+
}: Omit<ProviderIconProps, "provider">) {
|
|
116
|
+
const merge = useCn();
|
|
117
|
+
return (
|
|
118
|
+
<svg
|
|
119
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
+
width={size}
|
|
121
|
+
height={size}
|
|
122
|
+
viewBox="0 0 466.73 532.09"
|
|
123
|
+
className={merge("shrink-0 text-[#26251E] dark:text-[#EDECEC]", className)}
|
|
124
|
+
aria-hidden="true"
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
<path
|
|
128
|
+
fill="currentColor"
|
|
129
|
+
d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"
|
|
130
|
+
/>
|
|
131
|
+
</svg>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
type ComponentProps,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
4
10
|
import { motion, AnimatePresence } from "motion/react";
|
|
5
11
|
import { cn } from "../utils/cn";
|
|
6
12
|
import {
|
|
@@ -9,10 +15,17 @@ import {
|
|
|
9
15
|
} from "../ui/surface-classes";
|
|
10
16
|
import { Button } from "../ui/button";
|
|
11
17
|
import { Badge } from "../ui/badge";
|
|
18
|
+
import {
|
|
19
|
+
Collapsible,
|
|
20
|
+
CollapsibleContent,
|
|
21
|
+
CollapsibleTrigger,
|
|
22
|
+
} from "../ui/collapsible";
|
|
12
23
|
import { ScrollArea } from "../ui/scroll-area";
|
|
13
24
|
import { Textarea } from "../ui/textarea";
|
|
25
|
+
import { useCn, useUiConfig } from "../ui/ui-config";
|
|
14
26
|
import {
|
|
15
27
|
IconChevronDown,
|
|
28
|
+
IconChevronRight,
|
|
16
29
|
IconEdit,
|
|
17
30
|
IconTrash,
|
|
18
31
|
IconCheck,
|
|
@@ -20,13 +33,65 @@ import {
|
|
|
20
33
|
IconStack2,
|
|
21
34
|
} from "@tabler/icons-react";
|
|
22
35
|
|
|
36
|
+
// One Queue family, used in one of two ways:
|
|
37
|
+
// - The queued-messages widget: `Queue` with `count`, `QueueHeader`,
|
|
38
|
+
// `QueueList` and `QueueItem` with `text`. It animates with `motion.*`.
|
|
39
|
+
// - Composable parts (the AI Elements `Queue`): `Queue` without `count`, the
|
|
40
|
+
// `QueueSection*` parts, `QueueList`, `QueueItem` with children and the
|
|
41
|
+
// `QueueItem*` parts. Plain elements only, no `motion.*`, so it renders
|
|
42
|
+
// under a strict `LazyMotion`.
|
|
43
|
+
// `QueueList` inside a composable `Queue` is the plain list; anywhere else it
|
|
44
|
+
// is the widget's animated list.
|
|
45
|
+
//
|
|
46
|
+
// The widget keeps its own markup: its rows, actions and list have other
|
|
47
|
+
// elements and classes than the parts, so building it on them would change
|
|
48
|
+
// its DOM.
|
|
49
|
+
|
|
50
|
+
/** `UiConfig.Queue`: `classes.sectionTrigger` replaces the section header's classes. */
|
|
51
|
+
export interface QueueConfig {
|
|
52
|
+
classes?: {
|
|
53
|
+
sectionTrigger?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True inside a composable `Queue` (one without `count`). */
|
|
58
|
+
const QueuePartsContext = createContext(false);
|
|
59
|
+
|
|
60
|
+
/** The queued-messages widget: animates in while `count > 0`. */
|
|
23
61
|
export interface QueueProps {
|
|
24
62
|
className?: string;
|
|
25
63
|
children?: ReactNode;
|
|
26
64
|
count: number;
|
|
27
65
|
}
|
|
28
66
|
|
|
29
|
-
|
|
67
|
+
/** The composable root: a plain container for `QueueSection`s. */
|
|
68
|
+
export type QueueDivProps = ComponentProps<"div"> & { count?: never };
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* With `count`, the queued-messages widget. Without it, the root container
|
|
72
|
+
* of a collapsible queue of pending items.
|
|
73
|
+
*/
|
|
74
|
+
export function Queue(props: QueueProps | QueueDivProps) {
|
|
75
|
+
if (props.count === undefined) return <QueueRoot {...props} />;
|
|
76
|
+
return <QueueWidget {...props} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function QueueRoot({ className, count: _count, ...props }: QueueDivProps) {
|
|
80
|
+
const merge = useCn();
|
|
81
|
+
return (
|
|
82
|
+
<QueuePartsContext.Provider value>
|
|
83
|
+
<div
|
|
84
|
+
className={merge(
|
|
85
|
+
"flex flex-col rounded-xl border border-border bg-card/40 px-2.5 py-2",
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
</QueuePartsContext.Provider>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function QueueWidget({ className, children, count }: QueueProps) {
|
|
30
95
|
return (
|
|
31
96
|
<AnimatePresence initial={false}>
|
|
32
97
|
{count > 0 ? (
|
|
@@ -52,6 +117,7 @@ export interface QueueHeaderProps {
|
|
|
52
117
|
className?: string;
|
|
53
118
|
}
|
|
54
119
|
|
|
120
|
+
/** The widget's header: a toggle with the message count. */
|
|
55
121
|
export function QueueHeader({
|
|
56
122
|
count,
|
|
57
123
|
isOpen,
|
|
@@ -92,16 +158,41 @@ export function QueueHeader({
|
|
|
92
158
|
);
|
|
93
159
|
}
|
|
94
160
|
|
|
95
|
-
export interface QueueListProps {
|
|
96
|
-
|
|
97
|
-
children?: ReactNode;
|
|
161
|
+
export interface QueueListProps extends ComponentProps<"div"> {
|
|
162
|
+
/** Widget list only: animates the list in and out. Default `true`. */
|
|
98
163
|
open?: boolean;
|
|
99
164
|
}
|
|
100
165
|
|
|
101
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Inside a composable `Queue`, a scrollable list capped at `max-h-48`.
|
|
168
|
+
* Anywhere else, the widget's animated list capped at 240px.
|
|
169
|
+
*/
|
|
170
|
+
export function QueueList({ open = true, ...props }: QueueListProps) {
|
|
171
|
+
const inParts = useContext(QueuePartsContext);
|
|
172
|
+
if (inParts) return <QueuePartsList {...props} />;
|
|
173
|
+
return <QueueWidgetList open={open} {...props} />;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function QueuePartsList({
|
|
177
|
+
children,
|
|
178
|
+
className,
|
|
179
|
+
...props
|
|
180
|
+
}: ComponentProps<"div">) {
|
|
181
|
+
const merge = useCn();
|
|
182
|
+
return (
|
|
183
|
+
<div className={merge("mt-1", className)} {...props}>
|
|
184
|
+
<div className="max-h-48 overflow-y-auto">
|
|
185
|
+
<ul className="flex flex-col gap-1">{children}</ul>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function QueueWidgetList({
|
|
102
192
|
className,
|
|
103
193
|
children,
|
|
104
|
-
open
|
|
194
|
+
open,
|
|
195
|
+
...props
|
|
105
196
|
}: QueueListProps) {
|
|
106
197
|
return (
|
|
107
198
|
<AnimatePresence initial={false}>
|
|
@@ -114,7 +205,10 @@ export function QueueList({
|
|
|
114
205
|
transition={{ type: "spring", bounce: 0, duration: 0.35 }}
|
|
115
206
|
className="overflow-hidden"
|
|
116
207
|
>
|
|
117
|
-
<div
|
|
208
|
+
<div
|
|
209
|
+
className={cn(flatPanelSubtleClass, "rounded-b-2xl", className)}
|
|
210
|
+
{...props}
|
|
211
|
+
>
|
|
118
212
|
<ScrollArea className="max-h-[240px]">
|
|
119
213
|
<div className="p-2 space-y-1.5">{children}</div>
|
|
120
214
|
</ScrollArea>
|
|
@@ -125,6 +219,7 @@ export function QueueList({
|
|
|
125
219
|
);
|
|
126
220
|
}
|
|
127
221
|
|
|
222
|
+
/** A widget row: one queued message with edit and remove. */
|
|
128
223
|
export interface QueueItemProps {
|
|
129
224
|
text: string;
|
|
130
225
|
model?: string;
|
|
@@ -136,7 +231,29 @@ export interface QueueItemProps {
|
|
|
136
231
|
className?: string;
|
|
137
232
|
}
|
|
138
233
|
|
|
139
|
-
|
|
234
|
+
/** A composable row: an `li` for your own content and the `QueueItem*` parts. */
|
|
235
|
+
export type QueueItemLiProps = ComponentProps<"li"> & { text?: never };
|
|
236
|
+
|
|
237
|
+
/** With `text`, a widget row. Without it, a plain `li` row. */
|
|
238
|
+
export function QueueItem(props: QueueItemProps | QueueItemLiProps) {
|
|
239
|
+
if (props.text === undefined) return <QueuePartsItem {...props} />;
|
|
240
|
+
return <QueueWidgetItem {...props} />;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function QueuePartsItem({ className, text: _text, ...props }: QueueItemLiProps) {
|
|
244
|
+
const merge = useCn();
|
|
245
|
+
return (
|
|
246
|
+
<li
|
|
247
|
+
className={merge(
|
|
248
|
+
"group flex flex-col gap-1 rounded-lg px-2 py-1.5 text-sm transition-colors hover:bg-surface-secondary/50",
|
|
249
|
+
className,
|
|
250
|
+
)}
|
|
251
|
+
{...props}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function QueueWidgetItem({
|
|
140
257
|
text,
|
|
141
258
|
model,
|
|
142
259
|
personalityName,
|
|
@@ -249,3 +366,170 @@ export function QueueItem({
|
|
|
249
366
|
</div>
|
|
250
367
|
);
|
|
251
368
|
}
|
|
369
|
+
|
|
370
|
+
export type QueueSectionProps = ComponentProps<typeof Collapsible>;
|
|
371
|
+
|
|
372
|
+
export const QueueSection = ({
|
|
373
|
+
className,
|
|
374
|
+
defaultOpen = true,
|
|
375
|
+
...props
|
|
376
|
+
}: QueueSectionProps) => {
|
|
377
|
+
const merge = useCn();
|
|
378
|
+
return <Collapsible className={merge(className)} defaultOpen={defaultOpen} {...props} />;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export type QueueSectionTriggerProps = ComponentProps<"button">;
|
|
382
|
+
|
|
383
|
+
export const QueueSectionTrigger = ({
|
|
384
|
+
children,
|
|
385
|
+
className,
|
|
386
|
+
...props
|
|
387
|
+
}: QueueSectionTriggerProps) => {
|
|
388
|
+
const sectionTrigger = useUiConfig("Queue")?.classes?.sectionTrigger;
|
|
389
|
+
const merge = useCn();
|
|
390
|
+
return (
|
|
391
|
+
<CollapsibleTrigger asChild>
|
|
392
|
+
<button
|
|
393
|
+
className={merge(
|
|
394
|
+
sectionTrigger ??
|
|
395
|
+
"group flex w-full items-center rounded-md px-1 py-1 text-left text-xs font-medium text-muted-foreground transition-[color,scale] duration-150 ease-out hover:text-foreground active:scale-[0.99]",
|
|
396
|
+
className,
|
|
397
|
+
)}
|
|
398
|
+
type="button"
|
|
399
|
+
{...props}
|
|
400
|
+
>
|
|
401
|
+
{children}
|
|
402
|
+
</button>
|
|
403
|
+
</CollapsibleTrigger>
|
|
404
|
+
);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export type QueueSectionLabelProps = ComponentProps<"span"> & {
|
|
408
|
+
count?: number;
|
|
409
|
+
label: string;
|
|
410
|
+
icon?: ReactNode;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const QueueSectionLabel = ({
|
|
414
|
+
count,
|
|
415
|
+
label,
|
|
416
|
+
icon,
|
|
417
|
+
className,
|
|
418
|
+
...props
|
|
419
|
+
}: QueueSectionLabelProps) => {
|
|
420
|
+
const merge = useCn();
|
|
421
|
+
return (
|
|
422
|
+
<span className={merge("flex items-center gap-1.5", className)} {...props}>
|
|
423
|
+
{icon}
|
|
424
|
+
<span>
|
|
425
|
+
{count} {label}
|
|
426
|
+
</span>
|
|
427
|
+
{/* After the text, pointing right, turning 90° when the section is open. */}
|
|
428
|
+
<IconChevronRight className="-ml-0.5 size-3.5 opacity-70 transition-transform group-data-[state=open]:rotate-90" />
|
|
429
|
+
</span>
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export type QueueSectionContentProps = ComponentProps<
|
|
434
|
+
typeof CollapsibleContent
|
|
435
|
+
>;
|
|
436
|
+
|
|
437
|
+
export const QueueSectionContent = ({
|
|
438
|
+
className,
|
|
439
|
+
...props
|
|
440
|
+
}: QueueSectionContentProps) => {
|
|
441
|
+
const merge = useCn();
|
|
442
|
+
return <CollapsibleContent className={merge(className)} {...props} />;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
export type QueueItemIndicatorProps = ComponentProps<"span"> & {
|
|
446
|
+
completed?: boolean;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
export const QueueItemIndicator = ({
|
|
450
|
+
completed = false,
|
|
451
|
+
className,
|
|
452
|
+
...props
|
|
453
|
+
}: QueueItemIndicatorProps) => {
|
|
454
|
+
const merge = useCn();
|
|
455
|
+
return (
|
|
456
|
+
<span
|
|
457
|
+
className={merge(
|
|
458
|
+
"mt-1 inline-block size-2.5 shrink-0 rounded-full border",
|
|
459
|
+
completed
|
|
460
|
+
? "border-muted-foreground/20 bg-muted-foreground/10"
|
|
461
|
+
: "border-muted-foreground/45",
|
|
462
|
+
className,
|
|
463
|
+
)}
|
|
464
|
+
{...props}
|
|
465
|
+
/>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export type QueueItemContentProps = ComponentProps<"span"> & {
|
|
470
|
+
completed?: boolean;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
export const QueueItemContent = ({
|
|
474
|
+
completed = false,
|
|
475
|
+
className,
|
|
476
|
+
...props
|
|
477
|
+
}: QueueItemContentProps) => {
|
|
478
|
+
const merge = useCn();
|
|
479
|
+
return (
|
|
480
|
+
<span
|
|
481
|
+
className={merge(
|
|
482
|
+
"line-clamp-2 min-w-0 grow wrap-break-word leading-snug",
|
|
483
|
+
completed
|
|
484
|
+
? "text-muted-foreground/50 line-through"
|
|
485
|
+
: "text-foreground/90",
|
|
486
|
+
className,
|
|
487
|
+
)}
|
|
488
|
+
{...props}
|
|
489
|
+
/>
|
|
490
|
+
);
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
export type QueueItemActionsProps = ComponentProps<"div">;
|
|
494
|
+
|
|
495
|
+
export const QueueItemActions = ({
|
|
496
|
+
className,
|
|
497
|
+
...props
|
|
498
|
+
}: QueueItemActionsProps) => {
|
|
499
|
+
const merge = useCn();
|
|
500
|
+
return (
|
|
501
|
+
<div
|
|
502
|
+
className={merge(
|
|
503
|
+
// `reveal-on-hover`: hidden until row hover on pointer devices, always
|
|
504
|
+
// visible below `sm` where there is no hover.
|
|
505
|
+
"flex shrink-0 items-start gap-0.5 reveal-on-hover transition-opacity",
|
|
506
|
+
className,
|
|
507
|
+
)}
|
|
508
|
+
{...props}
|
|
509
|
+
/>
|
|
510
|
+
);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export type QueueItemActionProps = Omit<
|
|
514
|
+
ComponentProps<typeof Button>,
|
|
515
|
+
"variant" | "size"
|
|
516
|
+
>;
|
|
517
|
+
|
|
518
|
+
export const QueueItemAction = ({
|
|
519
|
+
className,
|
|
520
|
+
...props
|
|
521
|
+
}: QueueItemActionProps) => {
|
|
522
|
+
const merge = useCn();
|
|
523
|
+
return (
|
|
524
|
+
<Button
|
|
525
|
+
className={merge(
|
|
526
|
+
"size-auto rounded-md p-1 text-muted-foreground hover:bg-surface-secondary hover:text-foreground",
|
|
527
|
+
className,
|
|
528
|
+
)}
|
|
529
|
+
size="icon"
|
|
530
|
+
type="button"
|
|
531
|
+
variant="ghost"
|
|
532
|
+
{...props}
|
|
533
|
+
/>
|
|
534
|
+
);
|
|
535
|
+
};
|
|
@@ -2,18 +2,54 @@
|
|
|
2
2
|
import { type CSSProperties, type ElementType } from "react";
|
|
3
3
|
import { motion } from "motion/react";
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
|
+
import { useAnimOffscreenRef } from "../utils/runtimeVisibility";
|
|
6
|
+
import { useCn, useUiConfig } from "../ui/ui-config";
|
|
5
7
|
interface ShimmerCSSProperties extends CSSProperties {
|
|
6
8
|
"--spread"?: string;
|
|
7
9
|
"--bg"?: string;
|
|
8
10
|
}
|
|
11
|
+
/** Custom properties the `mask` sweep reads from the app's `.shimmer-*` CSS. */
|
|
12
|
+
interface ShimmerMaskStyle extends CSSProperties {
|
|
13
|
+
"--shimmer-spread": string;
|
|
14
|
+
"--shimmer-duration": string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `UiConfig.Shimmer`. `mode: "mask"` sweeps a masked copy of the text on the
|
|
18
|
+
* compositor (no repaint per frame, paused offscreen) instead of animating a
|
|
19
|
+
* `background-position`. The app defines the `overlay`, `band` and `copy`
|
|
20
|
+
* classes (default `shimmer-overlay`, `shimmer-band`, `shimmer-copy`).
|
|
21
|
+
*/
|
|
22
|
+
export interface ShimmerConfig {
|
|
23
|
+
mode?: "gradient" | "mask";
|
|
24
|
+
/** Default element. */
|
|
25
|
+
as?: ElementType;
|
|
26
|
+
classes?: {
|
|
27
|
+
/** Root element in `mask` mode. */
|
|
28
|
+
base?: string;
|
|
29
|
+
overlay?: string;
|
|
30
|
+
band?: string;
|
|
31
|
+
copy?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
9
34
|
interface ShimmerProps {
|
|
10
35
|
children: string;
|
|
11
36
|
as?: ElementType;
|
|
12
37
|
className?: string;
|
|
13
38
|
duration?: number;
|
|
14
39
|
spread?: number;
|
|
40
|
+
/** `"mask"`: compositor sweep, see `ShimmerConfig`. Default `"gradient"`. */
|
|
41
|
+
mode?: "gradient" | "mask";
|
|
42
|
+
}
|
|
43
|
+
function Shimmer({ mode, as, ...props }: ShimmerProps) {
|
|
44
|
+
const config = useUiConfig("Shimmer");
|
|
45
|
+
const Component = as ?? config?.as ?? "span";
|
|
46
|
+
return (mode ?? config?.mode) === "mask" ? (
|
|
47
|
+
<MaskShimmer as={Component} {...props} />
|
|
48
|
+
) : (
|
|
49
|
+
<GradientShimmer as={Component} {...props} />
|
|
50
|
+
);
|
|
15
51
|
}
|
|
16
|
-
function
|
|
52
|
+
function GradientShimmer({
|
|
17
53
|
children,
|
|
18
54
|
as: Component = "span",
|
|
19
55
|
className,
|
|
@@ -53,4 +89,39 @@ function Shimmer({
|
|
|
53
89
|
</MotionComponent>
|
|
54
90
|
);
|
|
55
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* The text renders once in the base colour; a copy in the highlight colour
|
|
94
|
+
* sits over it, masked to a soft band. The band translates one way while the
|
|
95
|
+
* copy inside it translates the other by the same amount, so only the mask
|
|
96
|
+
* moves: two transform animations, no repaint. The copy is `aria-hidden`.
|
|
97
|
+
*/
|
|
98
|
+
function MaskShimmer({
|
|
99
|
+
children,
|
|
100
|
+
as: Component = "span",
|
|
101
|
+
className,
|
|
102
|
+
duration = 2,
|
|
103
|
+
spread = 2,
|
|
104
|
+
}: ShimmerProps) {
|
|
105
|
+
const classes = useUiConfig("Shimmer")?.classes;
|
|
106
|
+
const merge = useCn();
|
|
107
|
+
const setOffscreen = useAnimOffscreenRef<HTMLElement>();
|
|
108
|
+
const style: ShimmerMaskStyle = {
|
|
109
|
+
"--shimmer-spread": `${children.length * spread}px`,
|
|
110
|
+
"--shimmer-duration": `${duration}s`,
|
|
111
|
+
};
|
|
112
|
+
return (
|
|
113
|
+
<Component
|
|
114
|
+
ref={setOffscreen}
|
|
115
|
+
className={merge(classes?.base ?? "relative inline-block text-muted-foreground", className)}
|
|
116
|
+
style={style}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
<span aria-hidden="true" data-anim-chrome="" className={classes?.overlay ?? "shimmer-overlay"}>
|
|
120
|
+
<span className={classes?.band ?? "shimmer-band"}>
|
|
121
|
+
<span className={classes?.copy ?? "shimmer-copy"}>{children}</span>
|
|
122
|
+
</span>
|
|
123
|
+
</span>
|
|
124
|
+
</Component>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
56
127
|
export { Shimmer, type ShimmerProps };
|