@townco/ui 0.1.7 → 0.1.8

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.
@@ -4,3 +4,4 @@
4
4
  export * from "./use-chat-input.js";
5
5
  export * from "./use-chat-messages.js";
6
6
  export * from "./use-chat-session.js";
7
+ export * from "./use-media-query.js";
@@ -4,3 +4,4 @@
4
4
  export * from "./use-chat-input.js";
5
5
  export * from "./use-chat-messages.js";
6
6
  export * from "./use-chat-session.js";
7
+ export * from "./use-media-query.js";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Custom hook for responsive breakpoints with SSR-safe implementation
3
+ *
4
+ * @param query - Media query string (e.g., "(min-width: 768px)")
5
+ * @returns boolean indicating if the media query matches
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const isMobile = useMediaQuery("(max-width: 640px)");
10
+ * const isDesktop = useMediaQuery("(min-width: 1024px)");
11
+ *
12
+ * return (
13
+ * <div>
14
+ * {isMobile && <MobileMenu />}
15
+ * {isDesktop && <DesktopSidebar />}
16
+ * </div>
17
+ * );
18
+ * ```
19
+ */
20
+ export declare function useMediaQuery(query: string): boolean;
21
+ /**
22
+ * Common breakpoint presets for convenience
23
+ */
24
+ export declare const breakpoints: {
25
+ readonly sm: "(min-width: 640px)";
26
+ readonly md: "(min-width: 768px)";
27
+ readonly lg: "(min-width: 1024px)";
28
+ readonly xl: "(min-width: 1280px)";
29
+ readonly "2xl": "(min-width: 1536px)";
30
+ };
31
+ /**
32
+ * Convenience hooks for common breakpoints
33
+ */
34
+ export declare function useIsSmallScreen(): boolean;
35
+ export declare function useIsMediumScreen(): boolean;
36
+ export declare function useIsLargeScreen(): boolean;
37
+ export declare function useIsDesktop(): boolean;
38
+ export declare function useIsMobile(): boolean;
39
+ export declare function useIsTablet(): boolean;
@@ -0,0 +1,84 @@
1
+ import { useEffect, useState } from "react";
2
+ /**
3
+ * Custom hook for responsive breakpoints with SSR-safe implementation
4
+ *
5
+ * @param query - Media query string (e.g., "(min-width: 768px)")
6
+ * @returns boolean indicating if the media query matches
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const isMobile = useMediaQuery("(max-width: 640px)");
11
+ * const isDesktop = useMediaQuery("(min-width: 1024px)");
12
+ *
13
+ * return (
14
+ * <div>
15
+ * {isMobile && <MobileMenu />}
16
+ * {isDesktop && <DesktopSidebar />}
17
+ * </div>
18
+ * );
19
+ * ```
20
+ */
21
+ export function useMediaQuery(query) {
22
+ const [matches, setMatches] = useState(() => {
23
+ // SSR-safe: return false on server, check on client
24
+ if (typeof window === "undefined") {
25
+ return false;
26
+ }
27
+ return window.matchMedia(query).matches;
28
+ });
29
+ useEffect(() => {
30
+ // SSR guard
31
+ if (typeof window === "undefined") {
32
+ return;
33
+ }
34
+ const mediaQueryList = window.matchMedia(query);
35
+ // Update state if query evaluation changes
36
+ const handleChange = (event) => {
37
+ setMatches(event.matches);
38
+ };
39
+ // Set initial value
40
+ setMatches(mediaQueryList.matches);
41
+ // Listen for changes
42
+ mediaQueryList.addEventListener("change", handleChange);
43
+ // Cleanup
44
+ return () => {
45
+ mediaQueryList.removeEventListener("change", handleChange);
46
+ };
47
+ }, [query]);
48
+ return matches;
49
+ }
50
+ /**
51
+ * Common breakpoint presets for convenience
52
+ */
53
+ export const breakpoints = {
54
+ sm: "(min-width: 640px)",
55
+ md: "(min-width: 768px)",
56
+ lg: "(min-width: 1024px)",
57
+ xl: "(min-width: 1280px)",
58
+ "2xl": "(min-width: 1536px)",
59
+ };
60
+ /**
61
+ * Convenience hooks for common breakpoints
62
+ */
63
+ export function useIsSmallScreen() {
64
+ const isSm = useMediaQuery(breakpoints.sm);
65
+ return !isSm;
66
+ }
67
+ export function useIsMediumScreen() {
68
+ return useMediaQuery(breakpoints.md);
69
+ }
70
+ export function useIsLargeScreen() {
71
+ return useMediaQuery(breakpoints.lg);
72
+ }
73
+ export function useIsDesktop() {
74
+ return useMediaQuery(breakpoints.lg);
75
+ }
76
+ export function useIsMobile() {
77
+ const isMd = useMediaQuery(breakpoints.md);
78
+ return !isMd;
79
+ }
80
+ export function useIsTablet() {
81
+ const isMd = useMediaQuery(breakpoints.md);
82
+ const isLg = useMediaQuery(breakpoints.lg);
83
+ return isMd && !isLg;
84
+ }
@@ -0,0 +1,38 @@
1
+ import * as React from "react";
2
+ interface ChatHeaderContextValue {
3
+ isExpanded: boolean;
4
+ setIsExpanded: (expanded: boolean) => void;
5
+ }
6
+ declare const useChatHeaderContext: () => ChatHeaderContextValue;
7
+ export interface ChatHeaderRootProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ /** Initial expanded state */
9
+ defaultExpanded?: boolean;
10
+ /** Controlled expanded state */
11
+ expanded?: boolean;
12
+ /** Callback when expanded state changes */
13
+ onExpandedChange?: (expanded: boolean) => void;
14
+ }
15
+ declare const ChatHeaderRoot: React.ForwardRefExoticComponent<ChatHeaderRootProps & React.RefAttributes<HTMLDivElement>>;
16
+ export interface ChatHeaderTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
17
+ }
18
+ declare const ChatHeaderTitle: React.ForwardRefExoticComponent<ChatHeaderTitleProps & React.RefAttributes<HTMLHeadingElement>>;
19
+ export interface ChatHeaderActionsProps extends React.HTMLAttributes<HTMLDivElement> {
20
+ }
21
+ declare const ChatHeaderActions: React.ForwardRefExoticComponent<ChatHeaderActionsProps & React.RefAttributes<HTMLDivElement>>;
22
+ export type ConnectionStatus = "connected" | "connecting" | "error" | "disconnected";
23
+ export interface ChatHeaderStatusIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
24
+ /** Connection status */
25
+ status: ConnectionStatus;
26
+ /** Optional status text override */
27
+ statusText?: string;
28
+ }
29
+ declare const ChatHeaderStatusIndicator: React.ForwardRefExoticComponent<ChatHeaderStatusIndicatorProps & React.RefAttributes<HTMLDivElement>>;
30
+ export interface ChatHeaderToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
31
+ /** Icon to display (should rotate based on expanded state) */
32
+ icon?: React.ReactNode;
33
+ }
34
+ declare const ChatHeaderToggle: React.ForwardRefExoticComponent<ChatHeaderToggleProps & React.RefAttributes<HTMLButtonElement>>;
35
+ export interface ChatHeaderExpandablePanelProps extends React.HTMLAttributes<HTMLDivElement> {
36
+ }
37
+ declare const ChatHeaderExpandablePanel: React.ForwardRefExoticComponent<ChatHeaderExpandablePanelProps & React.RefAttributes<HTMLDivElement>>;
38
+ export { ChatHeaderRoot as Root, ChatHeaderTitle as Title, ChatHeaderActions as Actions, ChatHeaderStatusIndicator as StatusIndicator, ChatHeaderToggle as Toggle, ChatHeaderExpandablePanel as ExpandablePanel, useChatHeaderContext, };
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ const ChatHeaderContext = React.createContext(undefined);
5
+ const useChatHeaderContext = () => {
6
+ const context = React.useContext(ChatHeaderContext);
7
+ if (!context) {
8
+ throw new Error("ChatHeader components must be used within ChatHeader.Root");
9
+ }
10
+ return context;
11
+ };
12
+ const ChatHeaderRoot = React.forwardRef(({ defaultExpanded = false, expanded: expandedProp, onExpandedChange, className, children, ...props }, ref) => {
13
+ const [isExpandedInternal, setIsExpandedInternal] = React.useState(defaultExpanded);
14
+ const isExpanded = expandedProp ?? isExpandedInternal;
15
+ const setIsExpanded = React.useCallback((expanded) => {
16
+ setIsExpandedInternal(expanded);
17
+ onExpandedChange?.(expanded);
18
+ }, [onExpandedChange]);
19
+ // Separate children into main content and expandable panel
20
+ const childrenArray = React.Children.toArray(children);
21
+ const expandablePanel = childrenArray.find((child) => React.isValidElement(child) &&
22
+ typeof child.type === "function" &&
23
+ child.type.displayName ===
24
+ "ChatHeader.ExpandablePanel");
25
+ const mainContent = childrenArray.filter((child) => child !== expandablePanel);
26
+ return (_jsxs(ChatHeaderContext.Provider, { value: { isExpanded, setIsExpanded }, children: [_jsx("div", { ref: ref, className: cn("flex items-center justify-between px-6 py-4", className), ...props, children: mainContent }), expandablePanel] }));
27
+ });
28
+ ChatHeaderRoot.displayName = "ChatHeader.Root";
29
+ const ChatHeaderTitle = React.forwardRef(({ className, children, ...props }, ref) => {
30
+ return (_jsx("h1", { ref: ref, className: cn("m-0 text-xl font-semibold", className), ...props, children: children }));
31
+ });
32
+ ChatHeaderTitle.displayName = "ChatHeader.Title";
33
+ const ChatHeaderActions = React.forwardRef(({ className, children, ...props }, ref) => {
34
+ return (_jsx("div", { ref: ref, className: cn("flex items-center gap-3", className), ...props, children: children }));
35
+ });
36
+ ChatHeaderActions.displayName = "ChatHeader.Actions";
37
+ const getStatusColor = (status) => {
38
+ switch (status) {
39
+ case "connected":
40
+ return "bg-green-500";
41
+ case "connecting":
42
+ return "bg-yellow-500";
43
+ case "error":
44
+ return "bg-red-500";
45
+ default:
46
+ return "bg-gray-500";
47
+ }
48
+ };
49
+ const getDefaultStatusText = (status) => {
50
+ switch (status) {
51
+ case "connected":
52
+ return "Connected";
53
+ case "connecting":
54
+ return "Connecting...";
55
+ case "error":
56
+ return "Connection Error";
57
+ default:
58
+ return "No Server";
59
+ }
60
+ };
61
+ const ChatHeaderStatusIndicator = React.forwardRef(({ status, statusText, className, ...props }, ref) => {
62
+ const text = statusText ?? getDefaultStatusText(status);
63
+ const colorClass = getStatusColor(status);
64
+ return (_jsxs("div", { ref: ref, className: cn("flex items-center gap-2", className), ...props, children: [_jsx("div", { className: cn("h-2 w-2 rounded-full", colorClass) }), _jsx("span", { className: "text-sm text-muted-foreground", children: text })] }));
65
+ });
66
+ ChatHeaderStatusIndicator.displayName = "ChatHeader.StatusIndicator";
67
+ const ChatHeaderToggle = React.forwardRef(({ icon, className, children, onClick, ...props }, ref) => {
68
+ const { isExpanded, setIsExpanded } = useChatHeaderContext();
69
+ const handleClick = (e) => {
70
+ setIsExpanded(!isExpanded);
71
+ onClick?.(e);
72
+ };
73
+ return (_jsxs("button", { ref: ref, type: "button", onClick: handleClick, className: cn("rounded p-1 transition-colors hover:bg-background lg:hidden", className), "aria-label": isExpanded ? "Collapse header" : "Expand header", ...props, children: [icon && (_jsx("div", { className: cn("transition-transform duration-200", isExpanded && "rotate-180"), children: icon })), children] }));
74
+ });
75
+ ChatHeaderToggle.displayName = "ChatHeader.Toggle";
76
+ const ChatHeaderExpandablePanel = React.forwardRef(({ className, children, ...props }, ref) => {
77
+ const { isExpanded } = useChatHeaderContext();
78
+ if (!isExpanded)
79
+ return null;
80
+ return (_jsx("div", { ref: ref, className: cn("absolute top-full left-0 right-0 z-50 border-b border-border bg-card px-6 py-4 shadow-lg lg:hidden", className), ...props, children: children }));
81
+ });
82
+ ChatHeaderExpandablePanel.displayName = "ChatHeader.ExpandablePanel";
83
+ /* -------------------------------------------------------------------------------------------------
84
+ * Exports
85
+ * -----------------------------------------------------------------------------------------------*/
86
+ export { ChatHeaderRoot as Root, ChatHeaderTitle as Title, ChatHeaderActions as Actions, ChatHeaderStatusIndicator as StatusIndicator, ChatHeaderToggle as Toggle, ChatHeaderExpandablePanel as ExpandablePanel, useChatHeaderContext, };
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { AcpClient } from "../../sdk/client/index.js";
3
+ import { type CommandMenuItem } from "./ChatInputCommandMenu.js";
3
4
  import type { Textarea } from "./Textarea.js";
