@asharca/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 asharca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @asharca/ui
2
+
3
+ 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
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @asharca/ui
9
+ # or: npm install @asharca/ui
10
+ ```
11
+
12
+ 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
+
14
+ Import the stylesheet once from the host application's global Tailwind stylesheet:
15
+
16
+ ```css
17
+ @import "tailwindcss";
18
+ @import "@asharca/ui/styles.css";
19
+ ```
20
+
21
+ The package stylesheet uses Tailwind's `@source` directive to scan the emitted `dist` files. Importing only the React components will leave their utility classes ungenerated.
22
+
23
+ ## Controls
24
+
25
+ Import lightweight controls without loading the chat runtime:
26
+
27
+ ```tsx
28
+ import { useState } from 'react';
29
+ import { Plus } from 'lucide-react';
30
+ import { Button, IconButton, Input, SearchInput } from '@asharca/ui/controls';
31
+
32
+ export function Toolbar() {
33
+ const [query, setQuery] = useState('');
34
+ return <>
35
+ <Button variant="primary">Save</Button>
36
+ <IconButton icon={<Plus />} label="Add item" />
37
+ <Input name="title" aria-label="Title" />
38
+ <SearchInput
39
+ value={query}
40
+ label="Search conversations"
41
+ onChange={(event) => setQuery(event.target.value)}
42
+ onClear={() => setQuery('')}
43
+ />
44
+ </>;
45
+ }
46
+ ```
47
+
48
+ `Button` supports `primary`, `secondary`, `ghost`, `danger`, and `danger-secondary` variants, `sm`/`md`/`lg` sizes, a loading state, and `asChild` for framework links. Controls also include `Textarea`, `Select`/`NativeSelect`, `Checkbox`, `Radio`, and field labels/descriptions/errors. All controls forward native props and refs.
49
+
50
+ ## Modules
51
+
52
+ - `@asharca/ui/controls` — buttons and native form controls.
53
+ - `@asharca/ui/forms` — submit, confirm-submit, and copy actions.
54
+ - `@asharca/ui/layout` — page, header, toolbar, section, panel, card, empty state, entity, and data table.
55
+ - `@asharca/ui/navigation` — tabs, chips, and pagination layout.
56
+ - `@asharca/ui/feedback` — badges, status, alerts, and spinners.
57
+ - `@asharca/ui/dialog` and `@asharca/ui/overlays` — Dialog, Popover, Tooltip, Context Menu, and Hover Card primitives.
58
+ - `@asharca/ui/chat-shell`, `@asharca/ui/chat-thread`, and `@asharca/ui/conversation-sidebar` — chat composition.
59
+ - The root export also includes `ToolPlaneLogo`, `ContentPage`, `RotatingHeadline`, `SafeStreamdown`, `Breadcrumbs`, `NavigationTabs`, and `WorkspaceTabBar`.
60
+
61
+ Next.js routing, translations, server actions, authentication, and domain data stay in the host. Pass those through children, labels, callbacks, and thin adapters.
62
+
63
+ ## Example
64
+
65
+ ```tsx
66
+ 'use client';
67
+
68
+ import { useState } from 'react';
69
+ import {
70
+ ChatShell,
71
+ ChatThread,
72
+ type ChatThreadProps,
73
+ ConversationSidebar,
74
+ } from '@asharca/ui';
75
+
76
+ export function ChatPage({ runtime }: { runtime: ChatThreadProps['runtime'] }) {
77
+ const [sidebarOpen, setSidebarOpen] = useState(true);
78
+ const [mobilePane, setMobilePane] = useState<'sidebar' | 'chat'>('sidebar');
79
+
80
+ return (
81
+ <div className="h-dvh">
82
+ <ChatShell
83
+ sidebar={(
84
+ <ConversationSidebar
85
+ groups={[]}
86
+ onSelectConversation={() => setMobilePane('chat')}
87
+ />
88
+ )}
89
+ header={<strong>Support assistant</strong>}
90
+ sidebarOpen={sidebarOpen}
91
+ onSidebarOpenChange={setSidebarOpen}
92
+ mobilePane={mobilePane}
93
+ onMobilePaneChange={setMobilePane}
94
+ rightPanel={<div>Optional inspector</div>}
95
+ >
96
+ <ChatThread
97
+ runtime={runtime}
98
+ assistantName="Support assistant"
99
+ />
100
+ </ChatShell>
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ Build the runtime with AI SDK, assistant-ui local runtime, or another adapter in the host. `sidebarOpen` controls the desktop column. `mobilePane` controls whether narrow screens show the sidebar or the chat. Set `rightPanelOpen={false}` to keep the optional desktop right panel closed, and pass `sidebarLabel` when the sidebar needs a named complementary landmark.
107
+
108
+ `ChatThread` accepts an optional `components` map for host-specific rendering without replacing the thread layout. Use `AssistantText` for a custom Markdown renderer, `AssistantMessageBefore` / `AssistantMessageAfter` for per-message context, `AssistantActions` for extra message actions, and `SentAttachment` for a host preview flow.
109
+
110
+ `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.
111
+
112
+ 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.
113
+
114
+ ## Build and pack
115
+
116
+ ```bash
117
+ pnpm --filter @asharca/ui build
118
+ pnpm --dir packages/ui pack
119
+ ```
120
+
121
+ ## Release
122
+
123
+ This package is versioned independently from the ToolPlane application, like
124
+ `packages/ai` in the Pi monorepo. To release a new version, update
125
+ `packages/ui/package.json`, merge it, and create a matching `ui-vX.Y.Z` tag.
126
+ The `publish-ui.yml` workflow builds and publishes that exact version to npm.
127
+
128
+ Publishing requires access to the `@asharca` npm scope. Configure npm trusted
129
+ publishing for `asharca/ToolPlane` and `publish-ui.yml`, allowing direct
130
+ publishing with `npm publish`. The workflow uses OIDC rather than a long-lived
131
+ npm token.
132
+
133
+ ## License
134
+
135
+ MIT. See [LICENSE](./LICENSE). This license applies only to `packages/ui`, not
136
+ to the rest of the ToolPlane repository.
@@ -0,0 +1,6 @@
1
+ export type ToolPlaneLogoProps = {
2
+ svgSize?: number;
3
+ wordmarkClass?: string;
4
+ hideWordmarkOnMobile?: boolean;
5
+ };
6
+ export declare function ToolPlaneLogo({ svgSize, wordmarkClass, hideWordmarkOnMobile, }: ToolPlaneLogoProps): import("react").JSX.Element;
package/dist/Brand.js ADDED
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Layers3 } from 'lucide-react';
3
+ export function ToolPlaneLogo({ svgSize = 28, wordmarkClass = 'text-2xl', hideWordmarkOnMobile = false, }) {
4
+ return (_jsxs("span", { "data-toolplane-ui": "logo", className: "inline-flex items-center gap-2", children: [_jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand ring-1 ring-inset ring-brand/15 transition-colors group-hover:bg-brand group-hover:text-brand-foreground", style: { width: svgSize, height: svgSize }, children: _jsx(Layers3, { size: Math.round(svgSize * 0.57), strokeWidth: 1.9 }) }), _jsxs("span", { className: `${hideWordmarkOnMobile ? 'hidden sm:inline' : 'inline'} whitespace-nowrap font-sans font-semibold text-foreground ${wordmarkClass}`, children: ["Tool", _jsx("span", { className: "font-medium text-muted-foreground", children: "Plane" })] })] }));
5
+ }
@@ -0,0 +1,21 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type ChatShellMobilePane = 'sidebar' | 'chat';
3
+ export type ChatShellLabels = {
4
+ showSidebar: string;
5
+ hideSidebar: string;
6
+ };
7
+ export declare const chatShellDefaultLabels: ChatShellLabels;
8
+ export type ChatShellProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
9
+ sidebar: ReactNode;
10
+ sidebarLabel?: string;
11
+ header?: ReactNode;
12
+ children: ReactNode;
13
+ rightPanel?: ReactNode;
14
+ sidebarOpen: boolean;
15
+ onSidebarOpenChange: (open: boolean) => void;
16
+ mobilePane: ChatShellMobilePane;
17
+ onMobilePaneChange: (pane: ChatShellMobilePane) => void;
18
+ rightPanelOpen?: boolean;
19
+ labels?: Partial<ChatShellLabels>;
20
+ };
21
+ export declare function ChatShell({ sidebar, sidebarLabel, header, children, rightPanel, sidebarOpen, onSidebarOpenChange, mobilePane, onMobilePaneChange, rightPanelOpen, labels, className, ...props }: ChatShellProps): import("react").JSX.Element;
@@ -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 { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
15
+ import { useId } from 'react';
16
+ import { IconButton } from "./Controls.js";
17
+ export const chatShellDefaultLabels = {
18
+ showSidebar: 'Show conversations',
19
+ hideSidebar: 'Hide conversations',
20
+ };
21
+ export function ChatShell(_a) {
22
+ var { sidebar, sidebarLabel, header, children, rightPanel, sidebarOpen, onSidebarOpenChange, mobilePane, onMobilePaneChange, rightPanelOpen = true, labels, className } = _a, props = __rest(_a, ["sidebar", "sidebarLabel", "header", "children", "rightPanel", "sidebarOpen", "onSidebarOpenChange", "mobilePane", "onMobilePaneChange", "rightPanelOpen", "labels", "className"]);
23
+ const copy = Object.assign(Object.assign({}, chatShellDefaultLabels), labels);
24
+ const showRightPanel = Boolean(rightPanel && rightPanelOpen);
25
+ const sidebarId = useId();
26
+ return (_jsx("div", Object.assign({}, props, { "data-chat-ui": "chat-shell", "data-mobile-pane": mobilePane, "data-right-panel-open": showRightPanel, "data-sidebar-open": sidebarOpen, className: `tp-chat-shell ${className !== null && className !== void 0 ? className : ''}`.trim(), children: _jsxs("div", { className: "tp-chat-shell__grid", children: [_jsxs("div", { id: sidebarId, className: "tp-chat-shell__sidebar", role: sidebarLabel ? 'complementary' : undefined, "aria-label": sidebarLabel, children: [_jsx("div", { className: "tp-chat-shell__sidebar-mobile-toolbar", children: _jsx(IconButton, { icon: _jsx(PanelLeftClose, { className: "tp-chat-shell__toggle-icon" }), label: copy.hideSidebar, size: "sm", variant: "ghost", "aria-controls": sidebarId, "aria-expanded": true, className: "tp-chat-shell__toggle", onClick: () => onMobilePaneChange('chat') }) }), _jsx("div", { className: "tp-chat-shell__sidebar-slot", children: sidebar })] }), _jsxs("section", { className: "tp-chat-shell__main", children: [_jsxs("div", { className: "tp-chat-shell__header", children: [_jsx(IconButton, { icon: sidebarOpen
27
+ ? _jsx(PanelLeftClose, { className: "tp-chat-shell__toggle-icon" })
28
+ : _jsx(PanelLeftOpen, { className: "tp-chat-shell__toggle-icon" }), label: sidebarOpen ? copy.hideSidebar : copy.showSidebar, size: "sm", variant: "ghost", "aria-controls": sidebarId, "aria-expanded": sidebarOpen, className: "tp-chat-shell__toggle tp-chat-shell__toggle--desktop", onClick: () => onSidebarOpenChange(!sidebarOpen) }), _jsx(IconButton, { icon: _jsx(PanelLeftOpen, { className: "tp-chat-shell__toggle-icon" }), label: copy.showSidebar, size: "sm", variant: "ghost", "aria-controls": sidebarId, "aria-expanded": mobilePane === 'sidebar', className: "tp-chat-shell__toggle tp-chat-shell__toggle--mobile", onClick: () => onMobilePaneChange('sidebar') }), _jsx("div", { className: "tp-chat-shell__header-slot", children: header })] }), _jsx("div", { className: "tp-chat-shell__content", children: children })] }), showRightPanel ? (_jsx("aside", { className: "tp-chat-shell__right-panel", children: rightPanel })) : null] }) })));
29
+ }
@@ -0,0 +1,96 @@
1
+ import { type ComponentType, type ReactNode } from 'react';
2
+ import { type AssistantRuntime, type CompleteAttachment, type TextMessagePartProps } from '@assistant-ui/react';
3
+ export type ChatBranchNavigation = {
4
+ messageId: string;
5
+ position: number;
6
+ total: number;
7
+ previousMessageId: string;
8
+ nextMessageId: string;
9
+ };
10
+ export type ChatThreadLabels = {
11
+ addAttachment: string;
12
+ allowTool: string;
13
+ attachment: string;
14
+ attachmentsUnavailable: string;
15
+ cancel: string;
16
+ composerTools: string;
17
+ conversationBranch: string;
18
+ copy: string;
19
+ edit: string;
20
+ expandComposer: string;
21
+ generatingReply: string;
22
+ messagePlaceholder: string;
23
+ next: string;
24
+ openComposerTools: string;
25
+ preparingReply: string;
26
+ previous: string;
27
+ processFailed: string;
28
+ processed: string;
29
+ processing: string;
30
+ regenerate: string;
31
+ rejectTool: string;
32
+ removeAttachment: (name: string) => string;
33
+ restoreComposer: string;
34
+ save: string;
35
+ scrollToLatestMessage: string;
36
+ send: string;
37
+ startBranch: string;
38
+ startConversation: string;
39
+ stop: string;
40
+ thinking: string;
41
+ thought: string;
42
+ toolApprovalDescription: string;
43
+ toolAwaitingApproval: string;
44
+ toolCompleted: string;
45
+ toolFailed: string;
46
+ toolInput: string;
47
+ toolKindMcp: string;
48
+ toolKindSandbox: string;
49
+ toolKindSkill: string;
50
+ toolKindSubagent: string;
51
+ toolKindTool: string;
52
+ toolKindWeb: string;
53
+ toolOutput: string;
54
+ toolRunning: string;
55
+ user: string;
56
+ usingTool: (toolName: string) => string;
57
+ };
58
+ export declare const chatThreadDefaultLabels: ChatThreadLabels;
59
+ export type ChatThreadProps = {
60
+ runtime: AssistantRuntime;
61
+ assistantName: string;
62
+ allowAttachments?: boolean;
63
+ allowEdit?: boolean;
64
+ allowRegenerate?: boolean;
65
+ branchNavigation?: readonly ChatBranchNavigation[];
66
+ busy?: boolean;
67
+ className?: string;
68
+ components?: ChatThreadComponents;
69
+ composerEnd?: ReactNode;
70
+ composerStatus?: ReactNode;
71
+ composerTools?: ReactNode;
72
+ disabled?: boolean;
73
+ emptyState?: ReactNode;
74
+ error?: ReactNode;
75
+ labels?: Partial<ChatThreadLabels>;
76
+ onBranchSelect?: (messageId: string) => void | Promise<void>;
77
+ onBranchStart?: (messageId: string) => void | Promise<void>;
78
+ onRegenerateMessage?: (messageId: string) => void | Promise<void>;
79
+ transformUserText?: (text: string) => string;
80
+ };
81
+ export type ChatThreadComponents = {
82
+ AssistantText?: ComponentType<TextMessagePartProps>;
83
+ AssistantMessageBefore?: ComponentType<{
84
+ messageId: string;
85
+ }>;
86
+ AssistantMessageAfter?: ComponentType<{
87
+ messageId: string;
88
+ }>;
89
+ AssistantActions?: ComponentType<{
90
+ messageId: string;
91
+ }>;
92
+ SentAttachment?: ComponentType<{
93
+ attachment: CompleteAttachment;
94
+ }>;
95
+ };
96
+ export declare function ChatThread({ runtime, assistantName, allowAttachments, allowEdit, allowRegenerate, branchNavigation, busy, disabled, labels: labelOverrides, transformUserText, ...props }: ChatThreadProps): import("react").JSX.Element;