@asharca/ui 0.2.1 → 0.2.2
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 +54 -3
- package/dist/Accordion.d.ts +5 -0
- package/dist/Accordion.js +29 -0
- package/dist/Avatar.d.ts +4 -0
- package/dist/Avatar.js +27 -0
- package/dist/ChatComposerToolbar.d.ts +26 -0
- package/dist/ChatComposerToolbar.js +24 -0
- package/dist/ChatThread.d.ts +1 -0
- package/dist/ChatThread.js +3 -3
- package/dist/Controls.d.ts +7 -2
- package/dist/Controls.js +10 -1
- package/dist/ConversationSidebar.d.ts +4 -1
- package/dist/ConversationSidebar.js +62 -5
- package/dist/Feedback.d.ts +4 -0
- package/dist/Feedback.js +8 -0
- package/dist/Layout.d.ts +6 -1
- package/dist/Layout.js +43 -2
- package/dist/Navigation.d.ts +5 -0
- package/dist/Navigation.js +15 -1
- package/dist/Overlays.d.ts +9 -1
- package/dist/Overlays.js +21 -1
- package/dist/WorkspaceSidebar.d.ts +64 -0
- package/dist/WorkspaceSidebar.js +24 -0
- package/dist/WorkspaceTabBar.js +7 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +25 -2
- package/src/styles.css +185 -0
package/README.md
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
# @asharca/ui
|
|
2
2
|
|
|
3
|
+
`ChatComposerToolbar` (`@asharca/ui/chat-composer-toolbar`) provides a unified
|
|
4
|
+
tool menu and configurable, ordered shortcuts. Pass `tools`, `pinnedIds`, and
|
|
5
|
+
`onPinnedIdsChange`; the consuming app owns actions and preference persistence.
|
|
6
|
+
Use it in `ChatThread.composerTools` with `showAttachmentPicker={false}` to
|
|
7
|
+
replace the default attachment button. The default remains unchanged.
|
|
8
|
+
|
|
3
9
|
Reusable React controls, chat thread, conversation sidebar, and responsive shell extracted from ToolPlane. Routing, persistence, authentication, and API handlers stay in the host application.
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
|
-
pnpm add @asharca/ui
|
|
9
|
-
# or: npm install @asharca/ui
|
|
14
|
+
pnpm add @asharca/ui
|
|
15
|
+
# or: npm install @asharca/ui
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
pnpm installs missing peer dependencies by default. If automatic peer installation
|
|
19
|
+
is disabled or your package manager leaves peers missing, install them explicitly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @asharca/ui @assistant-ui/react@0.15.18 react@^19 react-dom@^19 tailwindcss@^4
|
|
10
23
|
```
|
|
11
24
|
|
|
25
|
+
For version conflicts, first check that your application and other dependencies
|
|
26
|
+
support these versions. Installing the package does not configure your CSS build.
|
|
27
|
+
|
|
12
28
|
The package uses React 19 and Tailwind CSS 4. `ChatThread` additionally accepts an assistant-ui `AssistantRuntime`, so transport and persistence stay in the host application.
|
|
13
29
|
|
|
14
30
|
The runtime peer is pinned to `@assistant-ui/react@0.15.18`, paired with
|
|
@@ -55,9 +71,24 @@ export function Toolbar() {
|
|
|
55
71
|
|
|
56
72
|
## Modules
|
|
57
73
|
|
|
74
|
+
- `@asharca/ui/workspace-sidebar` provides `WorkspaceSidebar`: controlled `collapsed`
|
|
75
|
+
and `mobileOpen` states, `items`, `activeId`, `onSelect`, brand/workspace content
|
|
76
|
+
and a `footer` slot. Items accept `icon`, `label`, `badge`, and `disabled`.
|
|
77
|
+
Widths use `--workspace-sidebar-width` and `--workspace-sidebar-collapsed-width`.
|
|
78
|
+
The mobile breakpoint is 850px. Hosts own routing, persistence, the open trigger,
|
|
79
|
+
scrim, focus trapping/restoration, and background inertness for modal drawers;
|
|
80
|
+
`mobileCloseRef` exposes the built-in close button for focus management.
|
|
81
|
+
|
|
58
82
|
- `@asharca/ui/controls` — buttons and native form controls.
|
|
83
|
+
- `@asharca/ui/controls` also provides `Switch`, native `Slider`, and an additive `outline` button variant.
|
|
84
|
+
- `@asharca/ui/avatar` — `Avatar`, `AvatarImage`, and `AvatarFallback`.
|
|
85
|
+
- `@asharca/ui/workspace-tab-bar` — ToolPlane's `WorkspaceTabBar` with host-controlled selection, closing, pinning, drag reordering, and new-window callbacks. The showcase wires these actions to its sidebar and pages; route state stays in the host application.
|
|
86
|
+
- `@asharca/ui/accordion` — `Accordion`, `AccordionItem`, `AccordionTrigger`, and `AccordionContent`.
|
|
87
|
+
- `@asharca/ui/navigation` also provides Radix-backed `Tabs`, `TabsList`, `TabsTrigger`, and `TabsContent` with keyboard navigation. Existing `Tab` / `TabList` exports are unchanged.
|
|
88
|
+
- `@asharca/ui/feedback` also provides native `Progress` and decorative `Skeleton`. Give progress bars and sliders an accessible label; label switches via `htmlFor` or `aria-label`.
|
|
89
|
+
- `@asharca/ui/overlays` also provides `DropdownMenu`, its trigger, portal, content, item, group, label, and separator.
|
|
59
90
|
- `@asharca/ui/forms` — submit, confirm-submit, and copy actions.
|
|
60
|
-
- `@asharca/ui/layout` — page, header, toolbar, section, panel, card, empty state, entity, and data table.
|
|
91
|
+
- `@asharca/ui/layout` — page, header, toolbar, section, panel, card, empty state, entity, and data table. `DataTable` supports controlled multi-select with `selectable`, `rowIds`, `selectedRowIds`, and `onSelectedRowIdsChange`.
|
|
61
92
|
- `@asharca/ui/navigation` — tabs, chips, and pagination layout.
|
|
62
93
|
- `@asharca/ui/feedback` — badges, status, alerts, and spinners.
|
|
63
94
|
- `@asharca/ui/dialog` and `@asharca/ui/overlays` — Dialog, Popover, Tooltip, Context Menu, and Hover Card primitives.
|
|
@@ -115,8 +146,28 @@ Build the runtime with AI SDK, assistant-ui local runtime, or another adapter in
|
|
|
115
146
|
|
|
116
147
|
`ConversationSidebar` conversations may also provide optional `meta` content and a `deleting` state, so hosts can keep domain badges and show async delete progress without replacing the list layout.
|
|
117
148
|
|
|
149
|
+
Pass `onConversationOrderChange(groupId, conversationIds)` to enable same-group
|
|
150
|
+
conversation drag ordering and accessible move-up/down buttons. The callback
|
|
151
|
+
returns the full group order, including items hidden by search. Hosts own the
|
|
152
|
+
reordered state and persistence; selection is unchanged. Disabled/deleting
|
|
153
|
+
conversations cannot be moved. Omitting the callback preserves the default list.
|
|
154
|
+
|
|
118
155
|
Theme defaults are scoped to package component roots. Override them with HSL-channel variables such as `--toolplane-ui-background`, `--toolplane-ui-foreground`, and `--toolplane-ui-brand`. The older `--chat-ui-*` variables remain supported; `--chat-ui-sidebar-width` and `--chat-ui-right-panel-width` still control chat layout. Add a `.dark` class to an ancestor, or `data-theme="dark"` to a component, to use the dark defaults.
|
|
119
156
|
|
|
157
|
+
## Component workbench
|
|
158
|
+
|
|
159
|
+
Run `pnpm dev` and open the local URL printed by Vite. The standalone showcase
|
|
160
|
+
includes interactive controls, forms, tables, overlays, light/dark themes, and
|
|
161
|
+
a local chat demo (no AI service or credentials required). Demo data is kept in
|
|
162
|
+
memory and resets on reload.
|
|
163
|
+
|
|
164
|
+
`pnpm build:showcase` type-checks and builds the site into `showcase-dist/`.
|
|
165
|
+
The showcase is development-only and is not included in the npm package.
|
|
166
|
+
It includes nine interactive examples with copyable code snippets. Profile photos
|
|
167
|
+
in `showcase/public/avatars` are demo assets from Unsplash (photo IDs
|
|
168
|
+
`1494790108377-be9c29b29330`, `1506794778202-cad84cf45f1d`, and
|
|
169
|
+
`1534528741775-53994a69daeb`); member names and emails are fictional.
|
|
170
|
+
|
|
120
171
|
## Build and pack
|
|
121
172
|
|
|
122
173
|
```bash
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui';
|
|
2
|
+
export declare const Accordion: import("react").ForwardRefExoticComponent<(AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps) & import("react").RefAttributes<HTMLDivElement>>;
|
|
3
|
+
export declare const AccordionItem: import("react").ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export declare const AccordionTrigger: import("react").ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
5
|
+
export declare const AccordionContent: import("react").ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
+
import { forwardRef } from 'react';
|
|
15
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui';
|
|
16
|
+
import { ChevronDown } from 'lucide-react';
|
|
17
|
+
export const Accordion = AccordionPrimitive.Root;
|
|
18
|
+
export const AccordionItem = forwardRef(function AccordionItem(_a, ref) {
|
|
19
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
20
|
+
return _jsx(AccordionPrimitive.Item, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "accordion-item", className: `ui-accordion-item ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
21
|
+
});
|
|
22
|
+
export const AccordionTrigger = forwardRef(function AccordionTrigger(_a, ref) {
|
|
23
|
+
var { children, className } = _a, props = __rest(_a, ["children", "className"]);
|
|
24
|
+
return _jsx(AccordionPrimitive.Header, { className: "ui-accordion-header", children: _jsxs(AccordionPrimitive.Trigger, Object.assign({}, props, { ref: ref, className: `ui-accordion-trigger ${className !== null && className !== void 0 ? className : ''}`.trim(), children: [children, _jsx(ChevronDown, { "aria-hidden": "true", className: "ui-accordion-chevron" })] })) });
|
|
25
|
+
});
|
|
26
|
+
export const AccordionContent = forwardRef(function AccordionContent(_a, ref) {
|
|
27
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
28
|
+
return _jsx(AccordionPrimitive.Content, Object.assign({}, props, { ref: ref, className: `ui-accordion-content ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
29
|
+
});
|
package/dist/Avatar.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Avatar as AvatarPrimitive } from 'radix-ui';
|
|
2
|
+
export declare const Avatar: import("react").ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>;
|
|
3
|
+
export declare const AvatarImage: import("react").ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & import("react").RefAttributes<HTMLImageElement>, "ref"> & import("react").RefAttributes<HTMLImageElement>>;
|
|
4
|
+
export declare const AvatarFallback: import("react").ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>;
|
package/dist/Avatar.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
import { forwardRef } from 'react';
|
|
15
|
+
import { Avatar as AvatarPrimitive } from 'radix-ui';
|
|
16
|
+
export const Avatar = forwardRef(function Avatar(_a, ref) {
|
|
17
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
18
|
+
return _jsx(AvatarPrimitive.Root, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "avatar", className: `ui-avatar ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
19
|
+
});
|
|
20
|
+
export const AvatarImage = forwardRef(function AvatarImage(_a, ref) {
|
|
21
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
22
|
+
return _jsx(AvatarPrimitive.Image, Object.assign({}, props, { ref: ref, className: `ui-avatar-image ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
23
|
+
});
|
|
24
|
+
export const AvatarFallback = forwardRef(function AvatarFallback(_a, ref) {
|
|
25
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
26
|
+
return _jsx(AvatarPrimitive.Fallback, Object.assign({}, props, { ref: ref, className: `ui-avatar-fallback ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
27
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type ChatComposerTool = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon: ReactNode;
|
|
6
|
+
description?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
pressed?: boolean;
|
|
9
|
+
onSelect: () => void;
|
|
10
|
+
};
|
|
11
|
+
export type ChatComposerToolbarLabels = {
|
|
12
|
+
open: string;
|
|
13
|
+
customize: string;
|
|
14
|
+
close: string;
|
|
15
|
+
reset: string;
|
|
16
|
+
moveUp: (label: string) => string;
|
|
17
|
+
moveDown: (label: string) => string;
|
|
18
|
+
};
|
|
19
|
+
export type ChatComposerToolbarProps = {
|
|
20
|
+
tools: readonly ChatComposerTool[];
|
|
21
|
+
pinnedIds: readonly string[];
|
|
22
|
+
onPinnedIdsChange: (ids: string[]) => void;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
labels?: Partial<ChatComposerToolbarLabels>;
|
|
25
|
+
};
|
|
26
|
+
export declare function ChatComposerToolbar({ tools, pinnedIds, onPinnedIdsChange, disabled, labels }: ChatComposerToolbarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { ArrowDown, ArrowUp, Plus, RotateCcw, SlidersHorizontal, X } from 'lucide-react';
|
|
5
|
+
import { Button, IconButton } from "./Controls.js";
|
|
6
|
+
import { Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle } from "./Dialog.js";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuTrigger } from "./Overlays.js";
|
|
8
|
+
export function ChatComposerToolbar({ tools, pinnedIds, onPinnedIdsChange, disabled, labels }) {
|
|
9
|
+
const [customizing, setCustomizing] = useState(false);
|
|
10
|
+
const copy = Object.assign({ open: 'Open tools', customize: 'Customize toolbar', close: 'Close', reset: 'Reset toolbar', moveUp: (label) => `Move ${label} up`, moveDown: (label) => `Move ${label} down` }, labels);
|
|
11
|
+
const pinned = [...new Set(pinnedIds)].flatMap((id) => { var _a; return (_a = tools.find((tool) => tool.id === id)) !== null && _a !== void 0 ? _a : []; });
|
|
12
|
+
const ids = pinned.map((tool) => tool.id);
|
|
13
|
+
const unpinned = tools.filter((tool) => !ids.includes(tool.id));
|
|
14
|
+
const move = (index, offset) => {
|
|
15
|
+
const next = [...ids];
|
|
16
|
+
const [id] = next.splice(index, 1);
|
|
17
|
+
next.splice(index + offset, 0, id);
|
|
18
|
+
onPinnedIdsChange(next);
|
|
19
|
+
};
|
|
20
|
+
return _jsxs(_Fragment, { children: [_jsxs("div", { className: "ui-composer-toolbar", "data-chat-ui": "composer-toolbar", children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "ui-composer-tool", "aria-label": copy.open, title: copy.open, disabled: disabled, children: _jsx(Plus, { size: 18 }) }) }), _jsx(DropdownMenuPortal, { children: _jsxs(DropdownMenuContent, { side: "top", align: "start", sideOffset: 8, className: "ui-composer-menu", "aria-label": copy.open, children: [unpinned.map((tool) => _jsxs(DropdownMenuItem, { disabled: disabled || tool.disabled, onSelect: tool.onSelect, children: [tool.icon, _jsxs("span", { children: [tool.label, tool.description && _jsx("small", { children: tool.description })] })] }, tool.id)), unpinned.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: disabled, onSelect: () => setCustomizing(true), children: [_jsx(SlidersHorizontal, { size: 16 }), copy.customize] })] }) })] }), _jsx("div", { className: "ui-composer-shortcuts", children: pinned.map((tool) => _jsx("button", { type: "button", className: "ui-composer-tool", "data-composer-shortcut": tool.id, disabled: disabled || tool.disabled, "aria-label": tool.label, title: tool.label, "aria-pressed": tool.pressed, onClick: tool.onSelect, children: tool.icon }, tool.id)) })] }), _jsx(Dialog, { open: customizing, onOpenChange: setCustomizing, children: _jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogContent, { className: "ui-composer-customize", "aria-describedby": undefined, children: [_jsxs("header", { children: [_jsx(DialogTitle, { children: copy.customize }), _jsx(DialogClose, { asChild: true, children: _jsx(IconButton, { variant: "ghost", label: copy.close, icon: _jsx(X, { size: 16 }) }) })] }), _jsx("div", { children: [...pinned, ...unpinned].map((tool) => {
|
|
21
|
+
const index = ids.indexOf(tool.id);
|
|
22
|
+
return _jsxs("div", { className: "ui-composer-option", children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: index >= 0, disabled: disabled, onChange: (event) => onPinnedIdsChange(event.target.checked ? [...ids, tool.id] : ids.filter((id) => id !== tool.id)) }), tool.icon, _jsx("span", { children: tool.label })] }), index >= 0 && _jsxs(_Fragment, { children: [_jsx(IconButton, { variant: "ghost", label: copy.moveUp(tool.label), icon: _jsx(ArrowUp, { size: 14 }), disabled: disabled || index === 0, onClick: () => move(index, -1) }), _jsx(IconButton, { variant: "ghost", label: copy.moveDown(tool.label), icon: _jsx(ArrowDown, { size: 14 }), disabled: disabled || index === ids.length - 1, onClick: () => move(index, 1) })] })] }, tool.id);
|
|
23
|
+
}) }), _jsx("footer", { children: _jsxs(Button, { variant: "outline", disabled: disabled || !ids.length, onClick: () => onPinnedIdsChange([]), children: [_jsx(RotateCcw, { size: 14 }), copy.reset] }) })] })] }) })] });
|
|
24
|
+
}
|
package/dist/ChatThread.d.ts
CHANGED
package/dist/ChatThread.js
CHANGED
|
@@ -18,7 +18,7 @@ import { code } from '@streamdown/code';
|
|
|
18
18
|
import { Popover } from 'radix-ui';
|
|
19
19
|
import remarkBreaks from 'remark-breaks';
|
|
20
20
|
import { defaultRemarkPlugins } from 'streamdown';
|
|
21
|
-
import { Bot, Box, Brain, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CirclePause, Copy, Globe2, Loader2, Maximize2, Minimize2, Paperclip, Pencil, Plug, Plus, RefreshCw,
|
|
21
|
+
import { Bot, Box, Brain, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CirclePause, Copy, ArrowUp, Globe2, Loader2, Maximize2, Minimize2, Paperclip, Pencil, Plug, Plus, RefreshCw, Split, UserRound, Wrench, X, } from 'lucide-react';
|
|
22
22
|
import { Button, IconButton } from "./Controls.js";
|
|
23
23
|
import { hasMermaidFence } from "./chat-markdown.js";
|
|
24
24
|
const MermaidAssistantText = lazy(() => import("./MermaidAssistantText.js"));
|
|
@@ -259,7 +259,7 @@ function ComposerExpand({ expanded, onToggle }) {
|
|
|
259
259
|
const label = expanded ? labels.restoreComposer : labels.expandComposer;
|
|
260
260
|
return (_jsx("div", { className: "absolute right-px top-px z-10 size-8", children: _jsx("button", { type: "button", onClick: onToggle, "aria-label": label, title: label, "aria-pressed": expanded, className: "pointer-events-none absolute right-1 top-1 flex size-[22px] -translate-y-2.5 translate-x-2.5 rotate-[-8deg] scale-80 items-center justify-center rounded-full bg-transparent text-muted-foreground opacity-0 transition-all duration-300 hover:bg-muted hover:text-foreground focus-visible:pointer-events-auto focus-visible:translate-x-0 focus-visible:translate-y-0 focus-visible:rotate-0 focus-visible:scale-100 focus-visible:opacity-100 group-focus-within/composer:pointer-events-auto group-focus-within/composer:translate-x-0 group-focus-within/composer:translate-y-0 group-focus-within/composer:rotate-0 group-focus-within/composer:scale-100 group-focus-within/composer:opacity-100 group-hover/composer:pointer-events-auto group-hover/composer:translate-x-0 group-hover/composer:translate-y-0 group-hover/composer:rotate-0 group-hover/composer:scale-100 group-hover/composer:opacity-100", children: _jsx(Icon, { className: "size-3" }) }) }));
|
|
261
261
|
}
|
|
262
|
-
function ChatThreadContent({ assistantName, allowAttachments, allowEdit, allowRegenerate, branchNavigation, busy, className, composerEnd, composerStatus, composerTools, components, disabled, emptyState, error, onBranchSelect, onBranchStart, onRegenerateMessage, }) {
|
|
262
|
+
function ChatThreadContent({ assistantName, allowAttachments, allowEdit, allowRegenerate, branchNavigation, busy, className, composerEnd, composerStatus, composerTools, showAttachmentPicker = true, components, disabled, emptyState, error, onBranchSelect, onBranchStart, onRegenerateMessage, }) {
|
|
263
263
|
const labels = useLabels();
|
|
264
264
|
const [composerRows, setComposerRows] = useState(2);
|
|
265
265
|
const composerInputRef = useRef(null);
|
|
@@ -276,7 +276,7 @@ function ChatThreadContent({ assistantName, allowAttachments, allowEdit, allowRe
|
|
|
276
276
|
const blocked = disabled || busy;
|
|
277
277
|
return (_jsxs(ThreadPrimitive.Root, { "data-chat-ui": "chat-thread", className: cx('flex min-h-0 flex-1 flex-col', className), children: [_jsxs(ThreadPrimitive.Viewport, { className: "relative flex min-h-0 flex-1 flex-col overflow-y-auto bg-background", children: [_jsxs("div", { className: "flex-1 py-1.5", children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { className: "flex min-h-full items-center justify-center px-6 pb-24", children: emptyState !== null && emptyState !== void 0 ? emptyState : (_jsxs("div", { className: "max-w-md text-center", children: [_jsx("div", { className: "mx-auto mb-4 flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(Bot, { className: "size-6" }) }), _jsx("h3", { className: "text-lg font-medium text-foreground", children: labels.startConversation })] })) }) }), _jsx("div", { className: "mx-auto flex w-full max-w-[53rem] flex-col gap-0 px-6", children: _jsx(ThreadPrimitive.Messages, { children: ({ message }) => message.role === 'user'
|
|
278
278
|
? (_jsx(UserMessage, { allowEdit: allowEdit, branch: branchByMessageId.get(message.id), busy: blocked, components: components, messageId: message.id, onBranchSelect: onBranchSelect }))
|
|
279
|
-
: (_jsx(AssistantMessage, { agentName: assistantName, allowRegenerate: allowRegenerate, branch: branchByMessageId.get(message.id), busy: blocked, components: components, messageId: message.id, onBranchSelect: onBranchSelect, onBranchStart: onBranchStart, onRegenerateMessage: onRegenerateMessage })) }) })] }), _jsx(ThreadPrimitive.ScrollToBottom, { "aria-label": labels.scrollToLatestMessage, title: labels.scrollToLatestMessage, className: "sticky bottom-3 z-10 mx-auto mb-3 flex size-9 shrink-0 items-center justify-center rounded-full border border-border bg-card text-muted-foreground hover:text-foreground disabled:invisible", children: _jsx(ChevronDown, { className: "size-4" }) })] }), _jsx("div", { className: "shrink-0 bg-background pb-3", children: _jsxs("div", { className: "mx-auto w-full max-w-[53rem] px-6", children: [error ? (_jsx("div", { role: "alert", className: "mb-2 rounded-md border border-red-500/20 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300", children: error })) : null, _jsx(ComposerPrimitive.Root, { "data-ui": "chat.composer", className: "group/composer relative rounded-[20px] border-[0.5px] border-border bg-card pt-2 shadow-sm transition-all duration-200 ease-in-out hover:border-foreground/25 focus-within:border-foreground/25", children: _jsx(ComposerPrimitive.AttachmentDropzone, { asChild: true, children: _jsxs("div", { className: "contents", children: [_jsx(ComposerExpand, { expanded: composerExpanded, onToggle: toggleComposer }), _jsx("div", { className: "flex flex-wrap gap-1.5 px-[15px] empty:hidden", children: _jsx(ComposerPrimitive.Attachments, { children: ({ attachment }) => _jsx(ComposerAttachment, { attachment: attachment }) }) }), _jsx(ComposerPrimitive.Input, { ref: composerInputRef, placeholder: labels.messagePlaceholder, disabled: blocked, rows: 2, minRows: composerRows, submitMode: "enter", className: cx('block min-h-[46px] w-full resize-none overflow-y-auto bg-transparent pb-0 pl-[15px] pr-11 pt-1.5 text-sm leading-[1.4] text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-60 [&::-webkit-scrollbar]:w-[3px]', composerExpanded ? 'max-h-[max(220px,50vh)]' : 'max-h-[max(220px,40vh)]') }), _jsxs("div", { className: "relative z-[2] flex min-h-10 items-center justify-between gap-4 px-2 py-[5px]", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx(AttachmentPicker, { disabled: blocked, enabled: allowAttachments }), composerTools, composerStatus ? _jsx("div", { className: "min-w-0 text-[11px] text-muted-foreground", children: composerStatus }) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [composerEnd, _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { disabled: blocked || attachmentUploading, "aria-label": labels.send, title: attachmentUploading ? labels.processing : labels.send, className: "mr-0.5 mt-px flex size-[30px] shrink-0 items-center justify-center text-brand transition-all duration-200 disabled:cursor-not-allowed disabled:text-muted-foreground/50", children: _jsx(
|
|
279
|
+
: (_jsx(AssistantMessage, { agentName: assistantName, allowRegenerate: allowRegenerate, branch: branchByMessageId.get(message.id), busy: blocked, components: components, messageId: message.id, onBranchSelect: onBranchSelect, onBranchStart: onBranchStart, onRegenerateMessage: onRegenerateMessage })) }) })] }), _jsx(ThreadPrimitive.ScrollToBottom, { "aria-label": labels.scrollToLatestMessage, title: labels.scrollToLatestMessage, className: "sticky bottom-3 z-10 mx-auto mb-3 flex size-9 shrink-0 items-center justify-center rounded-full border border-border bg-card text-muted-foreground hover:text-foreground disabled:invisible", children: _jsx(ChevronDown, { className: "size-4" }) })] }), _jsx("div", { className: "shrink-0 bg-background pb-3", children: _jsxs("div", { className: "mx-auto w-full max-w-[53rem] px-6", children: [error ? (_jsx("div", { role: "alert", className: "mb-2 rounded-md border border-red-500/20 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300", children: error })) : null, _jsx(ComposerPrimitive.Root, { "data-ui": "chat.composer", className: "group/composer relative rounded-[20px] border-[0.5px] border-border bg-card pt-2 shadow-sm transition-all duration-200 ease-in-out hover:border-foreground/25 focus-within:border-foreground/25", children: _jsx(ComposerPrimitive.AttachmentDropzone, { asChild: true, children: _jsxs("div", { className: "contents", children: [_jsx(ComposerExpand, { expanded: composerExpanded, onToggle: toggleComposer }), _jsx("div", { className: "flex flex-wrap gap-1.5 px-[15px] empty:hidden", children: _jsx(ComposerPrimitive.Attachments, { children: ({ attachment }) => _jsx(ComposerAttachment, { attachment: attachment }) }) }), _jsx(ComposerPrimitive.Input, { ref: composerInputRef, placeholder: labels.messagePlaceholder, disabled: blocked, rows: 2, minRows: composerRows, submitMode: "enter", className: cx('block min-h-[46px] w-full resize-none overflow-y-auto bg-transparent pb-0 pl-[15px] pr-11 pt-1.5 text-sm leading-[1.4] text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-60 [&::-webkit-scrollbar]:w-[3px]', composerExpanded ? 'max-h-[max(220px,50vh)]' : 'max-h-[max(220px,40vh)]') }), _jsxs("div", { className: "relative z-[2] flex min-h-10 items-center justify-between gap-4 px-2 py-[5px]", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [showAttachmentPicker && _jsx(AttachmentPicker, { disabled: blocked, enabled: allowAttachments }), composerTools, composerStatus ? _jsx("div", { className: "min-w-0 text-[11px] text-muted-foreground", children: composerStatus }) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [composerEnd, _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { disabled: blocked || attachmentUploading, "aria-label": labels.send, title: attachmentUploading ? labels.processing : labels.send, className: "mr-0.5 mt-px flex size-[30px] shrink-0 items-center justify-center rounded-full bg-brand text-brand-foreground transition-all duration-200 hover:bg-brand/85 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground/50", children: _jsx(ArrowUp, { className: "size-[17px]", strokeWidth: 2.5 }) }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(ComposerPrimitive.Cancel, { "aria-label": labels.stop, title: labels.stop, className: "flex size-[30px] shrink-0 items-center justify-center rounded-full text-destructive hover:bg-muted", children: _jsx(CirclePause, { className: "size-5" }) }) })] })] })] }) }) })] }) })] }));
|
|
280
280
|
}
|
|
281
281
|
export function ChatThread(_a) {
|
|
282
282
|
var { runtime, assistantName, allowAttachments = false, allowEdit = false, allowRegenerate = true, branchNavigation = [], busy = false, disabled = false, labels: labelOverrides, transformUserText = (text) => text } = _a, props = __rest(_a, ["runtime", "assistantName", "allowAttachments", "allowEdit", "allowRegenerate", "branchNavigation", "busy", "disabled", "labels", "transformUserText"]);
|
package/dist/Controls.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes, type ComponentPropsWithoutRef, type InputHTMLAttributes, type ReactNode, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';
|
|
2
|
-
|
|
2
|
+
import { Switch as SwitchPrimitive } from 'radix-ui';
|
|
3
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'danger-secondary';
|
|
3
4
|
export type ControlSize = 'sm' | 'md' | 'lg';
|
|
4
5
|
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
5
6
|
asChild?: boolean;
|
|
@@ -44,6 +45,10 @@ export declare const NativeSelect: import("react").ForwardRefExoticComponent<Sel
|
|
|
44
45
|
controlSize?: ControlSize;
|
|
45
46
|
wrapperClassName?: string;
|
|
46
47
|
} & import("react").RefAttributes<HTMLSelectElement>>;
|
|
48
|
+
export type SwitchProps = ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>;
|
|
49
|
+
export declare const Switch: import("react").ForwardRefExoticComponent<Omit<SwitchPrimitive.SwitchProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
50
|
+
export type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>;
|
|
51
|
+
export declare const Slider: import("react").ForwardRefExoticComponent<SliderProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
47
52
|
export type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>;
|
|
48
53
|
export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
49
54
|
export type RadioProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>;
|
|
@@ -59,7 +64,7 @@ export type SearchInputProps = Omit<InputProps, 'defaultValue' | 'type' | 'value
|
|
|
59
64
|
value: string;
|
|
60
65
|
wrapperClassName?: string;
|
|
61
66
|
};
|
|
62
|
-
export declare const SearchInput: import("react").ForwardRefExoticComponent<Omit<InputProps, "type" | "
|
|
67
|
+
export declare const SearchInput: import("react").ForwardRefExoticComponent<Omit<InputProps, "type" | "defaultValue" | "value"> & {
|
|
63
68
|
clearLabel?: string;
|
|
64
69
|
label: string;
|
|
65
70
|
onClear: () => void;
|
package/dist/Controls.js
CHANGED
|
@@ -13,13 +13,14 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
14
|
import { forwardRef, useImperativeHandle, useRef, } from 'react';
|
|
15
15
|
import { ChevronDown, Loader2, Search, X } from 'lucide-react';
|
|
16
|
-
import { Slot } from 'radix-ui';
|
|
16
|
+
import { Slot, Switch as SwitchPrimitive } from 'radix-ui';
|
|
17
17
|
function cx(...classes) {
|
|
18
18
|
return classes.filter(Boolean).join(' ');
|
|
19
19
|
}
|
|
20
20
|
const buttonVariants = {
|
|
21
21
|
primary: 'ui-button-primary',
|
|
22
22
|
secondary: 'ui-button-secondary',
|
|
23
|
+
outline: 'ui-button-secondary ui-button-outline',
|
|
23
24
|
ghost: 'ui-button-ghost',
|
|
24
25
|
danger: 'ui-button-primary ui-button-danger',
|
|
25
26
|
'danger-secondary': 'ui-button-secondary ui-button-danger-secondary',
|
|
@@ -63,6 +64,14 @@ export const Select = forwardRef(function Select(_a, ref) {
|
|
|
63
64
|
return (_jsxs("span", { "data-toolplane-ui": "select", className: cx('ui-select', wrapperClassName), children: [_jsx("select", Object.assign({}, props, { ref: ref, disabled: disabled, className: cx('ui-input ui-select-control', controlSize === 'sm' && 'ui-input-sm', controlSize === 'lg' && 'ui-input-lg', className), children: children })), _jsx(ChevronDown, { "aria-hidden": "true", className: cx('ui-select-chevron', disabled && 'opacity-40') })] }));
|
|
64
65
|
});
|
|
65
66
|
export const NativeSelect = Select;
|
|
67
|
+
export const Switch = forwardRef(function Switch(_a, ref) {
|
|
68
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
69
|
+
return (_jsx(SwitchPrimitive.Root, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "switch", className: cx('ui-switch', className), children: _jsx(SwitchPrimitive.Thumb, { className: "ui-switch-thumb" }) })));
|
|
70
|
+
});
|
|
71
|
+
export const Slider = forwardRef(function Slider(_a, ref) {
|
|
72
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
73
|
+
return _jsx("input", Object.assign({}, props, { ref: ref, type: "range", "data-toolplane-ui": "slider", className: cx('ui-slider', className) }));
|
|
74
|
+
});
|
|
66
75
|
export const Checkbox = forwardRef(function Checkbox(_a, ref) {
|
|
67
76
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
68
77
|
return (_jsx("input", Object.assign({}, props, { ref: ref, type: "checkbox", "data-toolplane-ui": "checkbox", className: cx('ui-checkbox', className) })));
|
|
@@ -25,6 +25,8 @@ export type ConversationSidebarLabels = {
|
|
|
25
25
|
deleteConversation: string;
|
|
26
26
|
showConversations: string;
|
|
27
27
|
hideConversations: string;
|
|
28
|
+
moveUp?: string;
|
|
29
|
+
moveDown?: string;
|
|
28
30
|
};
|
|
29
31
|
export declare const conversationSidebarDefaultLabels: ConversationSidebarLabels;
|
|
30
32
|
export type ConversationSidebarProps = {
|
|
@@ -33,6 +35,7 @@ export type ConversationSidebarProps = {
|
|
|
33
35
|
activeConversationId?: string | null;
|
|
34
36
|
labels?: Partial<ConversationSidebarLabels>;
|
|
35
37
|
className?: string;
|
|
38
|
+
onConversationOrderChange?: (groupId: string, conversationIds: string[]) => void;
|
|
36
39
|
onSelectGroup?: (group: ConversationSidebarGroup) => void;
|
|
37
40
|
onSelectConversation: (conversation: ConversationSidebarConversation, group: ConversationSidebarGroup) => void;
|
|
38
41
|
onCreateGroup?: () => void;
|
|
@@ -40,4 +43,4 @@ export type ConversationSidebarProps = {
|
|
|
40
43
|
onRenameConversation?: (conversation: ConversationSidebarConversation, group: ConversationSidebarGroup) => void;
|
|
41
44
|
onDeleteConversation?: (conversation: ConversationSidebarConversation, group: ConversationSidebarGroup) => void;
|
|
42
45
|
};
|
|
43
|
-
export declare function ConversationSidebar({ groups, activeGroupId, activeConversationId, labels: labelsOverride, className, onSelectGroup, onSelectConversation, onCreateGroup, onCreateConversation, onRenameConversation, onDeleteConversation, }: ConversationSidebarProps): import("react").JSX.Element;
|
|
46
|
+
export declare function ConversationSidebar({ groups, activeGroupId, activeConversationId, labels: labelsOverride, className, onSelectGroup, onSelectConversation, onCreateGroup, onCreateConversation, onRenameConversation, onDeleteConversation, onConversationOrderChange, }: ConversationSidebarProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useMemo, useState } from 'react';
|
|
4
|
-
import { Bot, ChevronRight,
|
|
3
|
+
import { useId, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { Bot, ChevronRight, Pencil, Plus, Trash2, } from 'lucide-react';
|
|
5
5
|
import { IconButton, SearchInput } from "./Controls.js";
|
|
6
6
|
import { SidebarActionRail } from "./Sidebar.js";
|
|
7
7
|
export const conversationSidebarDefaultLabels = {
|
|
@@ -18,14 +18,32 @@ export const conversationSidebarDefaultLabels = {
|
|
|
18
18
|
deleteConversation: 'Delete conversation',
|
|
19
19
|
showConversations: 'Show conversations',
|
|
20
20
|
hideConversations: 'Hide conversations',
|
|
21
|
+
moveUp: 'Move up',
|
|
22
|
+
moveDown: 'Move down',
|
|
21
23
|
};
|
|
22
24
|
function cx(...classes) {
|
|
23
25
|
return classes.filter(Boolean).join(' ');
|
|
24
26
|
}
|
|
25
|
-
export function ConversationSidebar({ groups, activeGroupId, activeConversationId, labels: labelsOverride, className, onSelectGroup, onSelectConversation, onCreateGroup, onCreateConversation, onRenameConversation, onDeleteConversation, }) {
|
|
27
|
+
export function ConversationSidebar({ groups, activeGroupId, activeConversationId, labels: labelsOverride, className, onSelectGroup, onSelectConversation, onCreateGroup, onCreateConversation, onRenameConversation, onDeleteConversation, onConversationOrderChange, }) {
|
|
26
28
|
const labels = Object.assign(Object.assign({}, conversationSidebarDefaultLabels), labelsOverride);
|
|
27
29
|
const controlsId = useId();
|
|
28
30
|
const [query, setQuery] = useState('');
|
|
31
|
+
const dragging = useRef(null);
|
|
32
|
+
const [dropRow, setDropRow] = useState(null);
|
|
33
|
+
function edgeFor(event) {
|
|
34
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
35
|
+
return event.clientY < rect.top + rect.height / 2 ? 'before' : 'after';
|
|
36
|
+
}
|
|
37
|
+
function move(sourceId, targetId, group, edge) {
|
|
38
|
+
const source = group.conversations.find((item) => item.id === sourceId);
|
|
39
|
+
const target = group.conversations.find((item) => item.id === targetId);
|
|
40
|
+
if (!onConversationOrderChange || !source || !target || sourceId === targetId || source.disabled || source.deleting || target.disabled || target.deleting)
|
|
41
|
+
return;
|
|
42
|
+
const ids = group.conversations.map((item) => item.id).filter((id) => id !== sourceId);
|
|
43
|
+
ids.splice(ids.indexOf(targetId) + (edge === 'after' ? 1 : 0), 0, sourceId);
|
|
44
|
+
if (ids.some((id, index) => group.conversations[index].id !== id))
|
|
45
|
+
onConversationOrderChange(group.id, ids);
|
|
46
|
+
}
|
|
29
47
|
const [collapsedGroupIds, setCollapsedGroupIds] = useState(() => new Set());
|
|
30
48
|
const needle = query.trim().toLocaleLowerCase();
|
|
31
49
|
const visibleGroups = useMemo(() => groups.flatMap((group) => {
|
|
@@ -59,9 +77,48 @@ export function ConversationSidebar({ groups, activeGroupId, activeConversationI
|
|
|
59
77
|
const title = conversation.title || labels.untitledConversation;
|
|
60
78
|
const selected = conversation.id === activeConversationId;
|
|
61
79
|
const hasActions = Boolean(onRenameConversation || onDeleteConversation);
|
|
62
|
-
|
|
80
|
+
const index = group.conversations.findIndex((item) => item.id === conversation.id);
|
|
81
|
+
const canMove = Boolean(onConversationOrderChange && !conversation.disabled && !conversation.deleting);
|
|
82
|
+
return (_jsx("li", { className: "relative py-0.5", "data-chat-ui": "sidebar-conversation", "data-conversation-id": conversation.id, "data-drop-edge": (dropRow === null || dropRow === void 0 ? void 0 : dropRow.id) === conversation.id && dropRow.groupId === group.id ? dropRow.edge : undefined, draggable: canMove, onDragStart: (event) => {
|
|
83
|
+
if (!canMove) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
event.stopPropagation();
|
|
88
|
+
dragging.current = { id: conversation.id, groupId: group.id };
|
|
89
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
90
|
+
event.dataTransfer.setData('text/plain', conversation.id);
|
|
91
|
+
}, onDragEnd: () => { dragging.current = null; setDropRow(null); }, onDragOver: (event) => {
|
|
92
|
+
var _a;
|
|
93
|
+
if (!canMove || ((_a = dragging.current) === null || _a === void 0 ? void 0 : _a.groupId) !== group.id || dragging.current.id === conversation.id)
|
|
94
|
+
return;
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
event.dataTransfer.dropEffect = 'move';
|
|
98
|
+
setDropRow({ id: conversation.id, groupId: group.id, edge: edgeFor(event) });
|
|
99
|
+
}, onDragLeave: (event) => {
|
|
100
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
101
|
+
setDropRow(null);
|
|
102
|
+
}, onDrop: (event) => {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
event.stopPropagation();
|
|
105
|
+
const source = dragging.current;
|
|
106
|
+
dragging.current = null;
|
|
107
|
+
setDropRow(null);
|
|
108
|
+
if ((source === null || source === void 0 ? void 0 : source.groupId) === group.id && canMove)
|
|
109
|
+
move(source.id, conversation.id, group, edgeFor(event));
|
|
110
|
+
}, children: _jsxs("div", { className: cx('group group/sidebar-conversation flex h-8 min-w-0 items-center gap-1.5 rounded-lg px-2 transition-colors', selected
|
|
63
111
|
? 'bg-muted font-medium text-foreground'
|
|
64
|
-
: 'text-foreground/75 hover:bg-muted/60 hover:text-foreground'), children: [_jsxs("button", { type: "button", disabled: conversation.disabled,
|
|
112
|
+
: 'text-foreground/75 hover:bg-muted/60 hover:text-foreground'), children: [_jsxs("button", { type: "button", disabled: conversation.disabled, onKeyDown: (event) => {
|
|
113
|
+
if (!event.altKey || !['ArrowUp', 'ArrowDown'].includes(event.key) || !canMove)
|
|
114
|
+
return;
|
|
115
|
+
const target = group.conversations[index + (event.key === 'ArrowUp' ? -1 : 1)];
|
|
116
|
+
if (!target || target.disabled || target.deleting)
|
|
117
|
+
return;
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
move(conversation.id, target.id, group, event.key === 'ArrowUp' ? 'before' : 'after');
|
|
120
|
+
}, onClick: () => { if (!dragging.current)
|
|
121
|
+
onSelectConversation(conversation, group); }, "aria-current": selected ? 'page' : undefined, title: title, className: "flex h-full min-w-0 flex-1 items-center gap-1.5 text-left text-[13px] disabled:cursor-not-allowed disabled:opacity-50", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: title }), conversation.meta ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0", children: conversation.meta })) : null] }), hasActions ? (_jsxs(SidebarActionRail, { active: conversation.deleting, children: [onRenameConversation ? (_jsx(IconButton, { icon: _jsx(Pencil, { className: "size-3" }), label: `${labels.renameConversation}: ${title}`, size: "sm", variant: "ghost", onClick: () => onRenameConversation(conversation, group), title: labels.renameConversation, className: "min-h-6 w-6 rounded-md text-muted-foreground hover:bg-background hover:text-foreground" })) : null, onDeleteConversation ? (_jsx(IconButton, { icon: _jsx(Trash2, { className: "size-3" }), label: `${labels.deleteConversation}: ${title}`, size: "sm", variant: "ghost", loading: conversation.deleting, disabled: conversation.deleting, onClick: () => onDeleteConversation(conversation, group), title: labels.deleteConversation, className: "min-h-6 w-6 rounded-md text-muted-foreground hover:bg-background hover:text-destructive" })) : null] })) : null] }) }, conversation.id));
|
|
65
122
|
}) : (_jsx("li", { className: "flex h-8 items-center px-2 text-xs text-muted-foreground", children: labels.noConversations })) })) : null] }, group.id));
|
|
66
123
|
}) }), !visibleGroups.length ? (_jsx("p", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: needle ? labels.noResults : labels.noGroups })) : null] })] }));
|
|
67
124
|
}
|
package/dist/Feedback.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export type ProgressProps = ComponentPropsWithoutRef<'progress'>;
|
|
3
|
+
export declare function Progress({ className, max, ...props }: ProgressProps): import("react").JSX.Element;
|
|
4
|
+
export type SkeletonProps = ComponentPropsWithoutRef<'div'>;
|
|
5
|
+
export declare function Skeleton({ className, ...props }: SkeletonProps): import("react").JSX.Element;
|
|
2
6
|
export type FeedbackTone = 'neutral' | 'brand' | 'success' | 'warning' | 'danger';
|
|
3
7
|
export type BadgeProps = ComponentPropsWithoutRef<'span'> & {
|
|
4
8
|
tone?: FeedbackTone;
|
package/dist/Feedback.js
CHANGED
|
@@ -14,6 +14,14 @@ import { Loader2 } from 'lucide-react';
|
|
|
14
14
|
function cx(...classes) {
|
|
15
15
|
return classes.filter(Boolean).join(' ');
|
|
16
16
|
}
|
|
17
|
+
export function Progress(_a) {
|
|
18
|
+
var { className, max = 100 } = _a, props = __rest(_a, ["className", "max"]);
|
|
19
|
+
return _jsx("progress", Object.assign({}, props, { max: max, "data-toolplane-ui": "progress", className: cx('ui-progress', className) }));
|
|
20
|
+
}
|
|
21
|
+
export function Skeleton(_a) {
|
|
22
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
23
|
+
return _jsx("div", Object.assign({}, props, { "aria-hidden": "true", "data-toolplane-ui": "skeleton", className: cx('ui-skeleton', className) }));
|
|
24
|
+
}
|
|
17
25
|
const badgeTones = {
|
|
18
26
|
neutral: 'bg-muted text-muted-foreground',
|
|
19
27
|
brand: 'bg-brand-soft text-accent-foreground',
|
package/dist/Layout.d.ts
CHANGED
|
@@ -55,9 +55,14 @@ export type DataTableProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> &
|
|
|
55
55
|
label?: string;
|
|
56
56
|
minWidth?: string;
|
|
57
57
|
panel?: boolean;
|
|
58
|
+
rowIds?: readonly string[];
|
|
59
|
+
rowLabels?: readonly ReactNode[];
|
|
60
|
+
selectedRowIds?: readonly string[];
|
|
61
|
+
onSelectedRowIdsChange?: (rowIds: string[]) => void;
|
|
62
|
+
selectable?: boolean;
|
|
58
63
|
tableClassName?: string;
|
|
59
64
|
};
|
|
60
|
-
export declare function DataTable({ children, className, headers, label, minWidth, panel, tableClassName, ...props }: DataTableProps): import("react").JSX.Element;
|
|
65
|
+
export declare function DataTable({ children, className, headers, label, minWidth, panel, rowIds, rowLabels, selectedRowIds, onSelectedRowIdsChange, selectable, tableClassName, ...props }: DataTableProps): import("react").JSX.Element;
|
|
61
66
|
export type EntityProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
|
|
62
67
|
description?: ReactNode;
|
|
63
68
|
initials?: string;
|
package/dist/Layout.js
CHANGED
|
@@ -10,6 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { Children, cloneElement, isValidElement, useEffect, useRef } from 'react';
|
|
13
14
|
function cx(...classes) {
|
|
14
15
|
return classes.filter(Boolean).join(' ');
|
|
15
16
|
}
|
|
@@ -39,9 +40,49 @@ export function EmptyState(_a) {
|
|
|
39
40
|
var { actions, children, className, description, icon: IconComponent, title } = _a, props = __rest(_a, ["actions", "children", "className", "description", "icon", "title"]);
|
|
40
41
|
return (_jsxs("div", Object.assign({}, props, { "data-toolplane-ui": "empty-state", className: cx('ui-empty', className), children: [IconComponent ? _jsx(IconComponent, { className: "mb-3 size-8 text-muted-foreground" }) : null, title ? _jsx("h2", { className: "text-lg font-semibold text-foreground", children: title }) : null, description ? (_jsx("div", { className: cx('text-sm text-muted-foreground', Boolean(title) && 'mt-1'), children: description })) : null, children ? _jsx("div", { className: "mt-6 w-full", children: children }) : null, actions ? _jsx("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-2", children: actions }) : null] })));
|
|
41
42
|
}
|
|
43
|
+
function SelectionCheckbox(_a) {
|
|
44
|
+
var { indeterminate = false } = _a, props = __rest(_a, ["indeterminate"]);
|
|
45
|
+
const ref = useRef(null);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (ref.current)
|
|
48
|
+
ref.current.indeterminate = indeterminate;
|
|
49
|
+
}, [indeterminate]);
|
|
50
|
+
return _jsx("input", Object.assign({}, props, { ref: ref, type: "checkbox", className: cx('ui-checkbox ui-table__checkbox', props.className) }));
|
|
51
|
+
}
|
|
42
52
|
export function DataTable(_a) {
|
|
43
|
-
var { children, className, headers, label, minWidth = '40rem', panel = true, tableClassName } = _a, props = __rest(_a, ["children", "className", "headers", "label", "minWidth", "panel", "tableClassName"]);
|
|
44
|
-
|
|
53
|
+
var { children, className, headers, label, minWidth = '40rem', panel = true, rowIds, rowLabels, selectedRowIds = [], onSelectedRowIdsChange, selectable = false, tableClassName } = _a, props = __rest(_a, ["children", "className", "headers", "label", "minWidth", "panel", "rowIds", "rowLabels", "selectedRowIds", "onSelectedRowIdsChange", "selectable", "tableClassName"]);
|
|
54
|
+
const childList = Children.toArray(children);
|
|
55
|
+
const bodyChild = childList.length === 1 && isValidElement(childList[0]) && childList[0].type === 'tbody'
|
|
56
|
+
? childList[0]
|
|
57
|
+
: undefined;
|
|
58
|
+
const rows = bodyChild ? bodyChild.props.children : children;
|
|
59
|
+
const selectableRows = selectable ? Children.toArray(rows).flatMap((child) => {
|
|
60
|
+
if (!isValidElement(child) || child.type !== 'tr')
|
|
61
|
+
return [];
|
|
62
|
+
return [child];
|
|
63
|
+
}) : [];
|
|
64
|
+
const ids = selectableRows.map((_, index) => { var _a; return (_a = rowIds === null || rowIds === void 0 ? void 0 : rowIds[index]) !== null && _a !== void 0 ? _a : String(index); });
|
|
65
|
+
const selected = new Set(selectedRowIds);
|
|
66
|
+
const allSelected = ids.length > 0 && ids.every((id) => selected.has(id));
|
|
67
|
+
const someSelected = ids.some((id) => selected.has(id));
|
|
68
|
+
const toggle = (id) => onSelectedRowIdsChange === null || onSelectedRowIdsChange === void 0 ? void 0 : onSelectedRowIdsChange(selected.has(id) ? selectedRowIds.filter((value) => value !== id) : [...selectedRowIds, id]);
|
|
69
|
+
const toggleAll = () => onSelectedRowIdsChange === null || onSelectedRowIdsChange === void 0 ? void 0 : onSelectedRowIdsChange(allSelected ? selectedRowIds.filter((id) => !ids.includes(id)) : [...new Set([...selectedRowIds, ...ids])]);
|
|
70
|
+
let rowIndex = 0;
|
|
71
|
+
const tableChildren = selectable
|
|
72
|
+
? Children.map(rows, (child) => {
|
|
73
|
+
var _a;
|
|
74
|
+
if (!isValidElement(child) || child.type !== 'tr')
|
|
75
|
+
return child;
|
|
76
|
+
const row = child;
|
|
77
|
+
const id = ids[rowIndex];
|
|
78
|
+
const index = rowIndex++;
|
|
79
|
+
return cloneElement(row, {}, [
|
|
80
|
+
_jsx("td", { className: "ui-table__selection", children: _jsx(SelectionCheckbox, { checked: selected.has(id), onChange: () => toggle(id), "aria-label": `选择${(_a = rowLabels === null || rowLabels === void 0 ? void 0 : rowLabels[index]) !== null && _a !== void 0 ? _a : `第 ${index + 1} 行`}` }) }, "selection"),
|
|
81
|
+
...Children.toArray(row.props.children),
|
|
82
|
+
]);
|
|
83
|
+
})
|
|
84
|
+
: rows;
|
|
85
|
+
return (_jsx("div", Object.assign({}, props, { "data-toolplane-ui": "data-table", role: label ? 'region' : undefined, "aria-label": label, tabIndex: label ? 0 : undefined, className: cx(panel && 'ui-panel', 'relative overflow-x-auto overscroll-x-contain focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring', className), children: _jsxs("table", { className: cx('ui-table', tableClassName), style: { minWidth }, children: [_jsx("thead", { children: _jsxs("tr", { children: [selectable ? (_jsx("th", { scope: "col", className: "ui-table__selection", children: _jsx(SelectionCheckbox, { checked: allSelected, indeterminate: !allSelected && someSelected, onChange: toggleAll, "aria-label": "\u5168\u9009\u884C" }) })) : null, headers.map((header, index) => (_jsx("th", { scope: "col", colSpan: header.colSpan, className: cx('px-4 py-3 font-medium', header.align === 'right' && 'text-right', header.className), children: header.label }, index)))] }) }), _jsx("tbody", { className: cx('divide-y divide-border', bodyChild === null || bodyChild === void 0 ? void 0 : bodyChild.props.className), children: tableChildren })] }) })));
|
|
45
86
|
}
|
|
46
87
|
export function Entity(_a) {
|
|
47
88
|
var { className, description, initials, mono = false, title } = _a, props = __rest(_a, ["className", "description", "initials", "mono", "title"]);
|
package/dist/Navigation.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from 'radix-ui';
|
|
1
2
|
import type { ButtonHTMLAttributes, ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
|
+
export declare const Tabs: import("react").ForwardRefExoticComponent<TabsPrimitive.TabsProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export declare const TabsList: import("react").ForwardRefExoticComponent<Omit<TabsPrimitive.TabsListProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
5
|
+
export declare const TabsTrigger: import("react").ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
export declare const TabsContent: import("react").ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
2
7
|
export type ChipProps = ComponentPropsWithoutRef<'span'> & {
|
|
3
8
|
active?: boolean;
|
|
4
9
|
asChild?: boolean;
|
package/dist/Navigation.js
CHANGED
|
@@ -11,10 +11,24 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
-
import { Slot } from 'radix-ui';
|
|
14
|
+
import { Slot, Tabs as TabsPrimitive } from 'radix-ui';
|
|
15
|
+
import { forwardRef } from 'react';
|
|
15
16
|
function cx(...classes) {
|
|
16
17
|
return classes.filter(Boolean).join(' ');
|
|
17
18
|
}
|
|
19
|
+
export const Tabs = TabsPrimitive.Root;
|
|
20
|
+
export const TabsList = forwardRef(function TabsList(_a, ref) {
|
|
21
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
22
|
+
return _jsx(TabsPrimitive.List, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "tabs-list", className: cx('ui-tabs-list', className) }));
|
|
23
|
+
});
|
|
24
|
+
export const TabsTrigger = forwardRef(function TabsTrigger(_a, ref) {
|
|
25
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
26
|
+
return _jsx(TabsPrimitive.Trigger, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "tabs-trigger", className: cx('ui-tabs-trigger', className) }));
|
|
27
|
+
});
|
|
28
|
+
export const TabsContent = forwardRef(function TabsContent(_a, ref) {
|
|
29
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
30
|
+
return _jsx(TabsPrimitive.Content, Object.assign({}, props, { ref: ref, "data-toolplane-ui": "tabs-content", className: cx('ui-tabs-content', className) }));
|
|
31
|
+
});
|
|
18
32
|
export function Chip(_a) {
|
|
19
33
|
var { active = false, asChild = false, className } = _a, props = __rest(_a, ["active", "asChild", "className"]);
|
|
20
34
|
const Component = asChild ? Slot.Root : 'span';
|
package/dist/Overlays.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import { ContextMenu as ContextMenuPrimitive, HoverCard as HoverCardPrimitive, Popover as PopoverPrimitive, Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
1
|
+
import { ContextMenu as ContextMenuPrimitive, DropdownMenu as DropdownMenuPrimitive, HoverCard as HoverCardPrimitive, Popover as PopoverPrimitive, Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
2
|
+
export declare const DropdownMenu: import("react").FC<DropdownMenuPrimitive.DropdownMenuProps>;
|
|
3
|
+
export declare const DropdownMenuTrigger: import("react").ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
4
|
+
export declare const DropdownMenuPortal: import("react").FC<DropdownMenuPrimitive.DropdownMenuPortalProps>;
|
|
5
|
+
export declare const DropdownMenuGroup: import("react").ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuGroupProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
6
|
+
export declare const DropdownMenuContent: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export declare const DropdownMenuItem: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
8
|
+
export declare const DropdownMenuLabel: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuLabelProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
9
|
+
export declare const DropdownMenuSeparator: import("react").ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
2
10
|
export declare const Popover: import("react").FC<PopoverPrimitive.PopoverProps>;
|
|
3
11
|
export declare const PopoverTrigger: import("react").ForwardRefExoticComponent<PopoverPrimitive.PopoverTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
4
12
|
export declare const PopoverPortal: import("react").FC<PopoverPrimitive.PopoverPortalProps>;
|
package/dist/Overlays.js
CHANGED
|
@@ -12,7 +12,27 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
};
|
|
13
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
14
|
import { forwardRef, } from 'react';
|
|
15
|
-
import { ContextMenu as ContextMenuPrimitive, HoverCard as HoverCardPrimitive, Popover as PopoverPrimitive, Tooltip as TooltipPrimitive, } from 'radix-ui';
|
|
15
|
+
import { ContextMenu as ContextMenuPrimitive, DropdownMenu as DropdownMenuPrimitive, HoverCard as HoverCardPrimitive, Popover as PopoverPrimitive, Tooltip as TooltipPrimitive, } from 'radix-ui';
|
|
16
|
+
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
17
|
+
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
18
|
+
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
19
|
+
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
20
|
+
export const DropdownMenuContent = forwardRef(function DropdownMenuContent(_a, ref) {
|
|
21
|
+
var { className, sideOffset = 6, collisionPadding = 8 } = _a, props = __rest(_a, ["className", "sideOffset", "collisionPadding"]);
|
|
22
|
+
return _jsx(DropdownMenuPrimitive.Content, Object.assign({}, props, { ref: ref, sideOffset: sideOffset, collisionPadding: collisionPadding, "data-toolplane-ui": "dropdown-menu", className: `ui-dropdown-menu ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
23
|
+
});
|
|
24
|
+
export const DropdownMenuItem = forwardRef(function DropdownMenuItem(_a, ref) {
|
|
25
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
26
|
+
return _jsx(DropdownMenuPrimitive.Item, Object.assign({}, props, { ref: ref, className: `ui-dropdown-item ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
27
|
+
});
|
|
28
|
+
export const DropdownMenuLabel = forwardRef(function DropdownMenuLabel(_a, ref) {
|
|
29
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
30
|
+
return _jsx(DropdownMenuPrimitive.Label, Object.assign({}, props, { ref: ref, className: `ui-dropdown-label ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
31
|
+
});
|
|
32
|
+
export const DropdownMenuSeparator = forwardRef(function DropdownMenuSeparator(_a, ref) {
|
|
33
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
34
|
+
return _jsx(DropdownMenuPrimitive.Separator, Object.assign({}, props, { ref: ref, className: `ui-dropdown-separator ${className !== null && className !== void 0 ? className : ''}`.trim() }));
|
|
35
|
+
});
|
|
16
36
|
export const Popover = PopoverPrimitive.Root;
|
|
17
37
|
export const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
18
38
|
export const PopoverPortal = PopoverPrimitive.Portal;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export type WorkspaceSidebarItem = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon: ReactNode;
|
|
6
|
+
badge?: ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type WorkspaceSidebarProps = Omit<HTMLAttributes<HTMLElement>, 'children' | 'onSelect'> & {
|
|
10
|
+
brand: ReactNode;
|
|
11
|
+
brandIcon: ReactNode;
|
|
12
|
+
brandLabel: string;
|
|
13
|
+
onBrandClick?: () => void;
|
|
14
|
+
workspace?: {
|
|
15
|
+
icon: ReactNode;
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
badge?: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
footer?: ReactNode;
|
|
21
|
+
items: readonly WorkspaceSidebarItem[];
|
|
22
|
+
activeId?: string;
|
|
23
|
+
onSelect: (id: string) => void;
|
|
24
|
+
collapsed: boolean;
|
|
25
|
+
onCollapsedChange: (collapsed: boolean) => void;
|
|
26
|
+
mobileOpen?: boolean;
|
|
27
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
28
|
+
mobileCloseRef?: Ref<HTMLButtonElement>;
|
|
29
|
+
navigationLabel?: string;
|
|
30
|
+
sectionLabel?: string;
|
|
31
|
+
labels?: {
|
|
32
|
+
collapse?: string;
|
|
33
|
+
expand?: string;
|
|
34
|
+
close?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare const WorkspaceSidebar: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLElement>, "children" | "onSelect"> & {
|
|
38
|
+
brand: ReactNode;
|
|
39
|
+
brandIcon: ReactNode;
|
|
40
|
+
brandLabel: string;
|
|
41
|
+
onBrandClick?: () => void;
|
|
42
|
+
workspace?: {
|
|
43
|
+
icon: ReactNode;
|
|
44
|
+
name: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
badge?: ReactNode;
|
|
47
|
+
};
|
|
48
|
+
footer?: ReactNode;
|
|
49
|
+
items: readonly WorkspaceSidebarItem[];
|
|
50
|
+
activeId?: string;
|
|
51
|
+
onSelect: (id: string) => void;
|
|
52
|
+
collapsed: boolean;
|
|
53
|
+
onCollapsedChange: (collapsed: boolean) => void;
|
|
54
|
+
mobileOpen?: boolean;
|
|
55
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
56
|
+
mobileCloseRef?: Ref<HTMLButtonElement>;
|
|
57
|
+
navigationLabel?: string;
|
|
58
|
+
sectionLabel?: string;
|
|
59
|
+
labels?: {
|
|
60
|
+
collapse?: string;
|
|
61
|
+
expand?: string;
|
|
62
|
+
close?: string;
|
|
63
|
+
};
|
|
64
|
+
} & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
+
import { forwardRef, useId } from 'react';
|
|
15
|
+
import { PanelLeftClose, PanelLeftOpen, X } from 'lucide-react';
|
|
16
|
+
import { Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger } from "./Overlays.js";
|
|
17
|
+
export const WorkspaceSidebar = forwardRef(function WorkspaceSidebar(_a, ref) {
|
|
18
|
+
var _b, _c, _d, _e;
|
|
19
|
+
var { brand, brandIcon, brandLabel, onBrandClick, workspace, footer, items, activeId, onSelect, collapsed, onCollapsedChange, mobileOpen = false, onMobileOpenChange, mobileCloseRef, navigationLabel = 'Workspace navigation', sectionLabel, labels, className, id } = _a, props = __rest(_a, ["brand", "brandIcon", "brandLabel", "onBrandClick", "workspace", "footer", "items", "activeId", "onSelect", "collapsed", "onCollapsedChange", "mobileOpen", "onMobileOpenChange", "mobileCloseRef", "navigationLabel", "sectionLabel", "labels", "className", "id"]);
|
|
20
|
+
const generatedId = useId();
|
|
21
|
+
const sidebarId = id !== null && id !== void 0 ? id : generatedId;
|
|
22
|
+
const toggleLabel = collapsed ? (_b = labels === null || labels === void 0 ? void 0 : labels.expand) !== null && _b !== void 0 ? _b : 'Expand sidebar' : (_c = labels === null || labels === void 0 ? void 0 : labels.collapse) !== null && _c !== void 0 ? _c : 'Collapse sidebar';
|
|
23
|
+
return _jsxs("aside", Object.assign({}, props, { id: sidebarId, ref: ref, className: `tp-workspace-sidebar ${className !== null && className !== void 0 ? className : ''}`, "data-toolplane-ui": "workspace-sidebar", "data-collapsed": collapsed, "data-mobile-open": mobileOpen, children: [_jsxs("div", { className: "tp-workspace-sidebar__header", children: [_jsxs("button", { type: "button", className: "tp-workspace-sidebar__brand", "aria-label": brandLabel, onClick: onBrandClick, children: [brandIcon, _jsx("span", { children: brand })] }), _jsx("button", { type: "button", className: "tp-workspace-sidebar__toggle", "aria-label": toggleLabel, title: toggleLabel, "aria-controls": sidebarId, "aria-expanded": !collapsed, onClick: () => onCollapsedChange(!collapsed), children: collapsed ? _jsxs("span", { className: "tp-workspace-sidebar__expand", children: [brandIcon, _jsx(PanelLeftOpen, { size: 18 })] }) : _jsx(PanelLeftClose, { size: 18 }) }), _jsx("button", { type: "button", ref: mobileCloseRef, className: "tp-workspace-sidebar__mobile-close", "aria-label": (_d = labels === null || labels === void 0 ? void 0 : labels.close) !== null && _d !== void 0 ? _d : 'Close navigation', title: (_e = labels === null || labels === void 0 ? void 0 : labels.close) !== null && _e !== void 0 ? _e : 'Close navigation', onClick: () => onMobileOpenChange === null || onMobileOpenChange === void 0 ? void 0 : onMobileOpenChange(false), children: _jsx(X, { size: 18 }) })] }), sectionLabel && _jsx("div", { className: "tp-workspace-sidebar__section", children: sectionLabel }), _jsx(TooltipProvider, { children: _jsx("nav", { className: "tp-workspace-sidebar__nav", "aria-label": navigationLabel, children: items.map((item) => _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "tp-workspace-sidebar__item", disabled: item.disabled, "aria-label": item.label, "aria-current": activeId === item.id ? 'page' : undefined, onClick: () => onSelect(item.id), children: [_jsx("span", { className: "tp-workspace-sidebar__icon", children: item.icon }), _jsx("span", { className: "tp-workspace-sidebar__label", children: item.label }), _jsx("span", { className: "tp-workspace-sidebar__badge", children: item.badge })] }) }), collapsed && _jsx(TooltipPortal, { children: _jsx(TooltipContent, { side: "right", children: item.label }) })] }, item.id)) }) }), workspace && _jsxs("div", { className: "tp-workspace-sidebar__workspace", children: [_jsx("span", { className: "tp-workspace-sidebar__workspace-icon", children: workspace.icon }), _jsxs("span", { className: "tp-workspace-sidebar__workspace-text", children: [_jsx("strong", { children: workspace.name }), _jsx("small", { children: workspace.description })] }), _jsx("span", { className: "tp-workspace-sidebar__workspace-badge", children: workspace.badge })] }), footer && _jsx("div", { className: "tp-workspace-sidebar__footer", children: footer })] }));
|
|
24
|
+
});
|
package/dist/WorkspaceTabBar.js
CHANGED
|
@@ -18,7 +18,7 @@ export function WorkspaceTabBar({ actions, activeTabId, labels: labelsOverride,
|
|
|
18
18
|
var _a, _b;
|
|
19
19
|
(_b = (_a = activeRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, { block: 'nearest', inline: 'nearest' });
|
|
20
20
|
}, [activeTabId, tabs.length]);
|
|
21
|
-
return (_jsxs("nav", { "aria-label": labels.navigation, "data-toolplane-ui": "workspace-tab-bar", className: "flex h-11 shrink-0 bg-shell px-2", children: [_jsxs("ol", { className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto py-1 [&::-webkit-scrollbar]:hidden", children: [tabs.map((tab) => (_jsx(WorkspaceTabButton, { tab: tab, active: tab.id === activeTabId, canClose: tabs.length > 1, draggingId: draggingId, labels: labels, onClose: onClose, onDragChange: setDraggingId, onNewWindow: onOpenInNewWindow, onReorder: onReorder, onSelect: onSelect, onTogglePinned: onTogglePinned, activeButtonRef: tab.id === activeTabId ? activeRef : undefined }, tab.id))), _jsx("li", { className: "shrink-0", children: _jsx("button", { type: "button", "aria-label": labels.newTab, title: labels.newTab, onClick: onNewTab, className: "ui-button-ghost ui-icon-button", children: _jsx(Plus, { className: "size-4" }) }) })] }), actions] }));
|
|
21
|
+
return (_jsxs("nav", { "aria-label": labels.navigation, "data-toolplane-ui": "workspace-tab-bar", className: "flex h-11 shrink-0 bg-shell px-2", children: [_jsxs("ol", { className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto overflow-y-hidden py-1 [&::-webkit-scrollbar]:hidden", children: [tabs.map((tab) => (_jsx(WorkspaceTabButton, { tab: tab, active: tab.id === activeTabId, canClose: tabs.length > 1, draggingId: draggingId, labels: labels, onClose: onClose, onDragChange: setDraggingId, onNewWindow: onOpenInNewWindow, onReorder: onReorder, onSelect: onSelect, onTogglePinned: onTogglePinned, activeButtonRef: tab.id === activeTabId ? activeRef : undefined }, tab.id))), _jsx("li", { className: "shrink-0", children: _jsx("button", { type: "button", "aria-label": labels.newTab, title: labels.newTab, onClick: onNewTab, className: "ui-button-ghost ui-icon-button", children: _jsx(Plus, { className: "size-4" }) }) })] }), actions] }));
|
|
22
22
|
}
|
|
23
23
|
function WorkspaceTabButton({ active, activeButtonRef, canClose, draggingId, labels, onClose, onDragChange, onNewWindow, onReorder, onSelect, onTogglePinned, tab, }) {
|
|
24
24
|
const dragAllowed = Boolean(draggingId && draggingId !== tab.id);
|
|
@@ -41,9 +41,13 @@ function WorkspaceTabButton({ active, activeButtonRef, canClose, draggingId, lab
|
|
|
41
41
|
onReorder(draggingId, tab.id);
|
|
42
42
|
onDragChange(null);
|
|
43
43
|
}, className: `group flex h-[30px] min-w-24 max-w-56 shrink-0 items-center rounded-[10px] transition-[background-color,color,transform] duration-150 ${active ? 'bg-background text-foreground ring-1 ring-border/70' : 'text-muted-foreground hover:bg-accent/70 hover:text-foreground'} ${draggingId === tab.id ? 'scale-[0.98] opacity-50' : ''}`, children: [_jsxs("button", { ref: activeButtonRef, type: "button", "aria-current": active ? 'page' : undefined, title: tab.label, onAuxClick: (event) => {
|
|
44
|
-
if (event.button
|
|
44
|
+
if (event.button !== 1)
|
|
45
|
+
return;
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
if (canClose)
|
|
45
48
|
onClose(tab.id);
|
|
46
|
-
}, onClick: () => onSelect(tab.id), onDoubleClick: () =>
|
|
49
|
+
}, onClick: () => onSelect(tab.id), onDoubleClick: () => { if (canClose)
|
|
50
|
+
onClose(tab.id); }, className: "flex h-full min-w-0 flex-1 items-center gap-1.5 px-2 text-left text-xs", children: [_jsx(Icon, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: tab.label })] }), _jsxs("div", { className: "mr-1 flex shrink-0 items-center", children: [_jsx("button", { type: "button", "aria-pressed": tab.pinned, "aria-label": tab.pinned ? labels.unpin(tab.label) : labels.pin(tab.label), title: tab.pinned ? labels.unpin(tab.label) : labels.pin(tab.label), onClick: (event) => {
|
|
47
51
|
stop(event);
|
|
48
52
|
onTogglePinned(tab.id);
|
|
49
53
|
}, className: `flex size-[18px] items-center justify-center rounded-sm transition-colors hover:bg-foreground/10 ${tab.pinned ? (controls ? 'bg-brand-soft text-accent-foreground' : 'bg-brand-soft/50 text-accent-foreground/60 group-hover:bg-brand-soft group-hover:text-accent-foreground group-focus-within:bg-brand-soft group-focus-within:text-accent-foreground') : (controls ? '' : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100')}`, children: _jsx(Pin, { className: `size-3 ${tab.pinned ? 'fill-current' : ''}` }) }), _jsxs("div", { className: `flex items-center transition-opacity ${controls ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100'}`, children: [_jsx("button", { type: "button", "aria-label": labels.openInNewWindow(tab.label), title: labels.openInNewWindow(tab.label), onClick: (event) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from './Brand.js';
|
|
2
|
+
export * from './Accordion.js';
|
|
3
|
+
export * from './Avatar.js';
|
|
2
4
|
export * from './ChatShell.js';
|
|
3
5
|
export * from './ChatThread.js';
|
|
6
|
+
export * from './ChatComposerToolbar.js';
|
|
4
7
|
export * from './chat-markdown.js';
|
|
5
8
|
export * from './ContentPage.js';
|
|
6
9
|
export * from './Controls.js';
|
|
@@ -15,3 +18,4 @@ export * from './RotatingHeadline.js';
|
|
|
15
18
|
export * from './SafeStreamdown.js';
|
|
16
19
|
export * from './Sidebar.js';
|
|
17
20
|
export * from './WorkspaceTabBar.js';
|
|
21
|
+
export * from './WorkspaceSidebar.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from "./Brand.js";
|
|
2
|
+
export * from "./Accordion.js";
|
|
3
|
+
export * from "./Avatar.js";
|
|
2
4
|
export * from "./ChatShell.js";
|
|
3
5
|
export * from "./ChatThread.js";
|
|
6
|
+
export * from "./ChatComposerToolbar.js";
|
|
4
7
|
export * from "./chat-markdown.js";
|
|
5
8
|
export * from "./ContentPage.js";
|
|
6
9
|
export * from "./Controls.js";
|
|
@@ -15,3 +18,4 @@ export * from "./RotatingHeadline.js";
|
|
|
15
18
|
export * from "./SafeStreamdown.js";
|
|
16
19
|
export * from "./Sidebar.js";
|
|
17
20
|
export * from "./WorkspaceTabBar.js";
|
|
21
|
+
export * from "./WorkspaceSidebar.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asharca/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Reusable ToolPlane components and chat UI for React applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,6 +44,22 @@
|
|
|
44
44
|
"types": "./dist/Controls.d.ts",
|
|
45
45
|
"import": "./dist/Controls.js"
|
|
46
46
|
},
|
|
47
|
+
"./accordion": {
|
|
48
|
+
"types": "./dist/Accordion.d.ts",
|
|
49
|
+
"import": "./dist/Accordion.js"
|
|
50
|
+
},
|
|
51
|
+
"./avatar": {
|
|
52
|
+
"types": "./dist/Avatar.d.ts",
|
|
53
|
+
"import": "./dist/Avatar.js"
|
|
54
|
+
},
|
|
55
|
+
"./workspace-tab-bar": {
|
|
56
|
+
"types": "./dist/WorkspaceTabBar.d.ts",
|
|
57
|
+
"import": "./dist/WorkspaceTabBar.js"
|
|
58
|
+
},
|
|
59
|
+
"./workspace-sidebar": {
|
|
60
|
+
"types": "./dist/WorkspaceSidebar.d.ts",
|
|
61
|
+
"import": "./dist/WorkspaceSidebar.js"
|
|
62
|
+
},
|
|
47
63
|
"./dialog": {
|
|
48
64
|
"types": "./dist/Dialog.d.ts",
|
|
49
65
|
"import": "./dist/Dialog.js"
|
|
@@ -72,6 +88,10 @@
|
|
|
72
88
|
"types": "./dist/ChatShell.d.ts",
|
|
73
89
|
"import": "./dist/ChatShell.js"
|
|
74
90
|
},
|
|
91
|
+
"./chat-composer-toolbar": {
|
|
92
|
+
"types": "./dist/ChatComposerToolbar.d.ts",
|
|
93
|
+
"import": "./dist/ChatComposerToolbar.js"
|
|
94
|
+
},
|
|
75
95
|
"./chat-thread": {
|
|
76
96
|
"types": "./dist/ChatThread.d.ts",
|
|
77
97
|
"import": "./dist/ChatThread.js"
|
|
@@ -83,7 +103,9 @@
|
|
|
83
103
|
"./styles.css": "./src/styles.css"
|
|
84
104
|
},
|
|
85
105
|
"scripts": {
|
|
86
|
-
"
|
|
106
|
+
"dev": "vite --config showcase/vite.config.ts",
|
|
107
|
+
"build:showcase": "tsc -p showcase/tsconfig.json && vite build --config showcase/vite.config.ts",
|
|
108
|
+
"lint": "eslint src tests showcase",
|
|
87
109
|
"test": "vitest run",
|
|
88
110
|
"test:watch": "vitest",
|
|
89
111
|
"test:package": "node scripts/check-package.mjs",
|
|
@@ -128,6 +150,7 @@
|
|
|
128
150
|
"tailwindcss": "^4",
|
|
129
151
|
"typescript": "^5",
|
|
130
152
|
"typescript-eslint": "^8",
|
|
153
|
+
"vite": "^8.2.2",
|
|
131
154
|
"vitest": "^4.1.9"
|
|
132
155
|
}
|
|
133
156
|
}
|
package/src/styles.css
CHANGED
|
@@ -7,6 +7,171 @@
|
|
|
7
7
|
@source "../../../@streamdown/mermaid/dist/*.js";
|
|
8
8
|
@source "./**/*.{ts,tsx}";
|
|
9
9
|
|
|
10
|
+
@layer components {
|
|
11
|
+
.tp-workspace-sidebar { display: flex; flex-direction: column; flex-shrink: 0; position: relative; height: 100%; width: var(--workspace-sidebar-width, 256px); overflow: hidden; padding: 0 12px 14px; background: hsl(var(--shell)); color: hsl(var(--foreground)); transition: width 200ms ease-out, padding 200ms ease-out, transform 200ms ease-out; }
|
|
12
|
+
.tp-workspace-sidebar__header { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; height: 56px; gap: 4px; }
|
|
13
|
+
.tp-workspace-sidebar__brand { display: flex; align-items: center; gap: 9px; min-width: 0; white-space: nowrap; font-weight: 600; font-size: 20px; }
|
|
14
|
+
.tp-workspace-sidebar__brand svg { flex-shrink: 0; }
|
|
15
|
+
.tp-workspace-sidebar__toggle, .tp-workspace-sidebar__mobile-close { display: grid; place-items: center; width: 36px; height: 36px; flex-shrink: 0; border-radius: 6px; color: hsl(var(--muted-foreground)); }
|
|
16
|
+
.tp-workspace-sidebar__toggle:hover, .tp-workspace-sidebar__mobile-close:hover { background: hsl(var(--muted)); }
|
|
17
|
+
.tp-workspace-sidebar__mobile-close { display: none; }
|
|
18
|
+
.tp-workspace-sidebar__expand { display: grid; place-items: center; width: 24px; height: 24px; }
|
|
19
|
+
.tp-workspace-sidebar__expand > svg { grid-area: 1 / 1; transition: opacity 150ms ease-out, transform 150ms ease-out; }
|
|
20
|
+
.tp-workspace-sidebar__expand > svg:last-child { opacity: 0; transform: scale(.75); }
|
|
21
|
+
.tp-workspace-sidebar__toggle:is(:hover,:focus-visible) .tp-workspace-sidebar__expand > svg:first-child { opacity: 0; transform: scale(.9); }
|
|
22
|
+
.tp-workspace-sidebar__toggle:is(:hover,:focus-visible) .tp-workspace-sidebar__expand > svg:last-child { opacity: 1; transform: scale(1); }
|
|
23
|
+
.tp-workspace-sidebar__workspace { height: 70px; display: flex; flex-shrink: 0; align-items: center; gap: 8px; margin-top: auto; padding: 12px 7px; overflow: hidden; transition: padding 200ms ease-out, gap 200ms ease-out; }
|
|
24
|
+
.tp-workspace-sidebar__workspace-icon { display: grid; place-items: center; width: 30px; height: 30px; flex-shrink: 0; border: 1px solid hsl(var(--border)); border-radius: 6px; background: hsl(var(--background)); font-size: 12px; }
|
|
25
|
+
.tp-workspace-sidebar__workspace-text { min-width: 0; flex: 1; }
|
|
26
|
+
.tp-workspace-sidebar__workspace-text strong { display: block; font-size: 11px; }
|
|
27
|
+
.tp-workspace-sidebar__workspace-text small { display: block; font-size: 10px; color: hsl(var(--muted-foreground)); }
|
|
28
|
+
.tp-workspace-sidebar__section { height: 28px; padding: 4px 12px; flex-shrink: 0; font-size: 10px; color: hsl(var(--muted-foreground)); }
|
|
29
|
+
.tp-workspace-sidebar__nav { display: grid; align-content: start; gap: 3px; overflow-y: auto; overflow-x: hidden; min-height: 0; }
|
|
30
|
+
.tp-workspace-sidebar__item { display: grid; grid-template-columns: 18px minmax(0,1fr) auto; align-items: center; column-gap: 10px; width: 100%; height: 40px; padding: 0 12px; text-align: left; border-radius: 8px; color: hsl(var(--muted-foreground)); font-size: 12px; overflow: hidden; transition: height 200ms ease-out, border-radius 200ms ease-out, padding 200ms ease-out, column-gap 200ms ease-out; }
|
|
31
|
+
.tp-workspace-sidebar__icon { display: grid; place-items: center; width: 18px; height: 18px; }
|
|
32
|
+
.tp-workspace-sidebar__label, .tp-workspace-sidebar__badge, .tp-workspace-sidebar__workspace-text, .tp-workspace-sidebar__workspace-badge { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity 150ms ease-out, transform 150ms ease-out; }
|
|
33
|
+
.tp-workspace-sidebar__badge { font-size: 10px; }
|
|
34
|
+
.tp-workspace-sidebar__item:hover { background: hsl(var(--muted)); color: hsl(var(--foreground)); }
|
|
35
|
+
.tp-workspace-sidebar__item[aria-current="page"] { background: hsl(var(--brand-soft)); color: hsl(var(--foreground)); font-weight: 550; }
|
|
36
|
+
.tp-workspace-sidebar__item:disabled { opacity: .5; cursor: not-allowed; }
|
|
37
|
+
.tp-workspace-sidebar button:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: -2px; }
|
|
38
|
+
.tp-workspace-sidebar__footer { padding: 14px 7px 0; display: flex; align-items: center; gap: 9px; overflow: hidden; }
|
|
39
|
+
.tp-workspace-sidebar__footer > :first-child { flex-shrink: 0; }
|
|
40
|
+
.tp-workspace-sidebar__footer > :not(:first-child) { min-width: 0; white-space: nowrap; transition: opacity 150ms ease-out; }
|
|
41
|
+
.tp-workspace-sidebar__footer strong, .tp-workspace-sidebar__footer small { display: block; font-size: 11px; }
|
|
42
|
+
.tp-workspace-sidebar__footer small { color: hsl(var(--muted-foreground)); }
|
|
43
|
+
@media (min-width: 851px) {
|
|
44
|
+
.tp-workspace-sidebar[data-collapsed="true"] { width: var(--workspace-sidebar-collapsed-width, 64px); padding-inline: 8px; }
|
|
45
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__brand { display: none; }
|
|
46
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__header { justify-content: center; }
|
|
47
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__item { grid-template-columns: 18px 0px 0px; column-gap: 0; width: 36px; height: 36px; padding-inline: 9px; margin-inline: auto; border-radius: 50%; }
|
|
48
|
+
.tp-workspace-sidebar[data-collapsed="true"] :is(.tp-workspace-sidebar__label,.tp-workspace-sidebar__badge,.tp-workspace-sidebar__workspace-text,.tp-workspace-sidebar__workspace-badge,.tp-workspace-sidebar__section) { opacity: 0; transform: translateX(4px); }
|
|
49
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__workspace { gap: 0; padding-inline: 9px; }
|
|
50
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__workspace-badge { width: 0; }
|
|
51
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__footer { gap: 0; padding-inline: 9px; }
|
|
52
|
+
.tp-workspace-sidebar[data-collapsed="true"] .tp-workspace-sidebar__footer > :not(:first-child) { width: 0; opacity: 0; overflow: hidden; }
|
|
53
|
+
}
|
|
54
|
+
@media (max-width: 850px) {
|
|
55
|
+
.tp-workspace-sidebar { position: fixed; inset: 0 auto 0 0; z-index: 30; transform: translateX(-100%); visibility: hidden; }
|
|
56
|
+
.tp-workspace-sidebar[data-mobile-open="true"] { transform: translateX(0); visibility: visible; }
|
|
57
|
+
.tp-workspace-sidebar__toggle { display: none; }
|
|
58
|
+
.tp-workspace-sidebar__mobile-close { display: grid; }
|
|
59
|
+
}
|
|
60
|
+
@media (prefers-reduced-motion: reduce) { .tp-workspace-sidebar, .tp-workspace-sidebar * { transition-duration: 0.01ms !important; } }
|
|
61
|
+
[data-chat-ui="sidebar-conversation"][data-drop-edge]::after { content: ""; position: absolute; left: .5rem; right: .5rem; height: 2px; background: hsl(var(--brand)); pointer-events: none; }
|
|
62
|
+
[data-chat-ui="sidebar-conversation"][data-drop-edge="before"]::after { top: 0; }
|
|
63
|
+
[data-chat-ui="sidebar-conversation"][data-drop-edge="after"]::after { bottom: 0; }
|
|
64
|
+
.ui-composer-toolbar { display: flex; align-items: center; gap: .375rem; min-width: 0; }
|
|
65
|
+
.ui-composer-tool { display: inline-flex; width: 30px; height: 30px; flex-shrink: 0; align-items: center; justify-content: center; border-radius: 50%; color: hsl(var(--muted-foreground)); }
|
|
66
|
+
.ui-composer-tool svg, .ui-composer-menu svg, .ui-composer-option label svg { width: 17px; height: 17px; flex-shrink: 0; }
|
|
67
|
+
.ui-composer-tool:hover, .ui-composer-tool[aria-pressed="true"] { background: hsl(var(--muted)); color: hsl(var(--foreground)); }
|
|
68
|
+
.ui-composer-tool:disabled { opacity: .4; cursor: not-allowed; }
|
|
69
|
+
.ui-composer-tool:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
|
|
70
|
+
.ui-composer-shortcuts { display: flex; align-items: center; gap: .25rem; min-width: 0; overflow-x: auto; padding: 3px; }
|
|
71
|
+
.ui-composer-menu { width: 16rem; border-radius: .5rem; }
|
|
72
|
+
.ui-composer-menu .ui-dropdown-item { gap: .625rem; padding: .5rem .625rem; }
|
|
73
|
+
.ui-composer-menu small { display: block; margin-top: .125rem; color: hsl(var(--muted-foreground)); font-size: .6875rem; }
|
|
74
|
+
.ui-composer-customize { max-width: 28rem; border-radius: .5rem; }
|
|
75
|
+
.ui-composer-customize header, .ui-composer-customize footer { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
|
|
76
|
+
.ui-composer-customize footer { justify-content: flex-end; }
|
|
77
|
+
.ui-composer-option { display: flex; align-items: center; gap: .5rem; min-height: 44px; padding-block: .25rem; border-bottom: 1px solid hsl(var(--border)); }
|
|
78
|
+
.ui-composer-option label { display: flex; align-items: center; gap: .625rem; min-width: 0; flex: 1; font-size: .875rem; overflow-wrap: anywhere; }
|
|
79
|
+
.ui-composer-option input { width: 16px; height: 16px; flex-shrink: 0; accent-color: hsl(var(--brand)); }
|
|
80
|
+
.ui-switch {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
width: 2.25rem;
|
|
83
|
+
height: 1.25rem;
|
|
84
|
+
flex: none;
|
|
85
|
+
align-items: center;
|
|
86
|
+
padding: 2px;
|
|
87
|
+
border: 0;
|
|
88
|
+
border-radius: 999px;
|
|
89
|
+
background: hsl(var(--input));
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
transition: background-color 150ms ease;
|
|
92
|
+
}
|
|
93
|
+
.ui-switch[data-state="checked"] { background: hsl(var(--primary)); }
|
|
94
|
+
.ui-switch-thumb {
|
|
95
|
+
width: 1rem;
|
|
96
|
+
height: 1rem;
|
|
97
|
+
border-radius: 50%;
|
|
98
|
+
background: hsl(var(--background));
|
|
99
|
+
box-shadow: 0 1px 2px #0002;
|
|
100
|
+
transition: transform 150ms ease;
|
|
101
|
+
}
|
|
102
|
+
.ui-switch-thumb[data-state="checked"] { transform: translateX(1rem); }
|
|
103
|
+
.ui-switch[dir="rtl"] .ui-switch-thumb[data-state="checked"] { transform: translateX(-1rem); }
|
|
104
|
+
.ui-slider {
|
|
105
|
+
width: 100%;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
height: 1.5rem;
|
|
108
|
+
accent-color: hsl(var(--primary));
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
}
|
|
111
|
+
.ui-switch:disabled, .ui-slider:disabled { cursor: not-allowed; opacity: .5; }
|
|
112
|
+
.ui-progress {
|
|
113
|
+
display: block;
|
|
114
|
+
width: 100%;
|
|
115
|
+
height: .375rem;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
border: 0;
|
|
118
|
+
border-radius: 999px;
|
|
119
|
+
appearance: none;
|
|
120
|
+
background: hsl(var(--muted));
|
|
121
|
+
color: hsl(var(--primary));
|
|
122
|
+
accent-color: hsl(var(--primary));
|
|
123
|
+
}
|
|
124
|
+
.ui-progress::-webkit-progress-bar { border-radius: inherit; background: hsl(var(--muted)); }
|
|
125
|
+
.ui-progress::-webkit-progress-value { border-radius: inherit; background: hsl(var(--primary)); }
|
|
126
|
+
.ui-progress::-moz-progress-bar { border-radius: inherit; background: hsl(var(--primary)); }
|
|
127
|
+
.ui-progress:indeterminate { appearance: auto; }
|
|
128
|
+
.ui-skeleton { min-height: 1rem; border-radius: .375rem; background: hsl(var(--muted)); animation: tp-skeleton-pulse 2s ease-in-out infinite; }
|
|
129
|
+
.ui-avatar {
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
position: relative;
|
|
132
|
+
flex: none;
|
|
133
|
+
width: 2.25rem;
|
|
134
|
+
height: 2.25rem;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
border-radius: 50%;
|
|
137
|
+
vertical-align: middle;
|
|
138
|
+
}
|
|
139
|
+
.ui-avatar-image { width: 100%; height: 100%; object-fit: cover; }
|
|
140
|
+
.ui-avatar-fallback { display: grid; place-items: center; width: 100%; height: 100%; background: hsl(var(--muted)); color: hsl(var(--muted-foreground)); font-size: .75rem; font-weight: 500; }
|
|
141
|
+
.ui-tabs-list { display: inline-flex; max-width: 100%; align-items: center; gap: .25rem; padding: .25rem; border-radius: .5rem; background: hsl(var(--muted)); overflow-x: auto; }
|
|
142
|
+
.ui-tabs-trigger { flex: 1 0 auto; border: 0; border-radius: .375rem; padding: .375rem .75rem; background: transparent; color: hsl(var(--muted-foreground)); font-size: .875rem; font-weight: 500; cursor: pointer; }
|
|
143
|
+
.ui-tabs-trigger[data-state="active"] { background: hsl(var(--background)); color: hsl(var(--foreground)); box-shadow: 0 1px 3px #0001; }
|
|
144
|
+
.ui-tabs-trigger:disabled { opacity: .5; cursor: not-allowed; }
|
|
145
|
+
.ui-tabs-content { margin-top: 1rem; min-width: 0; }
|
|
146
|
+
.ui-accordion-item { border-bottom: 1px solid hsl(var(--border)); }
|
|
147
|
+
.ui-accordion-item:last-child { border-bottom: 0; }
|
|
148
|
+
.ui-accordion-header { margin: 0; }
|
|
149
|
+
.ui-accordion-trigger { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 0; border: 0; background: transparent; color: hsl(var(--foreground)); font-size: .875rem; font-weight: 500; text-align: start; cursor: pointer; }
|
|
150
|
+
.ui-accordion-trigger:hover { text-decoration: underline; text-underline-offset: 4px; }
|
|
151
|
+
.ui-accordion-trigger:disabled { opacity: .5; cursor: not-allowed; }
|
|
152
|
+
.ui-accordion-chevron { width: 1rem; height: 1rem; flex: none; color: hsl(var(--muted-foreground)); transition: transform 150ms ease; }
|
|
153
|
+
.ui-accordion-trigger[data-state="open"] .ui-accordion-chevron { transform: rotate(180deg); }
|
|
154
|
+
.ui-accordion-content { padding-bottom: 1rem; color: hsl(var(--muted-foreground)); font-size: .875rem; line-height: 1.65; overflow-wrap: anywhere; }
|
|
155
|
+
.ui-dropdown-menu { z-index: 60; min-width: 12rem; max-width: calc(100vw - 1rem); max-height: var(--radix-dropdown-menu-content-available-height); overflow-y: auto; border: 1px solid hsl(var(--border)); border-radius: .5rem; background: hsl(var(--popover)); color: hsl(var(--popover-foreground)); padding: .25rem; box-shadow: 0 4px 12px #0002; }
|
|
156
|
+
.ui-dropdown-item { display: flex; min-height: 2rem; align-items: center; gap: .5rem; padding: .375rem .5rem; border-radius: .25rem; font-size: .875rem; outline: none; cursor: default; user-select: none; }
|
|
157
|
+
.ui-dropdown-item[data-highlighted] { background: hsl(var(--accent)); }
|
|
158
|
+
.ui-dropdown-item[data-disabled] { opacity: .5; pointer-events: none; }
|
|
159
|
+
.ui-dropdown-label { padding: .375rem .5rem; font-size: .75rem; font-weight: 500; color: hsl(var(--muted-foreground)); }
|
|
160
|
+
.ui-dropdown-separator { height: 1px; margin: .25rem -.25rem; background: hsl(var(--border)); }
|
|
161
|
+
:is(.ui-switch, .ui-slider, .ui-tabs-trigger, .ui-tabs-content, .ui-accordion-trigger):focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 3px; }
|
|
162
|
+
@media (pointer: coarse) {
|
|
163
|
+
.ui-switch { position: relative; }
|
|
164
|
+
.ui-switch::before { content: ""; position: absolute; inset: -.75rem -.25rem; }
|
|
165
|
+
.ui-slider, .ui-tabs-trigger, .ui-dropdown-item { min-height: 2.75rem; }
|
|
166
|
+
}
|
|
167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
168
|
+
.ui-skeleton { animation: none; }
|
|
169
|
+
.ui-switch, .ui-switch-thumb, .ui-accordion-chevron { transition: none; }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@keyframes tp-skeleton-pulse { 50% { opacity: .45; } }
|
|
174
|
+
|
|
10
175
|
@custom-variant dark (&:is(.dark *));
|
|
11
176
|
|
|
12
177
|
@theme inline {
|
|
@@ -360,6 +525,16 @@
|
|
|
360
525
|
background: hsl(var(--accent) / 0.62);
|
|
361
526
|
}
|
|
362
527
|
|
|
528
|
+
.ui-table__selection {
|
|
529
|
+
width: 2.75rem;
|
|
530
|
+
padding: 0.75rem 0.875rem;
|
|
531
|
+
text-align: center;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.ui-table__checkbox {
|
|
535
|
+
margin: 0 auto;
|
|
536
|
+
}
|
|
537
|
+
|
|
363
538
|
.ui-button-primary,
|
|
364
539
|
.ui-button-secondary,
|
|
365
540
|
.ui-button-ghost {
|
|
@@ -420,6 +595,16 @@
|
|
|
420
595
|
color: hsl(var(--destructive-foreground));
|
|
421
596
|
}
|
|
422
597
|
|
|
598
|
+
.ui-button-outline {
|
|
599
|
+
border-color: hsl(var(--border));
|
|
600
|
+
background: hsl(var(--background));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.ui-button-outline:hover:not(:disabled) {
|
|
604
|
+
border-color: hsl(var(--border));
|
|
605
|
+
background: hsl(var(--accent));
|
|
606
|
+
}
|
|
607
|
+
|
|
423
608
|
.ui-button-danger:hover:not(:disabled) {
|
|
424
609
|
border-color: color-mix(in hsl, hsl(var(--destructive)) 88%, black 12%);
|
|
425
610
|
background: color-mix(in hsl, hsl(var(--destructive)) 88%, black 12%);
|