4
5
  export interface ChatInputRootProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onChange" | "onSubmit"> {
5
6
  /**
@@ -39,4 +40,21 @@ declare const ChatInputSubmit: React.ForwardRefExoticComponent<ChatInputSubmitPr
39
40
  export interface ChatInputToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
40
41
  }
41
42
  declare const ChatInputToolbar: React.ForwardRefExoticComponent<ChatInputToolbarProps & React.RefAttributes<HTMLDivElement>>;
42
- export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, };
43
+ export interface ChatInputActionsProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
44
+ asChild?: boolean;
45
+ }
46
+ declare const ChatInputActions: React.ForwardRefExoticComponent<ChatInputActionsProps & React.RefAttributes<HTMLButtonElement>>;
47
+ export interface ChatInputAttachmentProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
48
+ asChild?: boolean;
49
+ }
50
+ declare const ChatInputAttachment: React.ForwardRefExoticComponent<ChatInputAttachmentProps & React.RefAttributes<HTMLButtonElement>>;
51
+ export interface ChatInputVoiceInputProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
52
+ asChild?: boolean;
53
+ }
54
+ declare const ChatInputVoiceInput: React.ForwardRefExoticComponent<ChatInputVoiceInputProps & React.RefAttributes<HTMLButtonElement>>;
55
+ export interface ChatInputCommandMenuProps extends React.HTMLAttributes<HTMLDivElement> {
56
+ commands?: CommandMenuItem[];
57
+ }
58
+ declare const ChatInputCommandMenu: React.ForwardRefExoticComponent<ChatInputCommandMenuProps & React.RefAttributes<HTMLDivElement>>;
59
+ export type { CommandMenuItem };
60
+ export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Slot } from "@radix-ui/react-slot";
3
+ import { Mic, Paperclip, SquareSlash } from "lucide-react";
3
4
  import * as React from "react";
4
5
  import { useChatInput as useCoreChatInput } from "../../core/hooks/use-chat-input.js";
5
6
  import { useChatStore } from "../../core/store/chat-store.js";
6
7
  import { cn } from "../lib/utils.js";
7
8
  import { Button } from "./Button.js";
9
+ import { ChatInputCommandMenu as ChatInputCommandMenuComponent, } from "./ChatInputCommandMenu.js";
8
10
  const ChatInputContext = React.createContext(undefined);
9
11
  const useChatInputContext = () => {
10
12
  const context = React.useContext(ChatInputContext);
@@ -27,6 +29,15 @@ const ChatInputRoot = React.forwardRef(({ client, value: valueProp, onChange: on
27
29
  const isSubmitting = hookData
28
30
  ? hookData.isSubmitting || storeIsStreaming
29
31
  : isSubmittingProp || false;
32
+ // Command menu state
33
+ const [showCommandMenu, setShowCommandMenu] = React.useState(false);
34
+ const [commandMenuQuery, setCommandMenuQuery] = React.useState("");
35
+ const [selectedMenuIndex, setSelectedMenuIndex] = React.useState(0);
36
+ const [menuItemCount, setMenuItemCount] = React.useState(0);
37
+ const [triggerCounter, setTriggerCounter] = React.useState(0);
38
+ const triggerMenuSelect = React.useCallback(() => {
39
+ setTriggerCounter((prev) => prev + 1);
40
+ }, []);
30
41
  const handleSubmit = async (e) => {
31
42
  e.preventDefault();
32
43
  if (value.trim() && !isSubmitting && !disabled) {
@@ -58,11 +69,21 @@ const ChatInputRoot = React.forwardRef(({ client, value: valueProp, onChange: on
58
69
  disabled,
59
70
  isSubmitting,
60
71
  submitOnEnter,
61
- }, children: _jsx("form", { ref: ref, onSubmit: handleSubmit, className: cn("w-full divide-y overflow-hidden rounded-xl border bg-background shadow-sm", className), ...props, children: children }) }));
72
+ showCommandMenu,
73
+ setShowCommandMenu,
74
+ commandMenuQuery,
75
+ setCommandMenuQuery,
76
+ selectedMenuIndex,
77
+ setSelectedMenuIndex,
78
+ menuItemCount,
79
+ setMenuItemCount,
80
+ triggerMenuSelect,
81
+ triggerCounter,
82
+ }, children: _jsx("form", { ref: ref, onSubmit: handleSubmit, className: cn("relative w-full divide-y rounded-xl border bg-background shadow-md", className), ...props, children: children }) }));
62
83
  });
63
84
  ChatInputRoot.displayName = "ChatInput.Root";
64
85
  const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown, children, ...props }, ref) => {
65
- const { value, onChange, onSubmit, disabled, isSubmitting, submitOnEnter } = useChatInputContext();
86
+ const { value, onChange, onSubmit, disabled, isSubmitting, submitOnEnter, showCommandMenu, setShowCommandMenu, setCommandMenuQuery, setSelectedMenuIndex, menuItemCount, triggerMenuSelect, } = useChatInputContext();
66
87
  const textareaRef = React.useRef(null);
67
88
  const handleRef = React.useCallback((node) => {
68
89
  textareaRef.current = node;
@@ -74,6 +95,30 @@ const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown
74
95
  }
75
96
  }, [ref]);
76
97
  const handleKeyDown = (e) => {
98
+ // Handle arrow keys and Enter when command menu is open
99
+ if (showCommandMenu && menuItemCount > 0) {
100
+ if (e.key === "ArrowDown") {
101
+ e.preventDefault();
102
+ setSelectedMenuIndex((prev) => (prev + 1) % menuItemCount);
103
+ return;
104
+ }
105
+ else if (e.key === "ArrowUp") {
106
+ e.preventDefault();
107
+ setSelectedMenuIndex((prev) => (prev - 1 + menuItemCount) % menuItemCount);
108
+ return;
109
+ }
110
+ else if (e.key === "Enter" && !e.shiftKey) {
111
+ e.preventDefault();
112
+ triggerMenuSelect();
113
+ return;
114
+ }
115
+ else if (e.key === "Escape") {
116
+ e.preventDefault();
117
+ setShowCommandMenu(false);
118
+ setCommandMenuQuery("");
119
+ return;
120
+ }
121
+ }
77
122
  // Handle Enter without Shift - only submit if not already submitting
78
123
  if (submitOnEnter && e.key === "Enter" && !e.shiftKey) {
79
124
  // Only prevent default and submit if conditions are met
@@ -89,7 +134,19 @@ const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown
89
134
  onKeyDown?.(e);
90
135
  };
91
136
  const handleChange = (e) => {
92
- onChange(e.target.value);
137
+ const newValue = e.target.value;
138
+ onChange(newValue);
139
+ // Check if user typed "/" at the start to trigger command menu
140
+ if (newValue.startsWith("/") && !newValue.includes("\n")) {
141
+ setShowCommandMenu(true);
142
+ // Extract search query (everything after "/")
143
+ const query = newValue.slice(1);
144
+ setCommandMenuQuery(query);
145
+ }
146
+ else {
147
+ setShowCommandMenu(false);
148
+ setCommandMenuQuery("");
149
+ }
93
150
  // Auto-resize
94
151
  const textarea = textareaRef.current;
95
152
  if (!textarea)
@@ -116,22 +173,48 @@ const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown
116
173
  if (asChild && React.isValidElement(children)) {
117
174
  return React.cloneElement(children, fieldProps);
118
175
  }
119
- return (_jsx("textarea", { ...fieldProps, className: cn("w-full resize-none rounded-none border-none p-3 shadow-none", "outline-none ring-0 field-sizing-content max-h-[6lh]", "bg-transparent dark:bg-transparent focus-visible:ring-0", "text-sm placeholder:text-muted-foreground", "disabled:cursor-not-allowed disabled:opacity-50", className) }));
176
+ return (_jsx("textarea", { ...fieldProps, className: cn("w-full resize-none rounded-none border-none p-4 shadow-none", "outline-none ring-0 field-sizing-content max-h-[6lh]", "bg-transparent dark:bg-transparent focus-visible:ring-0", "text-sm placeholder:text-muted-foreground", "disabled:cursor-not-allowed disabled:opacity-50", className) }));
120
177
  });
121
178
  ChatInputField.displayName = "ChatInput.Field";
122
179
  const ChatInputSubmit = React.forwardRef(({ asChild = false, className, disabled: disabledProp, children, ...props }, ref) => {
123
180
  const { value, disabled, isSubmitting } = useChatInputContext();
124
181
  const isDisabled = disabledProp || disabled || isSubmitting || !value.trim();
125
182
  const Comp = asChild ? Slot : Button;
126
- return (_jsx(Comp, { ref: ref, type: "submit", disabled: isDisabled, size: "icon", className: cn(!asChild &&
127
- "gap-1.5 rounded-lg bg-transparent text-foreground hover:bg-transparent", className), ...props, children: children }));
183
+ return (_jsx(Comp, { ref: ref, type: "submit", disabled: isDisabled, variant: !asChild ? "default" : undefined, size: "icon", className: cn(!asChild && "gap-1.5 rounded-full", className), ...props, children: children }));
128
184
  });
129
185
  ChatInputSubmit.displayName = "ChatInput.Submit";
130
186
  const ChatInputToolbar = React.forwardRef(({ className, children, ...props }, ref) => {
131
- return (_jsx("div", { ref: ref, className: cn("flex items-center justify-between p-1", className), ...props, children: children }));
187
+ return (_jsx("div", { ref: ref, className: cn("flex items-center justify-between p-2", className), ...props, children: children }));
132
188
  });
133
189
  ChatInputToolbar.displayName = "ChatInput.Toolbar";
134
- /* -------------------------------------------------------------------------------------------------
135
- * Exports
136
- * -----------------------------------------------------------------------------------------------*/
137
- export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, };
190
+ const ChatInputActions = React.forwardRef(({ asChild = false, className, children, onClick, ...props }, ref) => {
191
+ const { value, onChange, setShowCommandMenu, setCommandMenuQuery } = useChatInputContext();
192
+ const handleClick = (e) => {
193
+ // Trigger command menu by setting "/" in the input
194
+ if (!value.startsWith("/")) {
195
+ onChange("/");
196
+ setShowCommandMenu(true);
197
+ setCommandMenuQuery("");
198
+ }
199
+ onClick?.(e);
200
+ };
201
+ const Comp = asChild ? Slot : Button;
202
+ return (_jsx(Comp, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full", className), onClick: handleClick, ...props, children: children || _jsx(SquareSlash, { className: "size-4" }) }));
203
+ });
204
+ ChatInputActions.displayName = "ChatInput.Actions";
205
+ const ChatInputAttachment = React.forwardRef(({ asChild = false, className, children, ...props }, ref) => {
206
+ const Comp = asChild ? Slot : Button;
207
+ return (_jsx(Comp, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full", className), ...props, children: children || _jsx(Paperclip, { className: "size-4" }) }));
208
+ });
209
+ ChatInputAttachment.displayName = "ChatInput.Attachment";
210
+ const ChatInputVoiceInput = React.forwardRef(({ asChild = false, className, children, ...props }, ref) => {
211
+ const Comp = asChild ? Slot : Button;
212
+ return (_jsx(Comp, { ref: ref, type: "button", variant: "ghost", size: "icon", className: cn("rounded-full", className), ...props, children: children || _jsx(Mic, { className: "size-4" }) }));
213
+ });
214
+ ChatInputVoiceInput.displayName = "ChatInput.VoiceInput";
215
+ const ChatInputCommandMenu = React.forwardRef(({ commands = [], className, onChange: _, ...props }, ref) => {
216
+ const { showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, } = useChatInputContext();
217
+ return (_jsx(ChatInputCommandMenuComponent, { ref: ref, commands: commands, showCommandMenu: showCommandMenu, commandMenuQuery: commandMenuQuery, selectedMenuIndex: selectedMenuIndex, setSelectedMenuIndex: setSelectedMenuIndex, setMenuItemCount: setMenuItemCount, triggerCounter: triggerCounter, onChange: onChange, className: className, ...props }));
218
+ });
219
+ ChatInputCommandMenu.displayName = "ChatInput.CommandMenu";
220
+ export { ChatInputRoot as Root, ChatInputField as Field, ChatInputSubmit as Submit, ChatInputToolbar as Toolbar, ChatInputActions as Actions, ChatInputAttachment as Attachment, ChatInputVoiceInput as VoiceInput, ChatInputCommandMenu as CommandMenu, };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ export interface CommandMenuItem {
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ icon?: React.ReactNode;
7
+ category?: string;
8
+ onSelect: () => void;
9
+ }
10
+ export interface ChatInputCommandMenuProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
11
+ commands?: CommandMenuItem[];
12
+ showCommandMenu: boolean;
13
+ commandMenuQuery: string;
14
+ selectedMenuIndex: number;
15
+ setSelectedMenuIndex: (index: number) => void;
16
+ setMenuItemCount: (count: number) => void;
17
+ triggerCounter: number;
18
+ onChange: (value: string) => void;
19
+ }
20
+ export declare const ChatInputCommandMenu: React.ForwardRefExoticComponent<ChatInputCommandMenuProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ /* -------------------------------------------------------------------------------------------------
5
+ * ChatInputCommandMenu
6
+ * -----------------------------------------------------------------------------------------------*/
7
+ export const ChatInputCommandMenu = React.forwardRef(({ commands = [], showCommandMenu, commandMenuQuery, selectedMenuIndex, setSelectedMenuIndex, setMenuItemCount, triggerCounter, onChange, className, ...props }, ref) => {
8
+ // Fuzzy search implementation
9
+ const fuzzyMatch = React.useCallback((text, query) => {
10
+ const lowerText = text.toLowerCase();
11
+ const lowerQuery = query.toLowerCase();
12
+ if (!query)
13
+ return 1; // Show all when no query
14
+ if (lowerText.includes(lowerQuery)) {
15
+ // Exact substring match gets high score
16
+ return 1 - lowerQuery.length / lowerText.length;
17
+ }
18
+ // Fuzzy matching - check if all query chars appear in order
19
+ let queryIndex = 0;
20
+ for (let i = 0; i < lowerText.length && queryIndex < lowerQuery.length; i++) {
21
+ if (lowerText[i] === lowerQuery[queryIndex]) {
22
+ queryIndex++;
23
+ }
24
+ }
25
+ if (queryIndex === lowerQuery.length) {
26
+ // All chars found, score based on how spread out they are
27
+ return 0.5 - queryIndex / lowerText.length;
28
+ }
29
+ return 0; // No match
30
+ }, []);
31
+ // Filter and sort commands by relevance
32
+ const filteredCommands = React.useMemo(() => {
33
+ return commands
34
+ .map((cmd) => ({
35
+ ...cmd,
36
+ score: Math.max(fuzzyMatch(cmd.label, commandMenuQuery), cmd.description ? fuzzyMatch(cmd.description, commandMenuQuery) : 0),
37
+ }))
38
+ .filter((cmd) => cmd.score > 0)
39
+ .sort((a, b) => b.score - a.score);
40
+ }, [commands, commandMenuQuery, fuzzyMatch]);
41
+ // Update menu item count
42
+ React.useEffect(() => {
43
+ setMenuItemCount(filteredCommands.length);
44
+ }, [filteredCommands.length, setMenuItemCount]);
45
+ // Reset selected index when filtered results change
46
+ React.useEffect(() => {
47
+ setSelectedMenuIndex(0);
48
+ }, [setSelectedMenuIndex]);
49
+ // Handle selection when triggered
50
+ React.useEffect(() => {
51
+ if (triggerCounter > 0 && filteredCommands[selectedMenuIndex]) {
52
+ filteredCommands[selectedMenuIndex].onSelect();
53
+ // Clear the input after selection
54
+ onChange("");
55
+ }
56
+ }, [triggerCounter, filteredCommands, selectedMenuIndex, onChange]);
57
+ if (!showCommandMenu || filteredCommands.length === 0) {
58
+ return null;
59
+ }
60
+ return (_jsxs("div", { ref: ref, className: cn("absolute bottom-full left-0 z-50 mb-2 w-full max-w-md", "rounded-md border border-border bg-card p-2 shadow-lg", className), ...props, children: [_jsx("div", { className: "text-xs font-semibold text-muted-foreground px-2 py-1", children: "Commands" }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredCommands.map((command, index) => (_jsxs("button", { type: "button", onClick: () => command.onSelect(), className: cn("w-full rounded-sm px-2 py-2 text-left text-sm transition-colors", "flex items-start gap-2", "hover:bg-muted", index === selectedMenuIndex && "bg-muted"), children: [command.icon && (_jsx("span", { className: "shrink-0 mt-0.5", children: command.icon })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: command.label }), command.description && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: command.description }))] })] }, command.id))) })] }));
61
+ });
62
+ ChatInputCommandMenu.displayName = "ChatInputCommandMenu";
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ type PanelSize = "hidden" | "small" | "large";
3
+ type PanelTabType = "todo" | "files" | "database";
4
+ interface ChatLayoutContextValue {
5
+ sidebarOpen: boolean;
6
+ setSidebarOpen: (open: boolean) => void;
7
+ panelSize: PanelSize;
8
+ setPanelSize: (size: PanelSize) => void;
9
+ activeTab: PanelTabType;
10
+ setActiveTab: (tab: PanelTabType) => void;
11
+ }
12
+ declare const useChatLayoutContext: () => ChatLayoutContextValue;
13
+ export interface ChatLayoutRootProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ /** Initial sidebar open state */
15
+ defaultSidebarOpen?: boolean;
16
+ /** Initial panel size state */
17
+ defaultPanelSize?: PanelSize;
18
+ /** Initial active tab */
19
+ defaultActiveTab?: PanelTabType;
20
+ }
21
+ declare const ChatLayoutRoot: React.ForwardRefExoticComponent<ChatLayoutRootProps & React.RefAttributes<HTMLDivElement>>;
22
+ export interface ChatLayoutHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
23
+ }
24
+ declare const ChatLayoutHeader: React.ForwardRefExoticComponent<ChatLayoutHeaderProps & React.RefAttributes<HTMLDivElement>>;
25
+ export interface ChatLayoutMainProps extends React.HTMLAttributes<HTMLDivElement> {
26
+ }
27
+ declare const ChatLayoutMain: React.ForwardRefExoticComponent<ChatLayoutMainProps & React.RefAttributes<HTMLDivElement>>;
28
+ export interface ChatLayoutBodyProps extends React.HTMLAttributes<HTMLDivElement> {
29
+ /** Whether to show toaster */
30
+ showToaster?: boolean;
31
+ }
32
+ declare const ChatLayoutBody: React.ForwardRefExoticComponent<ChatLayoutBodyProps & React.RefAttributes<HTMLDivElement>>;
33
+ export interface ChatLayoutMessagesProps extends React.HTMLAttributes<HTMLDivElement> {
34
+ /** Callback when scroll position changes */
35
+ onScrollChange?: (isAtBottom: boolean) => void;
36
+ /** Whether to show scroll to bottom button */
37
+ showScrollToBottom?: boolean;
38
+ }
39
+ declare const ChatLayoutMessages: React.ForwardRefExoticComponent<ChatLayoutMessagesProps & React.RefAttributes<HTMLDivElement>>;
40
+ export interface ChatLayoutFooterProps extends React.HTMLAttributes<HTMLDivElement> {
41
+ }
42
+ declare const ChatLayoutFooter: React.ForwardRefExoticComponent<ChatLayoutFooterProps & React.RefAttributes<HTMLDivElement>>;
43
+ export interface ChatLayoutSidebarProps extends React.HTMLAttributes<HTMLDivElement> {
44
+ }
45
+ declare const ChatLayoutSidebar: React.ForwardRefExoticComponent<ChatLayoutSidebarProps & React.RefAttributes<HTMLDivElement>>;
46
+ export interface ChatLayoutAsideProps extends React.HTMLAttributes<HTMLDivElement> {
47
+ /** Show panel on these breakpoints (default: lg and above) */
48
+ breakpoint?: "md" | "lg" | "xl" | "2xl";
49
+ }
50
+ declare const ChatLayoutAside: React.ForwardRefExoticComponent<ChatLayoutAsideProps & React.RefAttributes<HTMLDivElement>>;
51
+ export { ChatLayoutRoot as Root, ChatLayoutHeader as Header, ChatLayoutMain as Main, ChatLayoutBody as Body, ChatLayoutMessages as Messages, ChatLayoutFooter as Footer, ChatLayoutSidebar as Sidebar, ChatLayoutAside as Aside, useChatLayoutContext, };
52
+ export type { PanelSize, PanelTabType };
@@ -0,0 +1,105 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowDown } from "lucide-react";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Toaster } from "./Sonner.js";
6
+ const ChatLayoutContext = React.createContext(undefined);
7
+ const useChatLayoutContext = () => {
8
+ const context = React.useContext(ChatLayoutContext);
9
+ if (!context) {
10
+ throw new Error("ChatLayout components must be used within ChatLayout.Root");
11
+ }
12
+ return context;
13
+ };
14
+ const ChatLayoutRoot = React.forwardRef(({ defaultSidebarOpen = false, defaultPanelSize = "hidden", defaultActiveTab = "todo", className, children, ...props }, ref) => {
15
+ const [sidebarOpen, setSidebarOpen] = React.useState(defaultSidebarOpen);
16
+ const [panelSize, setPanelSize] = React.useState(defaultPanelSize);
17
+ const [activeTab, setActiveTab] = React.useState(defaultActiveTab);
18
+ return (_jsx(ChatLayoutContext.Provider, { value: {
19
+ sidebarOpen,
20
+ setSidebarOpen,
21
+ panelSize,
22
+ setPanelSize,
23
+ activeTab,
24
+ setActiveTab,
25
+ }, children: _jsx("div", { ref: ref, className: cn("flex h-screen flex-row bg-background text-foreground", className), ...props, children: children }) }));
26
+ });
27
+ ChatLayoutRoot.displayName = "ChatLayout.Root";
28
+ const ChatLayoutHeader = React.forwardRef(({ className, children, ...props }, ref) => {
29
+ return (_jsx("div", { ref: ref, className: cn("relative z-10 border-b border-border bg-card shrink-0", className), ...props, children: children }));
30
+ });
31
+ ChatLayoutHeader.displayName = "ChatLayout.Header";
32
+ const ChatLayoutMain = React.forwardRef(({ className, children, ...props }, ref) => {
33
+ return (_jsx("div", { ref: ref, className: cn("flex flex-1 flex-col overflow-hidden", className), ...props, children: children }));
34
+ });
35
+ ChatLayoutMain.displayName = "ChatLayout.Main";
36
+ const ChatLayoutBody = React.forwardRef(({ showToaster = true, className, children, ...props }, ref) => {
37
+ return (_jsxs("div", { ref: ref, className: cn("relative flex flex-1 flex-col overflow-hidden", className), ...props, children: [children, showToaster && _jsx(Toaster, {})] }));
38
+ });
39
+ ChatLayoutBody.displayName = "ChatLayout.Body";
40
+ const ChatLayoutMessages = React.forwardRef(({ className, children, onScrollChange, showScrollToBottom = true, ...props }, ref) => {
41
+ const [showScrollButton, setShowScrollButton] = React.useState(false);
42
+ const scrollContainerRef = React.useRef(null);
43
+ // Merge refs
44
+ React.useImperativeHandle(ref, () => scrollContainerRef.current);
45
+ // Check if user is at bottom of scroll
46
+ const checkScrollPosition = React.useCallback(() => {
47
+ const container = scrollContainerRef.current;
48
+ if (!container)
49
+ return;
50
+ const { scrollTop, scrollHeight, clientHeight } = container;
51
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
52
+ const isAtBottom = distanceFromBottom < 100; // 100px threshold
53
+ setShowScrollButton(!isAtBottom && showScrollToBottom);
54
+ onScrollChange?.(isAtBottom);
55
+ }, [onScrollChange, showScrollToBottom]);
56
+ // Handle scroll events
57
+ const handleScroll = React.useCallback(() => {
58
+ checkScrollPosition();
59
+ }, [checkScrollPosition]);
60
+ // Scroll to bottom function
61
+ const scrollToBottom = React.useCallback(() => {
62
+ const container = scrollContainerRef.current;
63
+ if (!container)
64
+ return;
65
+ container.scrollTo({
66
+ top: container.scrollHeight,
67
+ behavior: "smooth",
68
+ });
69
+ }, []);
70
+ // Check scroll position on mount and when children change
71
+ React.useEffect(() => {
72
+ checkScrollPosition();
73
+ }, [checkScrollPosition]);
74
+ return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsx("div", { ref: scrollContainerRef, className: cn("h-full overflow-y-auto", className), onScroll: handleScroll, ...props, children: children }), showScrollButton && (_jsx("button", { type: "button", onClick: scrollToBottom, className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 z-10", "flex items-center justify-center p-2 rounded-full", "bg-card border border-border shadow-lg", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-all duration-200 ease-in-out", "animate-in fade-in slide-in-from-bottom-2"), "aria-label": "Scroll to bottom", children: _jsx(ArrowDown, { className: "h-4 w-4" }) }))] }));
75
+ });
76
+ ChatLayoutMessages.displayName = "ChatLayout.Messages";
77
+ const ChatLayoutFooter = React.forwardRef(({ className, children, ...props }, ref) => {
78
+ return (_jsx("div", { ref: ref, className: cn("bg-linear-to-t from-background to-transparent px-4 pb-4", className), ...props, children: children }));
79
+ });
80
+ ChatLayoutFooter.displayName = "ChatLayout.Footer";
81
+ const ChatLayoutSidebar = React.forwardRef(({ className, children, ...props }, ref) => {
82
+ const { sidebarOpen } = useChatLayoutContext();
83
+ if (!sidebarOpen)
84
+ return null;
85
+ return (_jsx("div", { ref: ref, className: cn("border-r border-border bg-card w-64 overflow-y-auto", className), ...props, children: children }));
86
+ });
87
+ ChatLayoutSidebar.displayName = "ChatLayout.Sidebar";
88
+ const ChatLayoutAside = React.forwardRef(({ breakpoint = "lg", className, children, ...props }, ref) => {
89
+ const { panelSize } = useChatLayoutContext();
90
+ // Hidden state - don't render
91
+ if (panelSize === "hidden")
92
+ return null;
93
+ return (_jsx("div", { ref: ref, className: cn(
94
+ // Hidden by default, visible at breakpoint
95
+ "hidden border-l border-border bg-card overflow-y-auto transition-all duration-300",
96
+ // Breakpoint visibility
97
+ breakpoint === "md" && "md:block", breakpoint === "lg" && "lg:block", breakpoint === "xl" && "xl:block", breakpoint === "2xl" && "2xl:block",
98
+ // Size variants
99
+ panelSize === "small" && "w-80", panelSize === "large" && "w-lg", className), ...props, children: children }));
100
+ });
101
+ ChatLayoutAside.displayName = "ChatLayout.Aside";
102
+ /* -------------------------------------------------------------------------------------------------
103
+ * Exports
104
+ * -----------------------------------------------------------------------------------------------*/
105
+ export { ChatLayoutRoot as Root, ChatLayoutHeader as Header, ChatLayoutMain as Main, ChatLayoutBody as Body, ChatLayoutMessages as Messages, ChatLayoutFooter as Footer, ChatLayoutSidebar as Sidebar, ChatLayoutAside as Aside, useChatLayoutContext, };
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ import type { TodoItem } from "./TodoListItem.js";
3
+ /**
4
+ * Shared tab content components for both mobile (ChatHeader) and desktop (Panel) views
5
+ * Following component architecture best practices
6
+ */
7
+ export interface TodoTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ todos: TodoItem[];
9
+ }
10
+ export declare const TodoTabContent: React.ForwardRefExoticComponent<TodoTabContentProps & React.RefAttributes<HTMLDivElement>>;
11
+ export interface FilesTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ files?: string[];
13
+ }
14
+ export declare const FilesTabContent: React.ForwardRefExoticComponent<FilesTabContentProps & React.RefAttributes<HTMLDivElement>>;
15
+ export interface DatabaseTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
16
+ data?: unknown;
17
+ }
18
+ export declare const DatabaseTabContent: React.ForwardRefExoticComponent<DatabaseTabContentProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ export const TodoTabContent = React.forwardRef(({ todos, className, ...props }, ref) => {
5
+ return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: todos.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No todos yet" }) })) : (todos.map((todo) => (_jsx("div", { className: "text-sm", children: todo.text }, todo.id)))) }));
6
+ });
7
+ TodoTabContent.displayName = "TodoTabContent";
8
+ export const FilesTabContent = React.forwardRef(({ files = [], className, ...props }, ref) => {
9
+ return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: files.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No files attached" }) })) : (files.map((file) => (_jsx("div", { className: "text-sm", children: file }, file)))) }));
10
+ });
11
+ FilesTabContent.displayName = "FilesTabContent";
12
+ export const DatabaseTabContent = React.forwardRef(({ data, className, ...props }, ref) => {
13
+ return (_jsxs("div", { ref: ref, className: cn("space-y-4", className), ...props, children: [_jsx("h3", { className: "font-semibold text-lg", children: "Database" }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("p", { children: "Database viewer - panel automatically expanded to large size" }), _jsxs("div", { className: "mt-4 p-4 border border-border rounded", children: [_jsx("p", { children: "Your large data table would go here" }), data && typeof data === "object" ? (_jsx("pre", { className: "mt-2 text-xs overflow-auto", children: JSON.stringify(data, null, 2) })) : null] })] })] }));
14
+ });
15
+ DatabaseTabContent.displayName = "DatabaseTabContent";
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ export interface ChatSidebarRootProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ }
4
+ declare const ChatSidebarRoot: React.ForwardRefExoticComponent<ChatSidebarRootProps & React.RefAttributes<HTMLDivElement>>;
5
+ export interface ChatSidebarHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ }
7
+ declare const ChatSidebarHeader: React.ForwardRefExoticComponent<ChatSidebarHeaderProps & React.RefAttributes<HTMLDivElement>>;
8
+ export interface ChatSidebarContentProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ }
10
+ declare const ChatSidebarContent: React.ForwardRefExoticComponent<ChatSidebarContentProps & React.RefAttributes<HTMLDivElement>>;
11
+ export interface ChatSidebarFooterProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ }
13
+ declare const ChatSidebarFooter: React.ForwardRefExoticComponent<ChatSidebarFooterProps & React.RefAttributes<HTMLDivElement>>;
14
+ export { ChatSidebarRoot as Root, ChatSidebarHeader as Header, ChatSidebarContent as Content, ChatSidebarFooter as Footer, };
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ const ChatSidebarRoot = React.forwardRef(({ className, children, ...props }, ref) => {
5
+ return (_jsx("div", { ref: ref, className: cn("flex h-full flex-col", className), ...props, children: children }));
6
+ });
7
+ ChatSidebarRoot.displayName = "ChatSidebar.Root";
8
+ const ChatSidebarHeader = React.forwardRef(({ className, children, ...props }, ref) => {
9
+ return (_jsx("div", { ref: ref, className: cn("border-b border-border px-4 py-3", className), ...props, children: children }));
10
+ });
11
+ ChatSidebarHeader.displayName = "ChatSidebar.Header";
12
+ const ChatSidebarContent = React.forwardRef(({ className, children, ...props }, ref) => {
13
+ return (_jsx("div", { ref: ref, className: cn("flex-1 overflow-y-auto p-4", className), ...props, children: children }));
14
+ });
15
+ ChatSidebarContent.displayName = "ChatSidebar.Content";
16
+ const ChatSidebarFooter = React.forwardRef(({ className, children, ...props }, ref) => {
17
+ return (_jsx("div", { ref: ref, className: cn("border-t border-border px-4 py-3", className), ...props, children: children }));
18
+ });
19
+ ChatSidebarFooter.displayName = "ChatSidebar.Footer";
20
+ /* -------------------------------------------------------------------------------------------------
21
+ * Exports
22
+ * -----------------------------------------------------------------------------------------------*/
23
+ export { ChatSidebarRoot as Root, ChatSidebarHeader as Header, ChatSidebarContent as Content, ChatSidebarFooter as Footer, };
@@ -0,0 +1,27 @@
1
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
+ import * as React from "react";
3
+ declare const DropdownMenu: React.FC<DropdownMenuPrimitive.DropdownMenuProps>;
4
+ declare const DropdownMenuTrigger: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuTriggerProps & React.RefAttributes<HTMLButtonElement>>;
5
+ declare const DropdownMenuGroup: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuGroupProps & React.RefAttributes<HTMLDivElement>>;
6
+ declare const DropdownMenuPortal: React.FC<DropdownMenuPrimitive.DropdownMenuPortalProps>;
7
+ declare const DropdownMenuSub: React.FC<DropdownMenuPrimitive.DropdownMenuSubProps>;
8
+ declare const DropdownMenuRadioGroup: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuRadioGroupProps & React.RefAttributes<HTMLDivElement>>;
9
+ declare const DropdownMenuSubTrigger: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSubTriggerProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
10
+ inset?: boolean;
11
+ } & React.RefAttributes<HTMLDivElement>>;
12
+ declare const DropdownMenuSubContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSubContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
13
+ declare const DropdownMenuContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
14
+ declare const DropdownMenuItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
15
+ inset?: boolean;
16
+ } & React.RefAttributes<HTMLDivElement>>;
17
+ declare const DropdownMenuCheckboxItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuCheckboxItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
18
+ declare const DropdownMenuRadioItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuRadioItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
19
+ declare const DropdownMenuLabel: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
20
+ inset?: boolean;
21
+ } & React.RefAttributes<HTMLDivElement>>;
22
+ declare const DropdownMenuSeparator: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
23
+ declare const DropdownMenuShortcut: {
24
+ ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
25
+ displayName: string;
26
+ };
27
+ export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, };
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
+ import { Check, ChevronRight, Circle } from "lucide-react";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/utils.js";
6
+ /* -------------------------------------------------------------------------------------------------
7
+ * DropdownMenu Root
8
+ * -----------------------------------------------------------------------------------------------*/
9
+ const DropdownMenu = DropdownMenuPrimitive.Root;
10
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
12
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
13
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
14
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
15
+ /* -------------------------------------------------------------------------------------------------
16
+ * DropdownMenuSubTrigger
17
+ * -----------------------------------------------------------------------------------------------*/
18
+ const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.SubTrigger, { ref: ref, className: cn("flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none", "focus:bg-muted data-[state=open]:bg-muted", inset && "pl-8", className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto h-4 w-4" })] })));
19
+ DropdownMenuSubTrigger.displayName =
20
+ DropdownMenuPrimitive.SubTrigger.displayName;
21
+ /* -------------------------------------------------------------------------------------------------
22
+ * DropdownMenuSubContent
23
+ * -----------------------------------------------------------------------------------------------*/
24
+ const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.SubContent, { ref: ref, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-card p-1 shadow-lg", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[side=bottom]:slide-in-from-top-2", "data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2", "data-[side=top]:slide-in-from-bottom-2", className), ...props })));
25
+ DropdownMenuSubContent.displayName =
26
+ DropdownMenuPrimitive.SubContent.displayName;
27
+ /* -------------------------------------------------------------------------------------------------
28
+ * DropdownMenuContent
29
+ * -----------------------------------------------------------------------------------------------*/
30
+ const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-card p-1 shadow-md", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[side=bottom]:slide-in-from-top-2", "data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2", "data-[side=top]:slide-in-from-bottom-2", className), ...props }) })));
31
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
32
+ /* -------------------------------------------------------------------------------------------------
33
+ * DropdownMenuItem
34
+ * -----------------------------------------------------------------------------------------------*/
35
+ const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Item, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", inset && "pl-8", className), ...props })));
36
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
37
+ /* -------------------------------------------------------------------------------------------------
38
+ * DropdownMenuCheckboxItem
39
+ * -----------------------------------------------------------------------------------------------*/
40
+ const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), children] })));
41
+ DropdownMenuCheckboxItem.displayName =
42
+ DropdownMenuPrimitive.CheckboxItem.displayName;
43
+ /* -------------------------------------------------------------------------------------------------
44
+ * DropdownMenuRadioItem
45
+ * -----------------------------------------------------------------------------------------------*/
46
+ const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.RadioItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Circle, { className: "h-2 w-2 fill-current" }) }) }), children] })));
47
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
48
+ /* -------------------------------------------------------------------------------------------------
49
+ * DropdownMenuLabel
50
+ * -----------------------------------------------------------------------------------------------*/
51
+ const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className), ...props })));
52
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
53
+ /* -------------------------------------------------------------------------------------------------
54
+ * DropdownMenuSeparator
55
+ * -----------------------------------------------------------------------------------------------*/
56
+ const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Separator, { ref: ref, className: cn("-mx-1 my-1 h-px bg-border", className), ...props })));
57
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
58
+ /* -------------------------------------------------------------------------------------------------
59
+ * DropdownMenuShortcut
60
+ * -----------------------------------------------------------------------------------------------*/
61
+ const DropdownMenuShortcut = ({ className, ...props }) => {
62
+ return (_jsx("span", { className: cn("ml-auto text-xs tracking-widest opacity-60", className), ...props }));
63
+ };
64
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
65
+ /* -------------------------------------------------------------------------------------------------
66
+ * Exports
67
+ * -----------------------------------------------------------------------------------------------*/
68
+ export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, };
@@ -21,6 +21,10 @@ export interface MessageProps extends React.HTMLAttributes<HTMLDivElement>, Vari
21
21
  layout?: "default" | "full" | "compact";
22
22
  /** Optional message ID for accessibility (fallback if message not provided) */
23
23
  messageId?: string;
24
+ /** Whether to auto-scroll this message into view when mounted (default: true for user messages) */
25
+ autoScroll?: boolean;
26
+ /** Whether this is the last message in the conversation (for dynamic min-height) */
27
+ isLastMessage?: boolean;
24
28
  }
25
29
  export declare const Message: React.ForwardRefExoticComponent<MessageProps & React.RefAttributes<HTMLDivElement>>;
26
30
  export {};
@@ -9,7 +9,7 @@ import { cn } from "../lib/utils.js";
9
9
  const messageVariants = cva("flex animate-fadeIn", {
10
10
  variants: {
11
11
  role: {
12
- user: "max-w-[80%] self-end ml-auto",
12
+ user: "max-w-[80%] self-end ml-auto mr-2",
13
13
  assistant: "self-start mr-auto",
14
14
  system: "self-start mr-auto max-w-full",
15
15
  },
@@ -24,10 +24,84 @@ const messageVariants = cva("flex animate-fadeIn", {
24
24
  layout: "default",
25
25
  },
26
26
  });
27
- export const Message = React.forwardRef(({ message, role: roleProp, layout, className, children, messageId: messageIdProp, ...props }, ref) => {
27
+ export const Message = React.forwardRef(({ message, role: roleProp, layout, className, children, messageId: messageIdProp, autoScroll, isLastMessage = false, ...props }, ref) => {
28
28
  // Extract role and messageId from message if provided
29
29
  const role = message ? message.role : roleProp || "assistant";
30
30
  const messageId = message ? message.id : messageIdProp;
31
- return (_jsx("article", { ref: ref, "aria-label": `${role} message`, "data-message-id": messageId, className: cn(messageVariants({ role, layout }), className), ...props, children: children }));
31
+ const messageRef = React.useRef(null);
32
+ const [minHeight, setMinHeight] = React.useState(undefined);
33
+ // Merge refs
34
+ React.useImperativeHandle(ref, () => messageRef.current);
35
+ // Calculate dynamic min-height for last assistant message following a user message
36
+ React.useEffect(() => {
37
+ if (!isLastMessage || role !== "assistant") {
38
+ setMinHeight(undefined);
39
+ return;
40
+ }
41
+ const calculateMinHeight = () => {
42
+ const messageElement = messageRef.current;
43
+ if (!messageElement)
44
+ return;
45
+ // Find the ChatLayout.Messages container (scrollable parent)
46
+ let scrollContainer = messageElement.parentElement;
47
+ while (scrollContainer &&
48
+ !scrollContainer.classList.contains("overflow-y-auto")) {
49
+ scrollContainer = scrollContainer.parentElement;
50
+ }
51
+ if (!scrollContainer)
52
+ return;
53
+ // Find the previous user message
54
+ const previousSibling = messageElement.previousElementSibling;
55
+ if (!previousSibling ||
56
+ previousSibling.getAttribute("aria-label") !== "user message") {
57
+ setMinHeight(undefined);
58
+ return;
59
+ }
60
+ // Calculate: containerHeight - previousUserMessageHeight - spacing
61
+ const containerHeight = scrollContainer.clientHeight;
62
+ const previousMessageHeight = previousSibling.offsetHeight;
63
+ const spacing = 32; // gap-4 = 16px
64
+ // Min height = container height - user message height - spacing
65
+ // This ensures the user message can scroll to the top
66
+ const calculatedMinHeight = Math.max(0, containerHeight - previousMessageHeight - spacing);
67
+ setMinHeight(calculatedMinHeight);
68
+ };
69
+ // Calculate on mount and recalculate on resize
70
+ calculateMinHeight();
71
+ const resizeObserver = new ResizeObserver(calculateMinHeight);
72
+ // Observe the message element and its parent container
73
+ if (messageRef.current) {
74
+ resizeObserver.observe(messageRef.current);
75
+ let scrollContainer = messageRef.current.parentElement;
76
+ while (scrollContainer &&
77
+ !scrollContainer.classList.contains("overflow-y-auto")) {
78
+ scrollContainer = scrollContainer.parentElement;
79
+ }
80
+ if (scrollContainer) {
81
+ resizeObserver.observe(scrollContainer);
82
+ }
83
+ }
84
+ return () => resizeObserver.disconnect();
85
+ }, [isLastMessage, role]);
86
+ // Auto-scroll for user messages when they first appear
87
+ React.useEffect(() => {
88
+ // Default to auto-scroll for user messages unless explicitly disabled
89
+ const shouldAutoScroll = autoScroll !== undefined ? autoScroll : role === "user";
90
+ if (shouldAutoScroll && messageRef.current) {
91
+ // Small delay to ensure the message is fully rendered
92
+ const timeoutId = setTimeout(() => {
93
+ messageRef.current?.scrollIntoView({
94
+ behavior: "smooth",
95
+ block: "start",
96
+ inline: "nearest",
97
+ });
98
+ }, 50);
99
+ return () => clearTimeout(timeoutId);
100
+ }
101
+ return undefined;
102
+ }, [role, autoScroll]);
103
+ return (_jsx("article", { ref: messageRef, "aria-label": `${role} message`, "data-message-id": messageId, className: cn(messageVariants({ role, layout }), className), style: {
104
+ minHeight: minHeight !== undefined ? `${minHeight}px` : undefined,
105
+ }, ...props, children: children }));
32
106
  });
33
107
  Message.displayName = "Message";
@@ -66,7 +66,7 @@ const messageContentVariants = cva("w-full px-4 py-3 rounded-xl text-[var(--font
66
66
  variants: {
67
67
  role: {
68
68
  user: "bg-primary text-primary-foreground shadow-sm",
69
- assistant: "bg-muted text-foreground",
69
+ assistant: "text-foreground",
70
70
  system: "bg-card border border-border text-foreground opacity-80 text-sm",
71
71
  },
72
72
  variant: {
@@ -0,0 +1,5 @@
1
+ import type * as React from "react";
2
+ import { Toaster as Sonner } from "sonner";
3
+ type ToasterProps = React.ComponentProps<typeof Sonner>;
4
+ declare const Toaster: ({ ...props }: ToasterProps) => import("react/jsx-runtime").JSX.Element;
5
+ export { Toaster };
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Toaster as Sonner } from "sonner";
3
+ const Toaster = ({ ...props }) => {
4
+ return (_jsx(Sonner, { position: "top-center", className: "toaster group", style: {
5
+ "--top-offset": "16px",
6
+ "--width:": "calc(100% - 2 * var(--top-offset))",
7
+ position: "absolute",
8
+ zIndex: 5,
9
+ top: "var(--top-offset)",
10
+ left: "50%",
11
+ transform: "translateX(-50%)",
12
+ width: "calc(100% - 2 * var(--top-offset))",
13
+ maxWidth: "480px",
14
+ }, offset: 0, toastOptions: {
15
+ classNames: {
16
+ toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
17
+ description: "group-[.toast]:text-muted-foreground",
18
+ actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
19
+ cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
20
+ },
21
+ }, ...props }));
22
+ };
23
+ export { Toaster };
@@ -1,10 +1,17 @@
1
+ export { toast } from "sonner";
1
2
  export { Button, type ButtonProps, buttonVariants } from "./Button.js";
2
3
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
3
- export { type ChatInputFieldProps, type ChatInputRootProps, type ChatInputSubmitProps, type ChatInputToolbarProps, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, } from "./ChatInput.js";
4
+ export type { ConnectionStatus } from "./ChatHeader.js";
5
+ export * as ChatHeader from "./ChatHeader.js";
6
+ export { Actions as ChatInputActions, Attachment as ChatInputAttachment, type ChatInputActionsProps, type ChatInputAttachmentProps, type ChatInputCommandMenuProps, type ChatInputFieldProps, type ChatInputRootProps, type ChatInputSubmitProps, type ChatInputToolbarProps, type ChatInputVoiceInputProps, CommandMenu as ChatInputCommandMenu, type CommandMenuItem, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
7
+ export * as ChatLayout from "./ChatLayout.js";
8
+ export { DatabaseTabContent, type DatabaseTabContentProps, FilesTabContent, type FilesTabContentProps, TodoTabContent, type TodoTabContentProps, } from "./ChatPanelTabContent.js";
4
9
  export { ChatSecondaryPanel, type ChatSecondaryPanelProps, } from "./ChatSecondaryPanel.js";
10
+ export * as ChatSidebar from "./ChatSidebar.js";
5
11
  export { ChatStatus, type ChatStatusProps } from "./ChatStatus.js";
6
12
  export { Conversation, type ConversationProps } from "./Conversation.js";
7
13
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from "./Dialog.js";
14
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./DropdownMenu.js";
8
15
  export { HeightTransition } from "./HeightTransition.js";
9
16
  export { Input, type InputProps, inputVariants } from "./Input.js";
10
17
  export { Label } from "./Label.js";
@@ -15,6 +22,7 @@ export type { DisplayMessage } from "./MessageList.js";
15
22
  export { Reasoning, type ReasoningProps } from "./Reasoning.js";
16
23
  export { Response, type ResponseProps } from "./Response.js";
17
24
  export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
25
+ export { Toaster } from "./Sonner.js";
18
26
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
19
27
  export { Task, type TaskItem, TaskList, type TaskListProps, type TaskProps, } from "./Task.js";
20
28
  export { Textarea, type TextareaProps, textareaVariants } from "./Textarea.js";
@@ -1,14 +1,22 @@
1
1
  // Base UI components
2
+ export { toast } from "sonner";
2
3
  export { Button, buttonVariants } from "./Button.js";
3
4
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
5
+ export * as ChatHeader from "./ChatHeader.js";
4
6
  // Chat components - composable primitives
5
- export { Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, } from "./ChatInput.js";
7
+ export { Actions as ChatInputActions, Attachment as ChatInputAttachment, CommandMenu as ChatInputCommandMenu, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
8
+ // Chat layout components
9
+ export * as ChatLayout from "./ChatLayout.js";
10
+ export { DatabaseTabContent, FilesTabContent, TodoTabContent, } from "./ChatPanelTabContent.js";
6
11
  export { ChatSecondaryPanel, } from "./ChatSecondaryPanel.js";
12
+ export * as ChatSidebar from "./ChatSidebar.js";
7
13
  export { ChatStatus } from "./ChatStatus.js";
8
14
  // Chat components - shadcn.io/ai inspired primitives
9
15
  export { Conversation } from "./Conversation.js";
10
16
  // Dialog components
11
17
  export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, } from "./Dialog.js";
18
+ // DropdownMenu components
19
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./DropdownMenu.js";
12
20
  // Utility components
13
21
  export { HeightTransition } from "./HeightTransition.js";
14
22
  export { Input, inputVariants } from "./Input.js";
@@ -19,6 +27,8 @@ export { MessageContent } from "./MessageContent.js";
19
27
  export { Reasoning } from "./Reasoning.js";
20
28
  export { Response } from "./Response.js";
21
29
  export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
30
+ // Toast components
31
+ export { Toaster } from "./Sonner.js";
22
32
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
23
33
  // Task/Todo components
24
34
  export { Task, TaskList, } from "./Task.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -41,6 +41,7 @@
41
41
  "dependencies": {
42
42
  "@agentclientprotocol/sdk": "^0.5.1",
43
43
  "@radix-ui/react-dialog": "^1.1.15",
44
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
44
45
  "@radix-ui/react-label": "^2.1.8",
45
46
  "@radix-ui/react-select": "^2.2.6",
46
47
  "@radix-ui/react-slot": "^1.2.4",
@@ -51,13 +52,14 @@
51
52
  "lucide-react": "^0.552.0",
52
53
  "react-markdown": "^10.1.0",
53
54
  "remark-gfm": "^4.0.1",
55
+ "sonner": "^2.0.7",
54
56
  "tailwind-merge": "^3.3.1",
55
57
  "zod": "^4.1.12",
56
58
  "zustand": "^5.0.8"
57
59
  },
58
60
  "devDependencies": {
59
61
  "@tailwindcss/postcss": "^4.1.17",
60
- "@townco/tsconfig": "0.1.4",
62
+ "@townco/tsconfig": "0.1.5",
61
63
  "@types/node": "^24.10.0",
62
64
  "@types/react": "^19.2.2",
63
65
  "ink": "^6.4.0",
@@ -68,8 +70,8 @@
68
70
  },
69
71
  "peerDependencies": {
70
72
  "ink": "^5.1.0",
71
- "react": "^18.3.1 || ^19.0.0",
72
- "react-dom": "^18.3.1 || ^19.0.0"
73
+ "react": "^19.2.0",
74
+ "react-dom": "^19.2.0"
73
75
  },
74
76
  "peerDependenciesMeta": {
75
77
  "react": {
@@ -67,6 +67,8 @@
67
67
  --color-border: hsl(var(--border));
68
68
  --color-input: hsl(var(--input));
69
69
  --color-ring: hsl(var(--ring));
70
+ --shadow-sm: 0 8px 24px -16px rgba(0, 0, 0, 0.04), 0 4px 16px 0 rgba(0, 0, 0, 0.04);
71
+ --shadow-md: 0 8px 24px -16px rgba(0, 0, 0, 0.04), 0 4px 16px 0 rgba(0, 0, 0, 0.04);
70
72
  }
71
73
 
72
74
  @layer base {