@burtson-labs/ui 0.12.2 → 0.12.4
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/dist/components/chat-history.js +3 -2
- package/dist/components/chat-history.js.map +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.js +4 -4
- package/dist/components/command.js.map +1 -1
- package/dist/components/composer.js +2 -2
- package/dist/components/composer.js.map +1 -1
- package/dist/components/context-menu.js +1 -1
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/data-table.js +2 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/input.d.ts +15 -1
- package/dist/components/input.js +16 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/menubar.js +1 -1
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/message-actions.js +1 -1
- package/dist/components/message-actions.js.map +1 -1
- package/dist/components/popover.d.ts +7 -2
- package/dist/components/popover.js +7 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/secret-input.js +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/table.d.ts +13 -1
- package/dist/components/table.js +36 -3
- package/dist/components/table.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/mui/index.js +41 -7
- package/dist/mui/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
2
|
import { Button } from "./button.js";
|
|
3
3
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "./dropdown-menu.js";
|
|
4
|
+
import "./input.js";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import AlertCircle from "@burtson-labs/icons/react/alert-circle";
|
|
@@ -130,7 +131,7 @@ function ChatHistory({ items, activeId, onSelect, onNewChat, onRename, onDelete,
|
|
|
130
131
|
value: query,
|
|
131
132
|
onChange: (e) => setQuery(e.target.value),
|
|
132
133
|
placeholder: "Search chats",
|
|
133
|
-
className: "h-8 w-full rounded-md border border-input bg-surface pr-2 pl-8 text-base
|
|
134
|
+
className: cn("h-8 w-full rounded-md border border-input bg-surface pr-2 pl-8 text-base placeholder:text-muted-foreground sm:text-sm", "outline-hidden! focus-visible:border-ring focus-visible:inset-ring-1 focus-visible:inset-ring-ring aria-invalid:focus-visible:inset-ring-destructive")
|
|
134
135
|
})
|
|
135
136
|
]
|
|
136
137
|
})]
|
|
@@ -272,7 +273,7 @@ function RenameRow({ title, onDone }) {
|
|
|
272
273
|
onDone(title);
|
|
273
274
|
}
|
|
274
275
|
},
|
|
275
|
-
className: "h-8 w-full rounded-md border border-
|
|
276
|
+
className: "h-8 w-full rounded-md border border-ring bg-surface px-2 text-base inset-ring-1 inset-ring-ring outline-hidden! sm:text-sm"
|
|
276
277
|
})
|
|
277
278
|
});
|
|
278
279
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-history.js","names":["_Fragment"],"sources":["../../src/components/chat-history.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport Edit from '@burtson-labs/icons/react/edit';\nimport MessageSquarePlus from '@burtson-labs/icons/react/message-square-plus';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport Pin from '@burtson-labs/icons/react/pin';\nimport PinOff from '@burtson-labs/icons/react/pin-off';\nimport Search from '@burtson-labs/icons/react/search';\nimport Trash from '@burtson-labs/icons/react/trash';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './dropdown-menu';\n\nexport interface ChatHistoryItem {\n id: string;\n title: string;\n /** When the conversation last changed; decides its group. */\n updatedAt: Date | string | number;\n pinned?: boolean;\n /** A line of the latest message, searched along with the title. */\n preview?: string;\n}\n\nexport type ChatHistoryGroupKey = 'pinned' | 'today' | 'yesterday' | 'week' | 'older';\n\nexport interface ChatHistoryGroup {\n key: ChatHistoryGroupKey;\n label: string;\n items: ChatHistoryItem[];\n}\n\nconst LABELS: Record<ChatHistoryGroupKey, string> = {\n pinned: 'Pinned',\n today: 'Today',\n yesterday: 'Yesterday',\n week: 'Previous 7 days',\n older: 'Older',\n};\n\nconst time = (d: ChatHistoryItem['updatedAt']) => new Date(d).getTime();\nconst startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();\n\n/**\n * Pinned first, then Today, Yesterday, Previous 7 days and Older by the\n * local calendar, newest first in each. Empty groups are left out.\n */\nexport function groupConversations(\n items: ChatHistoryItem[],\n now: Date = new Date(),\n): ChatHistoryGroup[] {\n const today = startOfDay(now);\n const day = 24 * 60 * 60 * 1000;\n const buckets: Record<ChatHistoryGroupKey, ChatHistoryItem[]> = {\n pinned: [],\n today: [],\n yesterday: [],\n week: [],\n older: [],\n };\n for (const item of [...items].sort((a, b) => time(b.updatedAt) - time(a.updatedAt))) {\n if (item.pinned) {\n buckets.pinned.push(item);\n continue;\n }\n const t = startOfDay(new Date(item.updatedAt));\n const key: ChatHistoryGroupKey =\n t >= today\n ? 'today'\n : t >= today - day\n ? 'yesterday'\n : t >= today - 7 * day\n ? 'week'\n : 'older';\n buckets[key].push(item);\n }\n return (Object.keys(buckets) as ChatHistoryGroupKey[])\n .filter((k) => buckets[k].length > 0)\n .map((k) => ({ key: k, label: LABELS[k], items: buckets[k] }));\n}\n\n/** Case-insensitive match on every word, across title and preview. */\nexport function matchesConversation(item: ChatHistoryItem, query: string): boolean {\n const hay = `${item.title} ${item.preview ?? ''}`.toLowerCase();\n return query\n .toLowerCase()\n .split(/\\s+/)\n .filter(Boolean)\n .every((w) => hay.includes(w));\n}\n\nexport interface ChatHistoryProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: ChatHistoryItem[];\n activeId?: string;\n onSelect: (id: string) => void;\n /** Shows a \"New chat\" button at the top. */\n onNewChat?: () => void;\n /** Adds Rename to the row menu; the title is edited in place. */\n onRename?: (id: string, title: string) => void;\n /** Adds Delete to the row menu. Confirm or offer undo in your app. */\n onDelete?: (id: string) => void;\n /** Adds Pin / Unpin to the row menu. */\n onPinChange?: (id: string, pinned: boolean) => void;\n /** Controlled search text; leave out to let the list keep its own. */\n query?: string;\n onQueryChange?: (query: string) => void;\n /** Hides the search box, e.g. when the app searches on the server. */\n searchable?: boolean;\n loading?: boolean;\n /** Why the list could not load. Shown with Retry when `onRetry` is set. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no conversations at all. */\n empty?: React.ReactNode;\n /** The clock grouping uses; pass it in tests. */\n now?: Date;\n /**\n * Virtualisation hook for long histories: receives a group's items and the\n * row renderer, returns what to render (e.g. a windowed list). Default\n * renders every row.\n */\n renderItems?: (\n items: ChatHistoryItem[],\n renderItem: (item: ChatHistoryItem) => React.ReactNode,\n ) => React.ReactNode;\n label?: string;\n}\n\n/**\n * The conversation list beside a chat: a New chat action, search, and the\n * history grouped by day with pinned chats on top. Up and Down move between\n * rows, Home and End jump, Enter opens; each row has a menu for rename, pin\n * and delete. The app owns the data; this only reports what the person did.\n */\nfunction ChatHistory({\n items,\n activeId,\n onSelect,\n onNewChat,\n onRename,\n onDelete,\n onPinChange,\n query: controlledQuery,\n onQueryChange,\n searchable = true,\n loading = false,\n error,\n onRetry,\n empty,\n now,\n renderItems,\n label = 'Chat history',\n className,\n ...props\n}: ChatHistoryProps) {\n const [ownQuery, setOwnQuery] = React.useState('');\n const query = controlledQuery ?? ownQuery;\n const setQuery = (q: string) => {\n if (controlledQuery === undefined) setOwnQuery(q);\n onQueryChange?.(q);\n };\n const [renaming, setRenaming] = React.useState<string | null>(null);\n const list = React.useRef<HTMLDivElement>(null);\n const searchId = React.useId();\n\n const visible = query.trim() ? items.filter((i) => matchesConversation(i, query)) : items;\n const groups = groupConversations(visible, now);\n\n const onRowKeyDown = (e: React.KeyboardEvent) => {\n const rows = Array.from(\n list.current?.querySelectorAll<HTMLElement>('[data-slot=\"chat-history-row\"]') ?? [],\n );\n const i = rows.indexOf(document.activeElement as HTMLElement);\n let next = -1;\n if (e.key === 'ArrowDown') next = Math.min(rows.length - 1, i + 1);\n else if (e.key === 'ArrowUp') next = Math.max(0, i - 1);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = rows.length - 1;\n const row = next >= 0 ? rows[next] : undefined;\n if (row) {\n e.preventDefault();\n row.focus();\n }\n };\n\n const renderItem = (item: ChatHistoryItem) => (\n <HistoryRow\n key={item.id}\n item={item}\n active={item.id === activeId}\n renaming={renaming === item.id}\n onSelect={() => onSelect(item.id)}\n onStartRename={onRename ? () => setRenaming(item.id) : undefined}\n onRename={(title) => {\n setRenaming(null);\n if (onRename && title.trim() && title.trim() !== item.title)\n onRename(item.id, title.trim());\n }}\n onDelete={onDelete ? () => onDelete(item.id) : undefined}\n onPinChange={onPinChange ? (p) => onPinChange(item.id, p) : undefined}\n onKeyDown={onRowKeyDown}\n />\n );\n\n return (\n <div\n data-slot=\"chat-history\"\n aria-label={label}\n role=\"navigation\"\n className={cn('flex h-full min-h-0 flex-col', className)}\n {...props}\n >\n {(onNewChat || searchable) && (\n <div className=\"grid gap-2 p-2\">\n {onNewChat && (\n <Button variant=\"outline\" className=\"justify-start\" onClick={onNewChat}>\n <MessageSquarePlus /> New chat\n </Button>\n )}\n {searchable && (\n <label htmlFor={searchId} className=\"relative block\">\n <span className=\"sr-only\">Search chats</span>\n <Search\n aria-hidden\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n id={searchId}\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search chats\"\n className=\"h-8 w-full rounded-md border border-input bg-surface pr-2 pl-8 text-base outline-none placeholder:text-muted-foreground focus-visible:border-brand focus-visible:ring-[3px] focus-visible:ring-ring/15 sm:text-sm\"\n />\n </label>\n )}\n </div>\n )}\n <div ref={list} className=\"min-h-0 flex-1 overflow-y-auto px-2 pb-2\">\n {loading ? (\n <div role=\"status\" aria-label=\"Loading chats\" className=\"grid gap-1.5 pt-1\">\n {[72, 56, 64, 48, 60].map((w, i) => (\n <div key={i} className=\"h-8 rounded-md bg-muted/70 motion-safe:animate-pulse\">\n <span className=\"sr-only\">Loading</span>\n <span\n aria-hidden\n className=\"ml-2 inline-block h-2.5 translate-y-3 rounded bg-muted-foreground/15\"\n style={{ width: `${w}%` }}\n />\n </div>\n ))}\n </div>\n ) : error ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2 p-2 text-sm\">\n <span className=\"inline-flex items-center gap-1.5 font-medium text-destructive [&_svg]:size-4\">\n <AlertCircle aria-hidden /> Chats could not load\n </span>\n <span className=\"text-muted-foreground\">{error}</span>\n {onRetry && (\n <Button size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n ) : items.length === 0 ? (\n <div className=\"p-3 text-sm text-muted-foreground\">\n {empty ?? 'No chats yet. Start one and it will appear here.'}\n </div>\n ) : groups.length === 0 ? (\n <p role=\"status\" className=\"p-3 text-sm text-muted-foreground\">\n No chats match “{query}”.\n </p>\n ) : (\n groups.map((g) => (\n <section key={g.key} aria-label={g.label} className=\"mt-2 first:mt-0\">\n <h3 className=\"px-2 pt-2 pb-1 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase\">\n {g.label}\n </h3>\n <ul className=\"grid grid-cols-[minmax(0,1fr)] gap-0.5\">\n {renderItems ? renderItems(g.items, renderItem) : g.items.map(renderItem)}\n </ul>\n </section>\n ))\n )}\n </div>\n </div>\n );\n}\n\nfunction HistoryRow({\n item,\n active,\n renaming,\n onSelect,\n onStartRename,\n onRename,\n onDelete,\n onPinChange,\n onKeyDown,\n}: {\n item: ChatHistoryItem;\n active: boolean;\n renaming: boolean;\n onSelect: () => void;\n onStartRename?: () => void;\n onRename: (title: string) => void;\n onDelete?: () => void;\n onPinChange?: (pinned: boolean) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n}) {\n const hasMenu = Boolean(onStartRename || onDelete || onPinChange);\n if (renaming) return <RenameRow title={item.title} onDone={onRename} />;\n return (\n <li className=\"group/row relative min-w-0\">\n <button\n type=\"button\"\n data-slot=\"chat-history-row\"\n aria-current={active ? 'page' : undefined}\n onClick={onSelect}\n onKeyDown={onKeyDown}\n className={cn(\n 'flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-sm outline-none transition-colors hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/20 pointer-coarse:h-11',\n hasMenu && 'pr-9',\n active && 'bg-accent font-medium text-accent-foreground hover:bg-accent',\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">{item.title || 'Untitled chat'}</span>\n </button>\n {hasMenu && (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`Actions for ${item.title || 'chat'}`}\n className={cn(\n 'absolute top-1/2 right-1 grid size-7 -translate-y-1/2 place-items-center rounded-sm text-muted-foreground opacity-0 outline-none transition-opacity hover:bg-background hover:text-foreground focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/20 group-hover/row:opacity-100 data-[state=open]:opacity-100 pointer-coarse:size-9 pointer-coarse:opacity-100 [&_svg]:size-4',\n active && 'opacity-100',\n )}\n >\n <MoreHorizontal aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-40\">\n {onStartRename && (\n <DropdownMenuItem onSelect={onStartRename}>\n <Edit aria-hidden /> Rename\n </DropdownMenuItem>\n )}\n {onPinChange && (\n <DropdownMenuItem onSelect={() => onPinChange(!item.pinned)}>\n {item.pinned ? <PinOff aria-hidden /> : <Pin aria-hidden />}\n {item.pinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n )}\n {onDelete && (\n <>\n {(onStartRename || onPinChange) && <DropdownMenuSeparator />}\n <DropdownMenuItem variant=\"destructive\" onSelect={onDelete}>\n <Trash aria-hidden /> Delete\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </li>\n );\n}\n\n/** Rename in place: Enter or leaving the field saves, Escape keeps the old title. */\nfunction RenameRow({ title, onDone }: { title: string; onDone: (title: string) => void }) {\n const [draft, setDraft] = React.useState(title);\n const input = React.useRef<HTMLInputElement>(null);\n React.useEffect(() => {\n input.current?.select();\n }, []);\n return (\n <li className=\"px-1 py-0.5\">\n <input\n ref={input}\n aria-label=\"Chat title\"\n value={draft}\n // eslint-disable-next-line jsx-a11y/no-autofocus -- rename was just asked for\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n onBlur={() => onDone(draft)}\n onKeyDown={(e) => {\n if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n onDone(draft);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n onDone(title);\n }\n }}\n className=\"h-8 w-full rounded-md border border-brand bg-surface px-2 text-base ring-[3px] ring-ring/15 outline-none sm:text-sm\"\n />\n </li>\n );\n}\n\nexport { ChatHistory };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-history.js","names":["_Fragment"],"sources":["../../src/components/chat-history.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport Edit from '@burtson-labs/icons/react/edit';\nimport MessageSquarePlus from '@burtson-labs/icons/react/message-square-plus';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport Pin from '@burtson-labs/icons/react/pin';\nimport PinOff from '@burtson-labs/icons/react/pin-off';\nimport Search from '@burtson-labs/icons/react/search';\nimport Trash from '@burtson-labs/icons/react/trash';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './dropdown-menu';\nimport { fieldFocusClasses } from './input';\n\nexport interface ChatHistoryItem {\n id: string;\n title: string;\n /** When the conversation last changed; decides its group. */\n updatedAt: Date | string | number;\n pinned?: boolean;\n /** A line of the latest message, searched along with the title. */\n preview?: string;\n}\n\nexport type ChatHistoryGroupKey = 'pinned' | 'today' | 'yesterday' | 'week' | 'older';\n\nexport interface ChatHistoryGroup {\n key: ChatHistoryGroupKey;\n label: string;\n items: ChatHistoryItem[];\n}\n\nconst LABELS: Record<ChatHistoryGroupKey, string> = {\n pinned: 'Pinned',\n today: 'Today',\n yesterday: 'Yesterday',\n week: 'Previous 7 days',\n older: 'Older',\n};\n\nconst time = (d: ChatHistoryItem['updatedAt']) => new Date(d).getTime();\nconst startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();\n\n/**\n * Pinned first, then Today, Yesterday, Previous 7 days and Older by the\n * local calendar, newest first in each. Empty groups are left out.\n */\nexport function groupConversations(\n items: ChatHistoryItem[],\n now: Date = new Date(),\n): ChatHistoryGroup[] {\n const today = startOfDay(now);\n const day = 24 * 60 * 60 * 1000;\n const buckets: Record<ChatHistoryGroupKey, ChatHistoryItem[]> = {\n pinned: [],\n today: [],\n yesterday: [],\n week: [],\n older: [],\n };\n for (const item of [...items].sort((a, b) => time(b.updatedAt) - time(a.updatedAt))) {\n if (item.pinned) {\n buckets.pinned.push(item);\n continue;\n }\n const t = startOfDay(new Date(item.updatedAt));\n const key: ChatHistoryGroupKey =\n t >= today\n ? 'today'\n : t >= today - day\n ? 'yesterday'\n : t >= today - 7 * day\n ? 'week'\n : 'older';\n buckets[key].push(item);\n }\n return (Object.keys(buckets) as ChatHistoryGroupKey[])\n .filter((k) => buckets[k].length > 0)\n .map((k) => ({ key: k, label: LABELS[k], items: buckets[k] }));\n}\n\n/** Case-insensitive match on every word, across title and preview. */\nexport function matchesConversation(item: ChatHistoryItem, query: string): boolean {\n const hay = `${item.title} ${item.preview ?? ''}`.toLowerCase();\n return query\n .toLowerCase()\n .split(/\\s+/)\n .filter(Boolean)\n .every((w) => hay.includes(w));\n}\n\nexport interface ChatHistoryProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: ChatHistoryItem[];\n activeId?: string;\n onSelect: (id: string) => void;\n /** Shows a \"New chat\" button at the top. */\n onNewChat?: () => void;\n /** Adds Rename to the row menu; the title is edited in place. */\n onRename?: (id: string, title: string) => void;\n /** Adds Delete to the row menu. Confirm or offer undo in your app. */\n onDelete?: (id: string) => void;\n /** Adds Pin / Unpin to the row menu. */\n onPinChange?: (id: string, pinned: boolean) => void;\n /** Controlled search text; leave out to let the list keep its own. */\n query?: string;\n onQueryChange?: (query: string) => void;\n /** Hides the search box, e.g. when the app searches on the server. */\n searchable?: boolean;\n loading?: boolean;\n /** Why the list could not load. Shown with Retry when `onRetry` is set. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no conversations at all. */\n empty?: React.ReactNode;\n /** The clock grouping uses; pass it in tests. */\n now?: Date;\n /**\n * Virtualisation hook for long histories: receives a group's items and the\n * row renderer, returns what to render (e.g. a windowed list). Default\n * renders every row.\n */\n renderItems?: (\n items: ChatHistoryItem[],\n renderItem: (item: ChatHistoryItem) => React.ReactNode,\n ) => React.ReactNode;\n label?: string;\n}\n\n/**\n * The conversation list beside a chat: a New chat action, search, and the\n * history grouped by day with pinned chats on top. Up and Down move between\n * rows, Home and End jump, Enter opens; each row has a menu for rename, pin\n * and delete. The app owns the data; this only reports what the person did.\n */\nfunction ChatHistory({\n items,\n activeId,\n onSelect,\n onNewChat,\n onRename,\n onDelete,\n onPinChange,\n query: controlledQuery,\n onQueryChange,\n searchable = true,\n loading = false,\n error,\n onRetry,\n empty,\n now,\n renderItems,\n label = 'Chat history',\n className,\n ...props\n}: ChatHistoryProps) {\n const [ownQuery, setOwnQuery] = React.useState('');\n const query = controlledQuery ?? ownQuery;\n const setQuery = (q: string) => {\n if (controlledQuery === undefined) setOwnQuery(q);\n onQueryChange?.(q);\n };\n const [renaming, setRenaming] = React.useState<string | null>(null);\n const list = React.useRef<HTMLDivElement>(null);\n const searchId = React.useId();\n\n const visible = query.trim() ? items.filter((i) => matchesConversation(i, query)) : items;\n const groups = groupConversations(visible, now);\n\n const onRowKeyDown = (e: React.KeyboardEvent) => {\n const rows = Array.from(\n list.current?.querySelectorAll<HTMLElement>('[data-slot=\"chat-history-row\"]') ?? [],\n );\n const i = rows.indexOf(document.activeElement as HTMLElement);\n let next = -1;\n if (e.key === 'ArrowDown') next = Math.min(rows.length - 1, i + 1);\n else if (e.key === 'ArrowUp') next = Math.max(0, i - 1);\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = rows.length - 1;\n const row = next >= 0 ? rows[next] : undefined;\n if (row) {\n e.preventDefault();\n row.focus();\n }\n };\n\n const renderItem = (item: ChatHistoryItem) => (\n <HistoryRow\n key={item.id}\n item={item}\n active={item.id === activeId}\n renaming={renaming === item.id}\n onSelect={() => onSelect(item.id)}\n onStartRename={onRename ? () => setRenaming(item.id) : undefined}\n onRename={(title) => {\n setRenaming(null);\n if (onRename && title.trim() && title.trim() !== item.title)\n onRename(item.id, title.trim());\n }}\n onDelete={onDelete ? () => onDelete(item.id) : undefined}\n onPinChange={onPinChange ? (p) => onPinChange(item.id, p) : undefined}\n onKeyDown={onRowKeyDown}\n />\n );\n\n return (\n <div\n data-slot=\"chat-history\"\n aria-label={label}\n role=\"navigation\"\n className={cn('flex h-full min-h-0 flex-col', className)}\n {...props}\n >\n {(onNewChat || searchable) && (\n <div className=\"grid gap-2 p-2\">\n {onNewChat && (\n <Button variant=\"outline\" className=\"justify-start\" onClick={onNewChat}>\n <MessageSquarePlus /> New chat\n </Button>\n )}\n {searchable && (\n <label htmlFor={searchId} className=\"relative block\">\n <span className=\"sr-only\">Search chats</span>\n <Search\n aria-hidden\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n id={searchId}\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search chats\"\n className={cn(\n 'h-8 w-full rounded-md border border-input bg-surface pr-2 pl-8 text-base placeholder:text-muted-foreground sm:text-sm',\n fieldFocusClasses,\n )}\n />\n </label>\n )}\n </div>\n )}\n <div ref={list} className=\"min-h-0 flex-1 overflow-y-auto px-2 pb-2\">\n {loading ? (\n <div role=\"status\" aria-label=\"Loading chats\" className=\"grid gap-1.5 pt-1\">\n {[72, 56, 64, 48, 60].map((w, i) => (\n <div key={i} className=\"h-8 rounded-md bg-muted/70 motion-safe:animate-pulse\">\n <span className=\"sr-only\">Loading</span>\n <span\n aria-hidden\n className=\"ml-2 inline-block h-2.5 translate-y-3 rounded bg-muted-foreground/15\"\n style={{ width: `${w}%` }}\n />\n </div>\n ))}\n </div>\n ) : error ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2 p-2 text-sm\">\n <span className=\"inline-flex items-center gap-1.5 font-medium text-destructive [&_svg]:size-4\">\n <AlertCircle aria-hidden /> Chats could not load\n </span>\n <span className=\"text-muted-foreground\">{error}</span>\n {onRetry && (\n <Button size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n ) : items.length === 0 ? (\n <div className=\"p-3 text-sm text-muted-foreground\">\n {empty ?? 'No chats yet. Start one and it will appear here.'}\n </div>\n ) : groups.length === 0 ? (\n <p role=\"status\" className=\"p-3 text-sm text-muted-foreground\">\n No chats match “{query}”.\n </p>\n ) : (\n groups.map((g) => (\n <section key={g.key} aria-label={g.label} className=\"mt-2 first:mt-0\">\n <h3 className=\"px-2 pt-2 pb-1 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase\">\n {g.label}\n </h3>\n <ul className=\"grid grid-cols-[minmax(0,1fr)] gap-0.5\">\n {renderItems ? renderItems(g.items, renderItem) : g.items.map(renderItem)}\n </ul>\n </section>\n ))\n )}\n </div>\n </div>\n );\n}\n\nfunction HistoryRow({\n item,\n active,\n renaming,\n onSelect,\n onStartRename,\n onRename,\n onDelete,\n onPinChange,\n onKeyDown,\n}: {\n item: ChatHistoryItem;\n active: boolean;\n renaming: boolean;\n onSelect: () => void;\n onStartRename?: () => void;\n onRename: (title: string) => void;\n onDelete?: () => void;\n onPinChange?: (pinned: boolean) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n}) {\n const hasMenu = Boolean(onStartRename || onDelete || onPinChange);\n if (renaming) return <RenameRow title={item.title} onDone={onRename} />;\n return (\n <li className=\"group/row relative min-w-0\">\n <button\n type=\"button\"\n data-slot=\"chat-history-row\"\n aria-current={active ? 'page' : undefined}\n onClick={onSelect}\n onKeyDown={onKeyDown}\n className={cn(\n 'flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-sm outline-none transition-colors hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/20 pointer-coarse:h-11',\n hasMenu && 'pr-9',\n active && 'bg-accent font-medium text-accent-foreground hover:bg-accent',\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">{item.title || 'Untitled chat'}</span>\n </button>\n {hasMenu && (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`Actions for ${item.title || 'chat'}`}\n className={cn(\n 'absolute top-1/2 right-1 grid size-7 -translate-y-1/2 place-items-center rounded-sm text-muted-foreground opacity-0 outline-none transition-opacity hover:bg-background hover:text-foreground focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/20 group-hover/row:opacity-100 data-[state=open]:opacity-100 pointer-coarse:size-9 pointer-coarse:opacity-100 [&_svg]:size-4',\n active && 'opacity-100',\n )}\n >\n <MoreHorizontal aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-40\">\n {onStartRename && (\n <DropdownMenuItem onSelect={onStartRename}>\n <Edit aria-hidden /> Rename\n </DropdownMenuItem>\n )}\n {onPinChange && (\n <DropdownMenuItem onSelect={() => onPinChange(!item.pinned)}>\n {item.pinned ? <PinOff aria-hidden /> : <Pin aria-hidden />}\n {item.pinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n )}\n {onDelete && (\n <>\n {(onStartRename || onPinChange) && <DropdownMenuSeparator />}\n <DropdownMenuItem variant=\"destructive\" onSelect={onDelete}>\n <Trash aria-hidden /> Delete\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </li>\n );\n}\n\n/** Rename in place: Enter or leaving the field saves, Escape keeps the old title. */\nfunction RenameRow({ title, onDone }: { title: string; onDone: (title: string) => void }) {\n const [draft, setDraft] = React.useState(title);\n const input = React.useRef<HTMLInputElement>(null);\n React.useEffect(() => {\n input.current?.select();\n }, []);\n return (\n <li className=\"px-1 py-0.5\">\n <input\n ref={input}\n aria-label=\"Chat title\"\n value={draft}\n // eslint-disable-next-line jsx-a11y/no-autofocus -- rename was just asked for\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n onBlur={() => onDone(draft)}\n onKeyDown={(e) => {\n if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n onDone(draft);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n onDone(title);\n }\n }}\n className=\"h-8 w-full rounded-md border border-ring bg-surface px-2 text-base inset-ring-1 inset-ring-ring outline-hidden! sm:text-sm\"\n />\n </li>\n );\n}\n\nexport { ChatHistory };\n"],"mappings":";;;;;;;;;;;;;;;AAwCA,IAAM,SAA8C;CAClD,QAAQ;CACR,OAAO;CACP,WAAW;CACX,MAAM;CACN,OAAO;AACT;AAEA,IAAM,QAAQ,MAAoC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ;AACtE,IAAM,cAAc,MAAY,IAAI,KAAK,EAAE,YAAY,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ;;;;;AAM7F,SAAgB,mBACd,OACA,sBAAY,IAAI,KAAK,GACD;CACpB,MAAM,QAAQ,WAAW,GAAG;CAC5B,MAAM,MAAM;CACZ,MAAM,UAA0D;EAC9D,QAAQ,CAAC;EACT,OAAO,CAAC;EACR,WAAW,CAAC;EACZ,MAAM,CAAC;EACP,OAAO,CAAC;CACV;CACA,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,KAAK,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,CAAC,GAAG;EACnF,IAAI,KAAK,QAAQ;GACf,QAAQ,OAAO,KAAK,IAAI;GACxB;EACF;EACA,MAAM,IAAI,WAAW,IAAI,KAAK,KAAK,SAAS,CAAC;EAS7C,QAPE,KAAK,QACD,UACA,KAAK,QAAQ,MACX,cACA,KAAK,QAAQ,IAAI,MACf,SACA,QACE,CAAC,KAAK,IAAI;CACxB;CACA,OAAQ,OAAO,KAAK,OAAO,CAAC,CACzB,QAAQ,MAAM,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,CACpC,KAAK,OAAO;EAAE,KAAK;EAAG,OAAO,OAAO;EAAI,OAAO,QAAQ;CAAG,EAAE;AACjE;;AAGA,SAAgB,oBAAoB,MAAuB,OAAwB;CACjF,MAAM,MAAM,GAAG,KAAK,MAAM,GAAG,KAAK,WAAW,KAAK,YAAY;CAC9D,OAAO,MACJ,YAAY,CAAC,CACb,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,OAAO,MAAM,IAAI,SAAS,CAAC,CAAC;AACjC;;;;;;;AA6CA,SAAS,YAAY,EACnB,OACA,UACA,UACA,WACA,UACA,UACA,aACA,OAAO,iBACP,eACA,aAAa,MACb,UAAU,OACV,OACA,SACA,OACA,KACA,aACA,QAAQ,gBACR,WACA,GAAG,SACgB;CACnB,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,EAAE;CACjD,MAAM,QAAQ,mBAAmB;CACjC,MAAM,YAAY,MAAc;EAC9B,IAAI,oBAAoB,KAAA,GAAW,YAAY,CAAC;EAChD,gBAAgB,CAAC;CACnB;CACA,MAAM,CAAC,UAAU,eAAe,MAAM,SAAwB,IAAI;CAClE,MAAM,OAAO,MAAM,OAAuB,IAAI;CAC9C,MAAM,WAAW,MAAM,MAAM;CAG7B,MAAM,SAAS,mBADC,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,oBAAoB,GAAG,KAAK,CAAC,IAAI,OACzC,GAAG;CAE9C,MAAM,gBAAgB,MAA2B;EAC/C,MAAM,OAAO,MAAM,KACjB,KAAK,SAAS,iBAA8B,kCAAgC,KAAK,CAAC,CACpF;EACA,MAAM,IAAI,KAAK,QAAQ,SAAS,aAA4B;EAC5D,IAAI,OAAO;EACX,IAAI,EAAE,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,SAAS,GAAG,IAAI,CAAC;OAC5D,IAAI,EAAE,QAAQ,WAAW,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;OACjD,IAAI,EAAE,QAAQ,QAAQ,OAAO;OAC7B,IAAI,EAAE,QAAQ,OAAO,OAAO,KAAK,SAAS;EAC/C,MAAM,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAA;EACrC,IAAI,KAAK;GACP,EAAE,eAAe;GACjB,IAAI,MAAM;EACZ;CACF;CAEA,MAAM,cAAc,SAClB,oBAAC,YAAD;EAEQ;EACN,QAAQ,KAAK,OAAO;EACpB,UAAU,aAAa,KAAK;EAC5B,gBAAgB,SAAS,KAAK,EAAE;EAChC,eAAe,iBAAiB,YAAY,KAAK,EAAE,IAAI,KAAA;EACvD,WAAW,UAAU;GACnB,YAAY,IAAI;GAChB,IAAI,YAAY,MAAM,KAAK,KAAK,MAAM,KAAK,MAAM,KAAK,OACpD,SAAS,KAAK,IAAI,MAAM,KAAK,CAAC;EAClC;EACA,UAAU,iBAAiB,SAAS,KAAK,EAAE,IAAI,KAAA;EAC/C,aAAa,eAAe,MAAM,YAAY,KAAK,IAAI,CAAC,IAAI,KAAA;EAC5D,WAAW;CACZ,GAdM,KAAK,EAcX;CAGH,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY;EACZ,MAAK;EACL,WAAW,GAAG,gCAAgC,SAAS;EACvD,GAAI;EALN,UAAA,EAOI,aAAa,eACb,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACG,aACC,qBAAC,QAAD;IAAQ,SAAQ;IAAU,WAAU;IAAgB,SAAS;IAA7D,UAAA,CACE,oBAAC,mBAAD,CAAoB,CAAA,GAAC,WACf;GAET,CAAA,GAAA,cACC,qBAAC,SAAD;IAAO,SAAS;IAAU,WAAU;IAApC,UAAA;KACE,oBAAC,QAAD;MAAM,WAAU;MAAU,UAAA;KAAkB,CAAA;KAC5C,oBAAC,QAAD;MACE,eAAA;MACA,WAAU;KACX,CAAA;KACD,oBAAC,SAAD;MACE,IAAI;MACJ,MAAK;MACL,OAAO;MACP,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;MACxC,aAAY;MACZ,WAAW,GACT,yHAAA,sJAEF;KACD,CAAA;IACI;GAEN,CAAA,CAAA;EAEP,CAAA,GAAA,oBAAC,OAAD;GAAK,KAAK;GAAM,WAAU;GACvB,UAAA,UACC,oBAAC,OAAD;IAAK,MAAK;IAAS,cAAW;IAAgB,WAAU;IACrD,UAAA;KAAC;KAAI;KAAI;KAAI;KAAI;IAAE,CAAC,CAAC,KAAK,GAAG,MAC5B,qBAAC,OAAD;KAAa,WAAU;KAAvB,UAAA,CACE,oBAAC,QAAD;MAAM,WAAU;MAAU,UAAA;KAAa,CAAA,GACvC,oBAAC,QAAD;MACE,eAAA;MACA,WAAU;MACV,OAAO,EAAE,OAAO,GAAG,EAAE,GAAG;KACzB,CAAA,CACE;IAPK,GAAA,CAOL,CACN;GACE,CAAA,IACH,QACF,qBAAC,OAAD;IAAK,MAAK;IAAQ,WAAU;IAA5B,UAAA;KACE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,oBAAC,aAAD,EAAa,eAAA,KAAa,CAAA,GAAC,uBACvB;;KACN,oBAAC,QAAD;MAAM,WAAU;MAAyB,UAAA;KAAY,CAAA;KACpD,WACC,oBAAC,QAAD;MAAQ,MAAK;MAAK,SAAQ;MAAU,SAAS;MAAS,UAAA;KAE9C,CAAA;IAEP;GACH,CAAA,IAAA,MAAM,WAAW,IACnB,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,SAAS;GACP,CAAA,IACH,OAAO,WAAW,IACpB,qBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;IAA3B,UAAA;KAA+D;KACtC;KAAM;IAC5B;GAEH,CAAA,IAAA,OAAO,KAAK,MACV,qBAAC,WAAD;IAAqB,cAAY,EAAE;IAAO,WAAU;IAApD,UAAA,CACE,oBAAC,MAAD;KAAI,WAAU;KACX,UAAA,EAAE;IACD,CAAA,GACJ,oBAAC,MAAD;KAAI,WAAU;KACX,UAAA,cAAc,YAAY,EAAE,OAAO,UAAU,IAAI,EAAE,MAAM,IAAI,UAAU;IACtE,CAAA,CACG;GAPK,GAAA,EAAE,GAOP,CACV;EAEA,CAAA,CACF;;AAET;AAEA,SAAS,WAAW,EAClB,MACA,QACA,UACA,UACA,eACA,UACA,UACA,aACA,aAWC;CACD,MAAM,UAAU,QAAQ,iBAAiB,YAAY,WAAW;CAChE,IAAI,UAAU,OAAO,oBAAC,WAAD;EAAW,OAAO,KAAK;EAAO,QAAQ;CAAW,CAAA;CACtE,OACE,qBAAC,MAAD;EAAI,WAAU;EAAd,UAAA,CACE,oBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,gBAAc,SAAS,SAAS,KAAA;GAChC,SAAS;GACE;GACX,WAAW,GACT,8LACA,WAAW,QACX,UAAU,8DACZ;GAEA,UAAA,oBAAC,QAAD;IAAM,WAAU;IAA2B,UAAA,KAAK,SAAS;GAAsB,CAAA;EACzE,CAAA,GACP,WACC,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;GACE,cAAY,eAAe,KAAK,SAAS;GACzC,WAAW,GACT,yYACA,UAAU,aACZ;GAEA,UAAA,oBAAC,gBAAD,EAAgB,eAAA,KAAa,CAAA;EACV,CAAA,GACrB,qBAAC,qBAAD;GAAqB,OAAM;GAAM,WAAU;GAA3C,UAAA;IACG,iBACC,qBAAC,kBAAD;KAAkB,UAAU;KAA5B,UAAA,CACE,oBAAC,MAAD,EAAM,eAAA,KAAa,CAAA,GAAC,SACJ;;IAEnB,eACC,qBAAC,kBAAD;KAAkB,gBAAgB,YAAY,CAAC,KAAK,MAAM;KAA1D,UAAA,CACG,KAAK,SAAS,oBAAC,QAAD,EAAQ,eAAA,KAAa,CAAA,IAAI,oBAAC,KAAD,EAAK,eAAA,KAAa,CAAA,GACzD,KAAK,SAAS,UAAU,KACT;;IAEnB,YACC,qBAAAA,UAAA,EAAA,UAAA,EACI,iBAAiB,gBAAgB,oBAAC,uBAAD,CAAwB,CAAA,GAC3D,qBAAC,kBAAD;KAAkB,SAAQ;KAAc,UAAU;KAAlD,UAAA,CACE,oBAAC,OAAD,EAAO,eAAA,KAAa,CAAA,GAAC,SACL;IAClB,CAAA,CAAA,EAAA,CAAA;GAEe;EACT,CAAA,CAAA,EAAA,CAAA,CAEd;;AAER;;AAGA,SAAS,UAAU,EAAE,OAAO,UAA8D;CACxF,MAAM,CAAC,OAAO,YAAY,MAAM,SAAS,KAAK;CAC9C,MAAM,QAAQ,MAAM,OAAyB,IAAI;CACjD,MAAM,gBAAgB;EACpB,MAAM,SAAS,OAAO;CACxB,GAAG,CAAC,CAAC;CACL,OACE,oBAAC,MAAD;EAAI,WAAU;EACZ,UAAA,oBAAC,SAAD;GACE,KAAK;GACL,cAAW;GACX,OAAO;GAEP,WAAA;GACA,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;GACxC,cAAc,OAAO,KAAK;GAC1B,YAAY,MAAM;IAChB,IAAI,EAAE,YAAY,eAAe,EAAE,YAAY,YAAY,KAAK;IAChE,IAAI,EAAE,QAAQ,SAAS;KACrB,EAAE,eAAe;KACjB,OAAO,KAAK;IACd,OAAO,IAAI,EAAE,QAAQ,UAAU;KAC7B,EAAE,eAAe;KACjB,OAAO,KAAK;IACd;GACF;GACA,WAAU;EACX,CAAA;CACC,CAAA;AAER"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
2
|
import { Spinner } from "./spinner.js";
|
|
3
3
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
4
|
-
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command.js";
|
|
5
4
|
import { fieldClasses } from "./input.js";
|
|
5
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command.js";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
import Check from "@burtson-labs/icons/react/check";
|
|
@@ -8,7 +8,7 @@ import { Command } from "cmdk";
|
|
|
8
8
|
function Command$1({ className, ...props }) {
|
|
9
9
|
return /* @__PURE__ */ jsx(Command, {
|
|
10
10
|
"data-slot": "command",
|
|
11
|
-
className: cn("flex h-full w-full flex-col overflow-hidden rounded-lg bg-surface-raised text-popover-foreground", className),
|
|
11
|
+
className: cn("flex h-full w-full flex-col overflow-hidden rounded-lg bg-surface-raised text-popover-foreground has-[[cmdk-input]:focus-visible]:border-ring", className),
|
|
12
12
|
...props
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -38,7 +38,7 @@ function CommandInput({ className, ...props }) {
|
|
|
38
38
|
"aria-hidden": true
|
|
39
39
|
}), /* @__PURE__ */ jsx(Command.Input, {
|
|
40
40
|
"data-slot": "command-input",
|
|
41
|
-
className: cn("flex h-10 w-full bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
41
|
+
className: cn("flex h-10 w-full rounded-none border-0 bg-transparent py-3 text-sm shadow-none outline-hidden! placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
42
42
|
...props
|
|
43
43
|
})]
|
|
44
44
|
});
|
|
@@ -60,7 +60,7 @@ function CommandEmpty(props) {
|
|
|
60
60
|
function CommandGroup({ className, ...props }) {
|
|
61
61
|
return /* @__PURE__ */ jsx(Command.Group, {
|
|
62
62
|
"data-slot": "command-group",
|
|
63
|
-
className: cn("overflow-hidden
|
|
63
|
+
className: cn("overflow-hidden py-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", className),
|
|
64
64
|
...props
|
|
65
65
|
});
|
|
66
66
|
}
|
|
@@ -74,7 +74,7 @@ function CommandSeparator({ className, ...props }) {
|
|
|
74
74
|
function CommandItem({ className, ...props }) {
|
|
75
75
|
return /* @__PURE__ */ jsx(Command.Item, {
|
|
76
76
|
"data-slot": "command-item",
|
|
77
|
-
className: cn("relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-
|
|
77
|
+
className: cn("relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden! select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-secondary data-[selected=true]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
|
|
78
78
|
...props
|
|
79
79
|
});
|
|
80
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.js","names":["Command","CommandPrimitive"],"sources":["../../src/components/command.tsx"],"sourcesContent":["import Search from '@burtson-labs/icons/react/search';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog';\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n data-slot=\"command\"\n className={cn(\n 'flex h-full w-full flex-col overflow-hidden rounded-lg bg-surface-raised text-popover-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\n/** A command palette in a dialog, the usual ⌘K surface. */\nfunction CommandDialog({\n title = 'Command palette',\n description = 'Search for a page or an action',\n children,\n className,\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n title?: string;\n description?: string;\n className?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogContent\n className={cn(\n 'top-[20%] translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-xl sm:p-0',\n className,\n )}\n showCloseButton={false}\n >\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <Command className=\"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-2\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div data-slot=\"command-input-wrapper\" className=\"flex h-11 items-center gap-2 border-b px-3\">\n <Search className=\"size-4 shrink-0 opacity-50\" aria-hidden />\n <CommandPrimitive.Input\n data-slot=\"command-input\"\n className={cn(\n 'flex h-10 w-full bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n data-slot=\"command-list\"\n className={cn('max-h-80 scroll-py-1 overflow-x-hidden overflow-y-auto p-1', className)}\n {...props}\n />\n );\n}\n\nfunction CommandEmpty(props: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n data-slot=\"command-empty\"\n className=\"py-6 text-center text-sm text-muted-foreground\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n data-slot=\"command-group\"\n className={cn(\n 'overflow-hidden
|
|
1
|
+
{"version":3,"file":"command.js","names":["Command","CommandPrimitive"],"sources":["../../src/components/command.tsx"],"sourcesContent":["import Search from '@burtson-labs/icons/react/search';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog';\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n data-slot=\"command\"\n className={cn(\n // Standalone with a border, the box is the field: its border takes the\n // ring colour while the search input has keyboard focus. In a popover or\n // dialog there is no border of its own, so the rule draws nothing.\n 'flex h-full w-full flex-col overflow-hidden rounded-lg bg-surface-raised text-popover-foreground has-[[cmdk-input]:focus-visible]:border-ring',\n className,\n )}\n {...props}\n />\n );\n}\n\n/** A command palette in a dialog, the usual ⌘K surface. */\nfunction CommandDialog({\n title = 'Command palette',\n description = 'Search for a page or an action',\n children,\n className,\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n title?: string;\n description?: string;\n className?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogContent\n className={cn(\n 'top-[20%] translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-xl sm:p-0',\n className,\n )}\n showCloseButton={false}\n >\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <Command className=\"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-2\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n // The row is the field (icon + input, ruled off from the list); the input\n // itself draws no border or outline, whatever the host page's CSS says.\n <div data-slot=\"command-input-wrapper\" className=\"flex h-11 items-center gap-2 border-b px-3\">\n <Search className=\"size-4 shrink-0 opacity-50\" aria-hidden />\n <CommandPrimitive.Input\n data-slot=\"command-input\"\n className={cn(\n 'flex h-10 w-full rounded-none border-0 bg-transparent py-3 text-sm shadow-none outline-hidden! placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n data-slot=\"command-list\"\n className={cn('max-h-80 scroll-py-1 overflow-x-hidden overflow-y-auto p-1', className)}\n {...props}\n />\n );\n}\n\nfunction CommandEmpty(props: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n data-slot=\"command-empty\"\n className=\"py-6 text-center text-sm text-muted-foreground\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n data-slot=\"command-group\"\n className={cn(\n 'overflow-hidden py-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n data-slot=\"command-separator\"\n className={cn('-mx-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n data-slot=\"command-item\"\n className={cn(\n \"relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden! select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-secondary data-[selected=true]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"command-shortcut\"\n className={cn(\n 'ml-auto rounded-xs border border-border bg-surface-muted px-1.5 font-mono text-[11px] leading-5 text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Command,\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n};\n"],"mappings":";;;;;;;AAQA,SAASA,UAAQ,EAAE,WAAW,GAAG,SAAwD;CACvF,OACE,oBAACC,SAAD;EACE,aAAU;EACV,WAAW,GAIT,iJACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;;AAGA,SAAS,cAAc,EACrB,QAAQ,mBACR,cAAc,kCACd,UACA,WACA,GAAG,SAKF;CACD,OACE,oBAAC,QAAD;EAAQ,GAAI;EACV,UAAA,qBAAC,eAAD;GACE,WAAW,GACT,wEACA,SACF;GACA,iBAAiB;GALnB,UAAA,CAOE,qBAAC,cAAD;IAAc,WAAU;IAAxB,UAAA,CACE,oBAAC,aAAD,EAAA,UAAc,MAAmB,CAAA,GACjC,oBAAC,mBAAD,EAAA,UAAoB,YAA+B,CAAA,CACvC;GACd,CAAA,GAAA,oBAACD,WAAD;IAAS,WAAU;IAChB;GACM,CAAA,CACI;;CACT,CAAA;AAEZ;AAEA,SAAS,aAAa,EACpB,WACA,GAAG,SACmD;CACtD,OAGE,qBAAC,OAAD;EAAK,aAAU;EAAwB,WAAU;EAAjD,UAAA,CACE,oBAAC,QAAD;GAAQ,WAAU;GAA6B,eAAA;EAAa,CAAA,GAC5D,oBAACC,QAAiB,OAAlB;GACE,aAAU;GACV,WAAW,GACT,oLACA,SACF;GACA,GAAI;EACL,CAAA,CACE;;AAET;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,QAAiB,MAAlB;EACE,aAAU;EACV,WAAW,GAAG,8DAA8D,SAAS;EACrF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,OAA4D;CAChF,OACE,oBAACA,QAAiB,OAAlB;EACE,aAAU;EACV,WAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,EACpB,WACA,GAAG,SACmD;CACtD,OACE,oBAACA,QAAiB,OAAlB;EACE,aAAU;EACV,WAAW,GACT,2NACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,GAAG,SACuD;CAC1D,OACE,oBAACA,QAAiB,WAAlB;EACE,aAAU;EACV,WAAW,GAAG,wBAAwB,SAAS;EAC/C,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,QAAiB,MAAlB;EACE,aAAU;EACV,WAAW,GACT,yYACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,SAAuC;CAC9E,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,yHACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -97,7 +97,7 @@ function Composer({ onSubmit, onSubmitError, submitErrorText = "Message could no
|
|
|
97
97
|
setDragging(false);
|
|
98
98
|
attach(e.dataTransfer?.files);
|
|
99
99
|
},
|
|
100
|
-
className: cn("relative grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] focus-
|
|
100
|
+
className: cn("relative grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:inset-ring-1 has-[textarea:focus-visible]:inset-ring-ring dark:bg-surface-raised", "data-[dragging]:border-brand data-[dragging]:inset-ring-1 data-[dragging]:inset-ring-brand", disabled && "opacity-60", className),
|
|
101
101
|
...props,
|
|
102
102
|
children: [
|
|
103
103
|
dragging && /* @__PURE__ */ jsx("div", {
|
|
@@ -128,7 +128,7 @@ function Composer({ onSubmit, onSubmitError, submitErrorText = "Message could no
|
|
|
128
128
|
send();
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
|
-
className: "field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6
|
|
131
|
+
className: "field-sizing-content max-h-48 min-h-9 w-full resize-none rounded-none border-0 bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6 shadow-none outline-hidden! placeholder:text-muted-foreground disabled:cursor-not-allowed"
|
|
132
132
|
}),
|
|
133
133
|
failed && /* @__PURE__ */ jsx("p", {
|
|
134
134
|
id: errorId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.js","names":["_Fragment"],"sources":["../../src/components/composer.tsx"],"sourcesContent":["import ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport Paperclip from '@burtson-labs/icons/react/paperclip';\nimport Square from '@burtson-labs/icons/react/square';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\nexport interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSubmit' | 'onChange'> {\n /**\n * Clears after success. Reject a returned promise to retain the draft. The\n * text can be empty when `attachmentCount` allows an attachment-only send.\n */\n onSubmit: (text: string) => void | Promise<void>;\n onSubmitError?: (error: unknown) => void;\n submitErrorText?: string;\n /** Controlled value; leave out to let the composer keep its own. */\n value?: string;\n onValueChange?: (value: string) => void;\n /** While a reply streams, the send button becomes Stop. */\n streaming?: boolean;\n onStop?: () => void;\n disabled?: boolean;\n placeholder?: string;\n /** Attachment chips or a file button, shown under the text. */\n attachments?: React.ReactNode;\n /** Attachments ready to send. Above zero, an empty message can be sent. */\n attachmentCount?: number;\n /**\n * Extra gate on sending, e.g. false while an attachment is still parsing.\n * Unlike `disabled`, the person can keep typing.\n */\n canSubmit?: boolean;\n /** Extra controls left of the send button (model picker, tools). */\n actions?: React.ReactNode;\n label?: string;\n /**\n * Files the person picked, dropped on the composer or pasted. Setting it\n * shows a paperclip button. The composer only hands the files over: the\n * app uploads them and renders their state in `attachments` (an\n * AttachmentTray), with `attachmentCount` and `canSubmit` for sending.\n */\n onAttach?: (files: File[]) => void;\n /** File input `accept`, e.g. \"image/*,.pdf\". Dropped and pasted files are filtered too. */\n accept?: string;\n /** Allow several files at once. Default true. */\n multiple?: boolean;\n /** Accessible name of the attach button. */\n attachLabel?: string;\n}\n\n/** Does `file` match an `accept` list (\"image/*,.pdf,text/plain\")? Empty accepts all. */\nexport function acceptsFile(file: Pick<File, 'name' | 'type'>, accept?: string): boolean {\n if (!accept?.trim()) return true;\n const name = file.name.toLowerCase();\n const type = (file.type || '').toLowerCase();\n return accept\n .split(',')\n .map((a) => a.trim().toLowerCase())\n .filter(Boolean)\n .some((rule) =>\n rule.startsWith('.')\n ? name.endsWith(rule)\n : rule.endsWith('/*')\n ? type.startsWith(rule.slice(0, -1))\n : type === rule,\n );\n}\n\n/**\n * The message box: grows with its text, Enter sends, Shift+Enter adds a\n * line, and it never sends mid-composition (IME input).\n */\nfunction Composer({\n onSubmit,\n onSubmitError,\n submitErrorText = 'Message could not be sent. Your draft is saved here. Try again.',\n value: controlled,\n onValueChange,\n streaming = false,\n onStop,\n disabled = false,\n placeholder = 'Ask anything…',\n attachments,\n attachmentCount = 0,\n canSubmit = true,\n actions,\n label = 'Message',\n onAttach,\n accept,\n multiple = true,\n attachLabel = 'Attach files',\n className,\n ...props\n}: ComposerProps) {\n const fileInput = React.useRef<HTMLInputElement>(null);\n const [dragging, setDragging] = React.useState(false);\n const dragDepth = React.useRef(0);\n const attach = (list: FileList | File[] | null | undefined) => {\n if (!onAttach || !list) return;\n let files = Array.from(list).filter((f) => acceptsFile(f, accept));\n if (!multiple) files = files.slice(0, 1);\n if (files.length) onAttach(files);\n };\n const hasFiles = (e: React.DragEvent) =>\n Array.from(e.dataTransfer?.types ?? []).includes('Files');\n const [own, setOwn] = React.useState('');\n const value = controlled ?? own;\n const [pending, setPending] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const sending = React.useRef(false);\n const latest = React.useRef(value);\n const mounted = React.useRef(true);\n const errorId = React.useId();\n React.useEffect(() => {\n latest.current = value;\n }, [value]);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const setValue = (v: string) => {\n if (controlled === undefined) setOwn(v);\n onValueChange?.(v);\n };\n const canSend =\n canSubmit &&\n !disabled &&\n !streaming &&\n !pending &&\n (value.trim().length > 0 || attachmentCount > 0);\n const send = async () => {\n if (!canSend || sending.current) return;\n const draft = value;\n sending.current = true;\n setFailed(false);\n setPending(true);\n try {\n await onSubmit(draft.trim());\n if (mounted.current && latest.current === draft) setValue('');\n } catch (error) {\n if (mounted.current) setFailed(true);\n onSubmitError?.(error);\n } finally {\n sending.current = false;\n if (mounted.current) setPending(false);\n }\n };\n\n return (\n <form\n data-slot=\"composer\"\n data-dragging={dragging || undefined}\n onSubmit={(e) => {\n e.preventDefault();\n void send();\n }}\n onDragEnter={(e) => {\n if (!onAttach || disabled || !hasFiles(e)) return;\n e.preventDefault();\n dragDepth.current += 1;\n setDragging(true);\n }}\n onDragOver={(e) => {\n if (!onAttach || disabled || !hasFiles(e)) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }}\n onDragLeave={() => {\n if (!onAttach) return;\n dragDepth.current = Math.max(0, dragDepth.current - 1);\n if (dragDepth.current === 0) setDragging(false);\n }}\n onDrop={(e) => {\n if (!onAttach || disabled) return;\n e.preventDefault();\n dragDepth.current = 0;\n setDragging(false);\n attach(e.dataTransfer?.files);\n }}\n className={cn(\n 'relative grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] focus-within:border-brand focus-within:ring-[3px] focus-within:ring-ring/15 dark:bg-surface-raised',\n 'data-[dragging]:border-brand data-[dragging]:ring-[3px] data-[dragging]:ring-ring/20',\n disabled && 'opacity-60',\n className,\n )}\n {...props}\n >\n {dragging && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-1 z-10 grid place-items-center rounded-md border border-dashed border-brand/50 bg-brand-soft/80 text-sm font-medium text-brand-soft-foreground\"\n >\n Drop files to attach\n </div>\n )}\n <textarea\n aria-label={label}\n rows={1}\n value={value}\n disabled={disabled || pending}\n aria-describedby={failed ? errorId : undefined}\n aria-invalid={failed || undefined}\n placeholder={placeholder}\n onChange={(e) => setValue(e.target.value)}\n onPaste={(e) => {\n if (!onAttach) return;\n const files = Array.from(e.clipboardData?.files ?? []);\n // Only take over the paste when it carries files; text pastes as text.\n if (files.length) {\n e.preventDefault();\n attach(files);\n }\n }}\n onKeyDown={(e) => {\n if (\n e.key === 'Enter' &&\n !e.shiftKey &&\n !e.nativeEvent.isComposing &&\n e.nativeEvent.keyCode !== 229\n ) {\n e.preventDefault();\n void send();\n }\n }}\n className=\"field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n />\n {failed && (\n <p id={errorId} role=\"alert\" className=\"px-2 text-sm text-destructive\">\n {submitErrorText}\n </p>\n )}\n {attachments && <div className=\"flex flex-wrap gap-1.5 px-1\">{attachments}</div>}\n <div className=\"flex items-center gap-1\">\n {onAttach && (\n <>\n <input\n ref={fileInput}\n type=\"file\"\n hidden\n tabIndex={-1}\n accept={accept}\n multiple={multiple}\n data-slot=\"composer-file-input\"\n onChange={(e) => {\n attach(e.target.files);\n // Let the same file be picked again after a removal.\n e.target.value = '';\n }}\n />\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={attachLabel}\n title={attachLabel}\n disabled={disabled}\n onClick={() => fileInput.current?.click()}\n className=\"pointer-coarse:size-11\"\n >\n <Paperclip />\n </Button>\n </>\n )}\n {actions}\n <div className=\"ml-auto\">\n {streaming ? (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"secondary\"\n aria-label=\"Stop\"\n className=\"pointer-coarse:size-11\"\n onClick={onStop}\n disabled={!onStop || disabled}\n >\n <Square className=\"fill-current\" />\n </Button>\n ) : (\n <Button\n type=\"submit\"\n size=\"icon-sm\"\n aria-label=\"Send\"\n className=\"pointer-coarse:size-11\"\n loading={pending}\n disabled={!canSend}\n >\n <ArrowUp />\n </Button>\n )}\n </div>\n </div>\n </form>\n );\n}\n\nexport interface SuggestionsProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: string[];\n onSelect: (item: string) => void;\n}\n\n/** Starter prompts as chips. */\nfunction Suggestions({ items, onSelect, className, ...props }: SuggestionsProps) {\n return (\n <div\n data-slot=\"suggestions\"\n role=\"group\"\n aria-label=\"Suggestions\"\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {items.map((item) => (\n <button\n key={item}\n type=\"button\"\n onClick={() => onSelect(item)}\n className=\"animate-in rounded-full border border-border-strong bg-surface px-3 py-1.5 text-[13px] text-foreground outline-none transition-colors hover:border-brand/40 hover:bg-brand-soft hover:text-brand-soft-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n {item}\n </button>\n ))}\n </div>\n );\n}\n\nexport { Composer, Suggestions };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"composer.js","names":["_Fragment"],"sources":["../../src/components/composer.tsx"],"sourcesContent":["import ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport Paperclip from '@burtson-labs/icons/react/paperclip';\nimport Square from '@burtson-labs/icons/react/square';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\nexport interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSubmit' | 'onChange'> {\n /**\n * Clears after success. Reject a returned promise to retain the draft. The\n * text can be empty when `attachmentCount` allows an attachment-only send.\n */\n onSubmit: (text: string) => void | Promise<void>;\n onSubmitError?: (error: unknown) => void;\n submitErrorText?: string;\n /** Controlled value; leave out to let the composer keep its own. */\n value?: string;\n onValueChange?: (value: string) => void;\n /** While a reply streams, the send button becomes Stop. */\n streaming?: boolean;\n onStop?: () => void;\n disabled?: boolean;\n placeholder?: string;\n /** Attachment chips or a file button, shown under the text. */\n attachments?: React.ReactNode;\n /** Attachments ready to send. Above zero, an empty message can be sent. */\n attachmentCount?: number;\n /**\n * Extra gate on sending, e.g. false while an attachment is still parsing.\n * Unlike `disabled`, the person can keep typing.\n */\n canSubmit?: boolean;\n /** Extra controls left of the send button (model picker, tools). */\n actions?: React.ReactNode;\n label?: string;\n /**\n * Files the person picked, dropped on the composer or pasted. Setting it\n * shows a paperclip button. The composer only hands the files over: the\n * app uploads them and renders their state in `attachments` (an\n * AttachmentTray), with `attachmentCount` and `canSubmit` for sending.\n */\n onAttach?: (files: File[]) => void;\n /** File input `accept`, e.g. \"image/*,.pdf\". Dropped and pasted files are filtered too. */\n accept?: string;\n /** Allow several files at once. Default true. */\n multiple?: boolean;\n /** Accessible name of the attach button. */\n attachLabel?: string;\n}\n\n/** Does `file` match an `accept` list (\"image/*,.pdf,text/plain\")? Empty accepts all. */\nexport function acceptsFile(file: Pick<File, 'name' | 'type'>, accept?: string): boolean {\n if (!accept?.trim()) return true;\n const name = file.name.toLowerCase();\n const type = (file.type || '').toLowerCase();\n return accept\n .split(',')\n .map((a) => a.trim().toLowerCase())\n .filter(Boolean)\n .some((rule) =>\n rule.startsWith('.')\n ? name.endsWith(rule)\n : rule.endsWith('/*')\n ? type.startsWith(rule.slice(0, -1))\n : type === rule,\n );\n}\n\n/**\n * The message box: grows with its text, Enter sends, Shift+Enter adds a\n * line, and it never sends mid-composition (IME input).\n */\nfunction Composer({\n onSubmit,\n onSubmitError,\n submitErrorText = 'Message could not be sent. Your draft is saved here. Try again.',\n value: controlled,\n onValueChange,\n streaming = false,\n onStop,\n disabled = false,\n placeholder = 'Ask anything…',\n attachments,\n attachmentCount = 0,\n canSubmit = true,\n actions,\n label = 'Message',\n onAttach,\n accept,\n multiple = true,\n attachLabel = 'Attach files',\n className,\n ...props\n}: ComposerProps) {\n const fileInput = React.useRef<HTMLInputElement>(null);\n const [dragging, setDragging] = React.useState(false);\n const dragDepth = React.useRef(0);\n const attach = (list: FileList | File[] | null | undefined) => {\n if (!onAttach || !list) return;\n let files = Array.from(list).filter((f) => acceptsFile(f, accept));\n if (!multiple) files = files.slice(0, 1);\n if (files.length) onAttach(files);\n };\n const hasFiles = (e: React.DragEvent) =>\n Array.from(e.dataTransfer?.types ?? []).includes('Files');\n const [own, setOwn] = React.useState('');\n const value = controlled ?? own;\n const [pending, setPending] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const sending = React.useRef(false);\n const latest = React.useRef(value);\n const mounted = React.useRef(true);\n const errorId = React.useId();\n React.useEffect(() => {\n latest.current = value;\n }, [value]);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const setValue = (v: string) => {\n if (controlled === undefined) setOwn(v);\n onValueChange?.(v);\n };\n const canSend =\n canSubmit &&\n !disabled &&\n !streaming &&\n !pending &&\n (value.trim().length > 0 || attachmentCount > 0);\n const send = async () => {\n if (!canSend || sending.current) return;\n const draft = value;\n sending.current = true;\n setFailed(false);\n setPending(true);\n try {\n await onSubmit(draft.trim());\n if (mounted.current && latest.current === draft) setValue('');\n } catch (error) {\n if (mounted.current) setFailed(true);\n onSubmitError?.(error);\n } finally {\n sending.current = false;\n if (mounted.current) setPending(false);\n }\n };\n\n return (\n <form\n data-slot=\"composer\"\n data-dragging={dragging || undefined}\n onSubmit={(e) => {\n e.preventDefault();\n void send();\n }}\n onDragEnter={(e) => {\n if (!onAttach || disabled || !hasFiles(e)) return;\n e.preventDefault();\n dragDepth.current += 1;\n setDragging(true);\n }}\n onDragOver={(e) => {\n if (!onAttach || disabled || !hasFiles(e)) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }}\n onDragLeave={() => {\n if (!onAttach) return;\n dragDepth.current = Math.max(0, dragDepth.current - 1);\n if (dragDepth.current === 0) setDragging(false);\n }}\n onDrop={(e) => {\n if (!onAttach || disabled) return;\n e.preventDefault();\n dragDepth.current = 0;\n setDragging(false);\n attach(e.dataTransfer?.files);\n }}\n className={cn(\n // The box is the field: same focus as Input (ring border + 1px inset ring).\n 'relative grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:inset-ring-1 has-[textarea:focus-visible]:inset-ring-ring dark:bg-surface-raised',\n 'data-[dragging]:border-brand data-[dragging]:inset-ring-1 data-[dragging]:inset-ring-brand',\n disabled && 'opacity-60',\n className,\n )}\n {...props}\n >\n {dragging && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-1 z-10 grid place-items-center rounded-md border border-dashed border-brand/50 bg-brand-soft/80 text-sm font-medium text-brand-soft-foreground\"\n >\n Drop files to attach\n </div>\n )}\n <textarea\n aria-label={label}\n rows={1}\n value={value}\n disabled={disabled || pending}\n aria-describedby={failed ? errorId : undefined}\n aria-invalid={failed || undefined}\n placeholder={placeholder}\n onChange={(e) => setValue(e.target.value)}\n onPaste={(e) => {\n if (!onAttach) return;\n const files = Array.from(e.clipboardData?.files ?? []);\n // Only take over the paste when it carries files; text pastes as text.\n if (files.length) {\n e.preventDefault();\n attach(files);\n }\n }}\n onKeyDown={(e) => {\n if (\n e.key === 'Enter' &&\n !e.shiftKey &&\n !e.nativeEvent.isComposing &&\n e.nativeEvent.keyCode !== 229\n ) {\n e.preventDefault();\n void send();\n }\n }}\n className=\"field-sizing-content max-h-48 min-h-9 w-full resize-none rounded-none border-0 bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6 shadow-none outline-hidden! placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n />\n {failed && (\n <p id={errorId} role=\"alert\" className=\"px-2 text-sm text-destructive\">\n {submitErrorText}\n </p>\n )}\n {attachments && <div className=\"flex flex-wrap gap-1.5 px-1\">{attachments}</div>}\n <div className=\"flex items-center gap-1\">\n {onAttach && (\n <>\n <input\n ref={fileInput}\n type=\"file\"\n hidden\n tabIndex={-1}\n accept={accept}\n multiple={multiple}\n data-slot=\"composer-file-input\"\n onChange={(e) => {\n attach(e.target.files);\n // Let the same file be picked again after a removal.\n e.target.value = '';\n }}\n />\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={attachLabel}\n title={attachLabel}\n disabled={disabled}\n onClick={() => fileInput.current?.click()}\n className=\"pointer-coarse:size-11\"\n >\n <Paperclip />\n </Button>\n </>\n )}\n {actions}\n <div className=\"ml-auto\">\n {streaming ? (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"secondary\"\n aria-label=\"Stop\"\n className=\"pointer-coarse:size-11\"\n onClick={onStop}\n disabled={!onStop || disabled}\n >\n <Square className=\"fill-current\" />\n </Button>\n ) : (\n <Button\n type=\"submit\"\n size=\"icon-sm\"\n aria-label=\"Send\"\n className=\"pointer-coarse:size-11\"\n loading={pending}\n disabled={!canSend}\n >\n <ArrowUp />\n </Button>\n )}\n </div>\n </div>\n </form>\n );\n}\n\nexport interface SuggestionsProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: string[];\n onSelect: (item: string) => void;\n}\n\n/** Starter prompts as chips. */\nfunction Suggestions({ items, onSelect, className, ...props }: SuggestionsProps) {\n return (\n <div\n data-slot=\"suggestions\"\n role=\"group\"\n aria-label=\"Suggestions\"\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {items.map((item) => (\n <button\n key={item}\n type=\"button\"\n onClick={() => onSelect(item)}\n className=\"animate-in rounded-full border border-border-strong bg-surface px-3 py-1.5 text-[13px] text-foreground outline-none transition-colors hover:border-brand/40 hover:bg-brand-soft hover:text-brand-soft-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n {item}\n </button>\n ))}\n </div>\n );\n}\n\nexport { Composer, Suggestions };\n"],"mappings":"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"}
|
|
@@ -110,7 +110,7 @@ function ContextMenuSubTrigger({ className, inset, children, ...props }) {
|
|
|
110
110
|
return /* @__PURE__ */ jsxs(ContextMenu.SubTrigger, {
|
|
111
111
|
"data-slot": "context-menu-sub-trigger",
|
|
112
112
|
"data-inset": inset || void 0,
|
|
113
|
-
className: cn(menuItemClasses, "data-[state=open]:bg-
|
|
113
|
+
className: cn(menuItemClasses, "data-[state=open]:bg-secondary", className),
|
|
114
114
|
...props,
|
|
115
115
|
children: [children, /* @__PURE__ */ jsx(ChevronRight, {
|
|
116
116
|
className: "ml-auto size-4",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-menu.js","names":["ContextMenu","ContextMenuPrimitive","itemClasses"],"sources":["../../src/components/context-menu.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport { ContextMenu as ContextMenuPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { menuItemClasses as itemClasses, surfaceClasses } from './popover';\n\n/**\n * A right-click menu. Wrap the target in ContextMenuTrigger (use asChild to keep\n * your element); the menu opens at the pointer, and from the keyboard with the\n * context-menu key or Shift+F10 on the focused target. Same rows as\n * DropdownMenu, so a command can appear in both.\n */\nfunction ContextMenu(props: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {\n return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />;\n}\n\nfunction ContextMenuTrigger(props: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n return <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" {...props} />;\n}\n\nfunction ContextMenuGroup(props: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n return <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />;\n}\n\nfunction ContextMenuContent({\n className,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Content\n data-slot=\"context-menu-content\"\n className={cn(\n surfaceClasses,\n 'max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto p-1',\n className,\n )}\n {...props}\n />\n </ContextMenuPrimitive.Portal>\n );\n}\n\nfunction ContextMenuItem({\n className,\n inset,\n variant = 'default',\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) {\n return (\n <ContextMenuPrimitive.Item\n data-slot=\"context-menu-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n itemClasses,\n 'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ContextMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {\n return (\n <ContextMenuPrimitive.CheckboxItem\n data-slot=\"context-menu-checkbox-item\"\n className={cn(itemClasses, 'pl-8', className)}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <Check className=\"size-4 text-foreground\" aria-hidden />\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n}\n\nfunction ContextMenuRadioGroup(\n props: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>,\n) {\n return <ContextMenuPrimitive.RadioGroup data-slot=\"context-menu-radio-group\" {...props} />;\n}\n\nfunction ContextMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {\n return (\n <ContextMenuPrimitive.RadioItem\n data-slot=\"context-menu-radio-item\"\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <span className=\"block size-2 rounded-full bg-foreground\" />\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.RadioItem>\n );\n}\n\nfunction ContextMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & { inset?: boolean }) {\n return (\n <ContextMenuPrimitive.Label\n data-slot=\"context-menu-label\"\n data-inset={inset || undefined}\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ContextMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator\n data-slot=\"context-menu-separator\"\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction ContextMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"context-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction ContextMenuSub(props: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n return <ContextMenuPrimitive.Sub data-slot=\"context-menu-sub\" {...props} />;\n}\n\nfunction ContextMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <ContextMenuPrimitive.SubTrigger\n data-slot=\"context-menu-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(itemClasses, 'data-[state=open]:bg-
|
|
1
|
+
{"version":3,"file":"context-menu.js","names":["ContextMenu","ContextMenuPrimitive","itemClasses"],"sources":["../../src/components/context-menu.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport { ContextMenu as ContextMenuPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { menuItemClasses as itemClasses, surfaceClasses } from './popover';\n\n/**\n * A right-click menu. Wrap the target in ContextMenuTrigger (use asChild to keep\n * your element); the menu opens at the pointer, and from the keyboard with the\n * context-menu key or Shift+F10 on the focused target. Same rows as\n * DropdownMenu, so a command can appear in both.\n */\nfunction ContextMenu(props: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {\n return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />;\n}\n\nfunction ContextMenuTrigger(props: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n return <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" {...props} />;\n}\n\nfunction ContextMenuGroup(props: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n return <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />;\n}\n\nfunction ContextMenuContent({\n className,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Content\n data-slot=\"context-menu-content\"\n className={cn(\n surfaceClasses,\n 'max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto p-1',\n className,\n )}\n {...props}\n />\n </ContextMenuPrimitive.Portal>\n );\n}\n\nfunction ContextMenuItem({\n className,\n inset,\n variant = 'default',\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) {\n return (\n <ContextMenuPrimitive.Item\n data-slot=\"context-menu-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n itemClasses,\n 'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ContextMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {\n return (\n <ContextMenuPrimitive.CheckboxItem\n data-slot=\"context-menu-checkbox-item\"\n className={cn(itemClasses, 'pl-8', className)}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <Check className=\"size-4 text-foreground\" aria-hidden />\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n}\n\nfunction ContextMenuRadioGroup(\n props: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>,\n) {\n return <ContextMenuPrimitive.RadioGroup data-slot=\"context-menu-radio-group\" {...props} />;\n}\n\nfunction ContextMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {\n return (\n <ContextMenuPrimitive.RadioItem\n data-slot=\"context-menu-radio-item\"\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <span className=\"block size-2 rounded-full bg-foreground\" />\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.RadioItem>\n );\n}\n\nfunction ContextMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & { inset?: boolean }) {\n return (\n <ContextMenuPrimitive.Label\n data-slot=\"context-menu-label\"\n data-inset={inset || undefined}\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ContextMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator\n data-slot=\"context-menu-separator\"\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction ContextMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"context-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction ContextMenuSub(props: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n return <ContextMenuPrimitive.Sub data-slot=\"context-menu-sub\" {...props} />;\n}\n\nfunction ContextMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <ContextMenuPrimitive.SubTrigger\n data-slot=\"context-menu-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(itemClasses, 'data-[state=open]:bg-secondary', className)}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto size-4\" aria-hidden />\n </ContextMenuPrimitive.SubTrigger>\n );\n}\n\nfunction ContextMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.SubContent\n data-slot=\"context-menu-sub-content\"\n className={cn(\n surfaceClasses,\n 'min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden p-1',\n className,\n )}\n {...props}\n />\n </ContextMenuPrimitive.Portal>\n );\n}\n\nexport {\n ContextMenu,\n ContextMenuCheckboxItem,\n ContextMenuContent,\n ContextMenuGroup,\n ContextMenuItem,\n ContextMenuLabel,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuSeparator,\n ContextMenuShortcut,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,\n ContextMenuTrigger,\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAASA,cAAY,OAA+D;CAClF,OAAO,oBAACC,YAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,mBAAmB,OAAkE;CAC5F,OAAO,oBAACA,YAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;CAAQ,CAAA;AACpF;AAEA,SAAS,iBAAiB,OAAgE;CACxF,OAAO,oBAACA,YAAqB,OAAtB;EAA4B,aAAU;EAAqB,GAAI;CAAQ,CAAA;AAChF;AAEA,SAAS,mBAAmB,EAC1B,WACA,GAAG,SACyD;CAC5D,OACE,oBAACA,YAAqB,QAAtB,EAAA,UACE,oBAACA,YAAqB,SAAtB;EACE,aAAU;EACV,WAAW,GACT,gBACA,mKACA,SACF;EACA,GAAI;CACL,CAAA,EAC0B,CAAA;AAEjC;AAEA,SAAS,gBAAgB,EACvB,WACA,OACA,UAAU,WACV,GAAG,SAIF;CACD,OACE,oBAACA,YAAqB,MAAtB;EACE,aAAU;EACV,cAAY,SAAS,KAAA;EACrB,gBAAc;EACd,WAAW,GACTC,iBACA,uJACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,wBAAwB,EAC/B,WACA,UACA,SACA,GAAG,SAC8D;CACjE,OACE,qBAACD,YAAqB,cAAtB;EACE,aAAU;EACV,WAAW,GAAGC,iBAAa,QAAQ,SAAS;EACnC;EACT,GAAI;EAJN,UAAA,CAME,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACD,YAAqB,eAAtB,EAAA,UACE,oBAAC,OAAD;IAAO,WAAU;IAAyB,eAAA;GAAa,CAAA,EACrB,CAAA;EAChC,CAAA,GACL,QACgC;;AAEvC;AAEA,SAAS,sBACP,OACA;CACA,OAAO,oBAACA,YAAqB,YAAtB;EAAiC,aAAU;EAA2B,GAAI;CAAQ,CAAA;AAC3F;AAEA,SAAS,qBAAqB,EAC5B,WACA,UACA,GAAG,SAC2D;CAC9D,OACE,qBAACA,YAAqB,WAAtB;EACE,aAAU;EACV,WAAW,GAAGC,iBAAa,QAAQ,SAAS;EAC5C,GAAI;EAHN,UAAA,CAKE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACD,YAAqB,eAAtB,EAAA,UACE,oBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA,EACzB,CAAA;EAChC,CAAA,GACL,QAC6B;;AAEpC;AAEA,SAAS,iBAAiB,EACxB,WACA,OACA,GAAG,SAC6E;CAChF,OACE,oBAACA,YAAqB,OAAtB;EACE,aAAU;EACV,cAAY,SAAS,KAAA;EACrB,WAAW,GACT,gHACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,qBAAqB,EAC5B,WACA,GAAG,SAC2D;CAC9D,OACE,oBAACA,YAAqB,WAAtB;EACE,aAAU;EACV,WAAW,GAAG,6BAA6B,SAAS;EACpD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,oBAAoB,EAAE,WAAW,GAAG,SAAuC;CAClF,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GAAG,yDAAyD,SAAS;EAChF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,OAA8D;CACpF,OAAO,oBAACA,YAAqB,KAAtB;EAA0B,aAAU;EAAmB,GAAI;CAAQ,CAAA;AAC5E;AAEA,SAAS,sBAAsB,EAC7B,WACA,OACA,UACA,GAAG,SACkF;CACrF,OACE,qBAACA,YAAqB,YAAtB;EACE,aAAU;EACV,cAAY,SAAS,KAAA;EACrB,WAAW,GAAGC,iBAAa,kCAAkC,SAAS;EACtE,GAAI;EAJN,UAAA,CAMG,UACD,oBAAC,cAAD;GAAc,WAAU;GAAiB,eAAA;EAAa,CAAA,CACvB;;AAErC;AAEA,SAAS,sBAAsB,EAC7B,WACA,GAAG,SAC4D;CAC/D,OACE,oBAACD,YAAqB,QAAtB,EAAA,UACE,oBAACA,YAAqB,YAAtB;EACE,aAAU;EACV,WAAW,GACT,gBACA,2FACA,SACF;EACA,GAAI;CACL,CAAA,EAC0B,CAAA;AAEjC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
2
|
import { Button } from "./button.js";
|
|
3
|
-
import { Checkbox } from "./checkbox.js";
|
|
4
3
|
import { Input } from "./input.js";
|
|
4
|
+
import { Checkbox } from "./checkbox.js";
|
|
5
5
|
import { Pagination } from "./pagination.js";
|
|
6
6
|
import { Skeleton } from "./skeleton.js";
|
|
7
7
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
|
|
@@ -199,6 +199,7 @@ function DataTable({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLab
|
|
|
199
199
|
className: "rounded-lg border bg-surface",
|
|
200
200
|
children: /* @__PURE__ */ jsxs(Table, {
|
|
201
201
|
"aria-label": ariaLabel,
|
|
202
|
+
scrollLabel: ariaLabel,
|
|
202
203
|
"aria-busy": loading || void 0,
|
|
203
204
|
density,
|
|
204
205
|
stickyHeader,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.js","names":[],"sources":["../../src/components/data-table.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport Search from '@burtson-labs/icons/react/search';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Checkbox } from './checkbox';\nimport { Input } from './input';\nimport { Pagination } from './pagination';\nimport { Skeleton } from './skeleton';\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';\n\nexport interface DataTableColumn<T> {\n id: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n sortable?: boolean;\n /** Right-aligned tabular figures. */\n numeric?: boolean;\n className?: string;\n}\n\nexport type SortDirection = 'asc' | 'desc';\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport interface DataTableProps<T> extends Omit<React.ComponentProps<'div'>, 'children'> {\n /** Required: what the table lists, e.g. \"Workspace members\". */\n 'aria-label': string;\n columns: DataTableColumn<T>[];\n /** The rows to show now: already sorted, filtered and paged by the app or server. */\n rows: T[];\n getRowId: (row: T) => string;\n /** Names a row for screen readers (\"Select Ada Lovelace\"). Defaults to its id. */\n getRowLabel?: (row: T) => string;\n\n /** Controlled sort. A sortable header cycles ascending, descending, none. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Controlled filter text; a search box appears when onFilterChange is set. */\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n\n /** Controlled selection by row id; checkboxes appear when onSelectedChange is set. */\n selected?: string[];\n onSelectedChange?: (ids: string[]) => void;\n\n /** Enter or a click on the row, e.g. open the record. */\n onRowAction?: (row: T) => void;\n /** Per-row controls in a trailing column, e.g. a DropdownMenu. */\n rowActions?: (row: T) => React.ReactNode;\n\n /** 1-based; Pagination appears when onPageChange is set. */\n page?: number;\n pageCount?: number;\n onPageChange?: (page: number) => void;\n /** e.g. \"1–25 of 312\". */\n pageSummary?: React.ReactNode;\n\n loading?: boolean;\n /** A message shown in place of the rows. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no rows and nothing failed. */\n empty?: React.ReactNode;\n /** Extra controls beside the search box, e.g. filters or a bulk action. */\n toolbar?: React.ReactNode;\n density?: 'compact' | 'default';\n stickyHeader?: boolean;\n}\n\nconst nextSort = (current: DataTableSort | null | undefined, id: string): DataTableSort | null => {\n if (!current || current.columnId !== id) return { columnId: id, direction: 'asc' };\n return current.direction === 'asc' ? { columnId: id, direction: 'desc' } : null;\n};\n\n// Clicks and keys aimed at these belong to them, not to the row.\nconst INTERACTIVE = 'a,button,input,select,textarea,[role=\"checkbox\"],[role=\"menuitem\"]';\n\n/**\n * A table recipe for records: sort, search, select, page, and row actions,\n * with loading, empty and error states. Fully controlled, so the data can\n * come from a server; the table never reorders rows itself.\n */\nfunction DataTable<T>({\n 'aria-label': ariaLabel,\n columns,\n rows,\n getRowId,\n getRowLabel,\n sort,\n onSortChange,\n filter,\n onFilterChange,\n filterPlaceholder = 'Search',\n selected,\n onSelectedChange,\n onRowAction,\n rowActions,\n page,\n pageCount,\n onPageChange,\n pageSummary,\n loading = false,\n error,\n onRetry,\n empty = 'No results.',\n toolbar,\n density = 'default',\n stickyHeader,\n className,\n ...props\n}: DataTableProps<T>) {\n const selectable = Boolean(onSelectedChange);\n const selectedSet = React.useMemo(() => new Set(selected ?? []), [selected]);\n const ids = rows.map(getRowId);\n const pageSelected = ids.filter((id) => selectedSet.has(id)).length;\n const allState: boolean | 'indeterminate' =\n ids.length > 0 && pageSelected === ids.length\n ? true\n : pageSelected > 0\n ? 'indeterminate'\n : false;\n const colCount = columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);\n const interactive = Boolean(onRowAction) || selectable;\n\n // One tab stop in the body: the active row. Arrows move it.\n const [active, setActive] = React.useState(0);\n const frameRef = React.useRef<HTMLDivElement>(null);\n // Rows can shrink under the active index (a new page, a filter).\n const activeRow = Math.min(active, Math.max(0, rows.length - 1));\n\n const toggle = (id: string) => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n onSelectedChange([...next]);\n };\n const toggleAll = () => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (allState === true) ids.forEach((id) => next.delete(id));\n else ids.forEach((id) => next.add(id));\n onSelectedChange([...next]);\n };\n const focusRow = (i: number) => {\n const clamped = Math.min(Math.max(i, 0), rows.length - 1);\n setActive(clamped);\n frameRef.current\n ?.querySelectorAll<HTMLElement>('[data-slot=\"table-body\"] > [data-slot=\"table-row\"]')\n [clamped]?.focus();\n };\n\n const onRowKeyDown = (event: React.KeyboardEvent<HTMLTableRowElement>, i: number, row: T) => {\n if (event.target !== event.currentTarget) return;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n focusRow(i + 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n focusRow(i - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusRow(0);\n break;\n case 'End':\n event.preventDefault();\n focusRow(rows.length - 1);\n break;\n case 'Enter':\n if (onRowAction) {\n event.preventDefault();\n onRowAction(row);\n }\n break;\n case ' ':\n if (selectable) {\n event.preventDefault();\n toggle(getRowId(row));\n }\n break;\n }\n };\n\n const showToolbar = Boolean(onFilterChange || toolbar || (selectable && selectedSet.size));\n let body: React.ReactNode;\n if (error) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"h-auto py-8 text-center whitespace-normal\">\n <div role=\"alert\" className=\"grid justify-items-center gap-3 text-sm\">\n <span className=\"text-destructive\">{error}</span>\n {onRetry && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n );\n } else if (loading && rows.length === 0) {\n body = Array.from({ length: 3 }, (_, r) => (\n <TableRow key={`loading-${r}`} className=\"hover:bg-transparent\" aria-hidden>\n {Array.from({ length: colCount }, (_, c) => (\n <TableCell key={c}>\n <Skeleton className=\"h-3.5 w-full max-w-40\" />\n </TableCell>\n ))}\n </TableRow>\n ));\n } else if (rows.length === 0) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount}\n className=\"h-auto py-10 text-center whitespace-normal text-muted-foreground\"\n >\n {empty}\n </TableCell>\n </TableRow>\n );\n } else {\n body = rows.map((row, i) => {\n const id = getRowId(row);\n const isSelected = selectedSet.has(id);\n const label = getRowLabel?.(row) ?? id;\n return (\n <TableRow\n key={id}\n selected={isSelected}\n tabIndex={interactive ? (i === activeRow ? 0 : -1) : undefined}\n onFocus={() => setActive(i)}\n onKeyDown={interactive ? (e) => onRowKeyDown(e, i, row) : undefined}\n onClick={\n onRowAction\n ? (e) => {\n if ((e.target as HTMLElement).closest(INTERACTIVE)) return;\n onRowAction(row);\n }\n : undefined\n }\n className={cn(\n interactive &&\n 'outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]',\n onRowAction && 'cursor-pointer',\n )}\n >\n {selectable && (\n <TableCell className=\"w-10\">\n <Checkbox\n checked={isSelected}\n onCheckedChange={() => toggle(id)}\n aria-label={`Select ${label}`}\n tabIndex={-1}\n />\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={col.id} numeric={col.numeric} className={col.className}>\n {col.cell(row)}\n </TableCell>\n ))}\n {rowActions && <TableCell className=\"w-12 text-right\">{rowActions(row)}</TableCell>}\n </TableRow>\n );\n });\n }\n\n return (\n <div data-slot=\"data-table\" className={cn('grid min-w-0 gap-3', className)} {...props}>\n {showToolbar && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {onFilterChange && (\n <div className=\"relative w-full max-w-xs min-w-0 flex-1\">\n <Search\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n type=\"search\"\n value={filter ?? ''}\n onChange={(e) => onFilterChange(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={`Search ${ariaLabel.toLowerCase()}`}\n className=\"pl-8\"\n />\n </div>\n )}\n {selectable && (\n <span aria-live=\"polite\" className=\"text-xs text-muted-foreground tabular-nums\">\n {selectedSet.size ? `${selectedSet.size} selected` : ''}\n </span>\n )}\n {toolbar && <div className=\"ml-auto flex flex-wrap items-center gap-2\">{toolbar}</div>}\n </div>\n )}\n <div ref={frameRef} className=\"rounded-lg border bg-surface\">\n <Table\n aria-label={ariaLabel}\n aria-busy={loading || undefined}\n density={density}\n stickyHeader={stickyHeader}\n className={cn(loading && rows.length > 0 && 'opacity-60 transition-opacity')}\n >\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n {selectable && (\n <TableHead className=\"w-10\">\n <Checkbox\n checked={allState}\n onCheckedChange={toggleAll}\n disabled={rows.length === 0}\n aria-label=\"Select all rows on this page\"\n />\n </TableHead>\n )}\n {columns.map((col) => {\n const sorted = sort?.columnId === col.id ? sort.direction : undefined;\n return (\n <TableHead\n key={col.id}\n numeric={col.numeric}\n className={col.className}\n aria-sort={\n col.sortable\n ? sorted === 'asc'\n ? 'ascending'\n : sorted === 'desc'\n ? 'descending'\n : 'none'\n : undefined\n }\n >\n {col.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, col.id))}\n className={cn(\n '-mx-1.5 inline-flex h-7 items-center gap-1 rounded-sm px-1.5 uppercase outline-none hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20',\n sorted && 'text-foreground',\n col.numeric && 'flex-row-reverse',\n )}\n >\n {col.header}\n {sorted === 'asc' ? (\n <ArrowUp className=\"size-3.5\" aria-hidden />\n ) : sorted === 'desc' ? (\n <ArrowDown className=\"size-3.5\" aria-hidden />\n ) : (\n <ChevronsUpDown className=\"size-3.5 opacity-50\" aria-hidden />\n )}\n </button>\n ) : (\n col.header\n )}\n </TableHead>\n );\n })}\n {rowActions && (\n <TableHead className=\"w-12\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n )}\n </TableRow>\n </TableHeader>\n <TableBody>{body}</TableBody>\n </Table>\n </div>\n {onPageChange && page !== undefined && pageCount !== undefined && pageCount > 0 && (\n <Pagination\n page={page}\n pageCount={pageCount}\n onPageChange={onPageChange}\n summary={pageSummary}\n />\n )}\n </div>\n );\n}\n\nexport { DataTable };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-table.js","names":[],"sources":["../../src/components/data-table.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport Search from '@burtson-labs/icons/react/search';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Checkbox } from './checkbox';\nimport { Input } from './input';\nimport { Pagination } from './pagination';\nimport { Skeleton } from './skeleton';\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';\n\nexport interface DataTableColumn<T> {\n id: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n sortable?: boolean;\n /** Right-aligned tabular figures. */\n numeric?: boolean;\n className?: string;\n}\n\nexport type SortDirection = 'asc' | 'desc';\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport interface DataTableProps<T> extends Omit<React.ComponentProps<'div'>, 'children'> {\n /** Required: what the table lists, e.g. \"Workspace members\". */\n 'aria-label': string;\n columns: DataTableColumn<T>[];\n /** The rows to show now: already sorted, filtered and paged by the app or server. */\n rows: T[];\n getRowId: (row: T) => string;\n /** Names a row for screen readers (\"Select Ada Lovelace\"). Defaults to its id. */\n getRowLabel?: (row: T) => string;\n\n /** Controlled sort. A sortable header cycles ascending, descending, none. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Controlled filter text; a search box appears when onFilterChange is set. */\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n\n /** Controlled selection by row id; checkboxes appear when onSelectedChange is set. */\n selected?: string[];\n onSelectedChange?: (ids: string[]) => void;\n\n /** Enter or a click on the row, e.g. open the record. */\n onRowAction?: (row: T) => void;\n /** Per-row controls in a trailing column, e.g. a DropdownMenu. */\n rowActions?: (row: T) => React.ReactNode;\n\n /** 1-based; Pagination appears when onPageChange is set. */\n page?: number;\n pageCount?: number;\n onPageChange?: (page: number) => void;\n /** e.g. \"1–25 of 312\". */\n pageSummary?: React.ReactNode;\n\n loading?: boolean;\n /** A message shown in place of the rows. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no rows and nothing failed. */\n empty?: React.ReactNode;\n /** Extra controls beside the search box, e.g. filters or a bulk action. */\n toolbar?: React.ReactNode;\n density?: 'compact' | 'default';\n stickyHeader?: boolean;\n}\n\nconst nextSort = (current: DataTableSort | null | undefined, id: string): DataTableSort | null => {\n if (!current || current.columnId !== id) return { columnId: id, direction: 'asc' };\n return current.direction === 'asc' ? { columnId: id, direction: 'desc' } : null;\n};\n\n// Clicks and keys aimed at these belong to them, not to the row.\nconst INTERACTIVE = 'a,button,input,select,textarea,[role=\"checkbox\"],[role=\"menuitem\"]';\n\n/**\n * A table recipe for records: sort, search, select, page, and row actions,\n * with loading, empty and error states. Fully controlled, so the data can\n * come from a server; the table never reorders rows itself.\n */\nfunction DataTable<T>({\n 'aria-label': ariaLabel,\n columns,\n rows,\n getRowId,\n getRowLabel,\n sort,\n onSortChange,\n filter,\n onFilterChange,\n filterPlaceholder = 'Search',\n selected,\n onSelectedChange,\n onRowAction,\n rowActions,\n page,\n pageCount,\n onPageChange,\n pageSummary,\n loading = false,\n error,\n onRetry,\n empty = 'No results.',\n toolbar,\n density = 'default',\n stickyHeader,\n className,\n ...props\n}: DataTableProps<T>) {\n const selectable = Boolean(onSelectedChange);\n const selectedSet = React.useMemo(() => new Set(selected ?? []), [selected]);\n const ids = rows.map(getRowId);\n const pageSelected = ids.filter((id) => selectedSet.has(id)).length;\n const allState: boolean | 'indeterminate' =\n ids.length > 0 && pageSelected === ids.length\n ? true\n : pageSelected > 0\n ? 'indeterminate'\n : false;\n const colCount = columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);\n const interactive = Boolean(onRowAction) || selectable;\n\n // One tab stop in the body: the active row. Arrows move it.\n const [active, setActive] = React.useState(0);\n const frameRef = React.useRef<HTMLDivElement>(null);\n // Rows can shrink under the active index (a new page, a filter).\n const activeRow = Math.min(active, Math.max(0, rows.length - 1));\n\n const toggle = (id: string) => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n onSelectedChange([...next]);\n };\n const toggleAll = () => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (allState === true) ids.forEach((id) => next.delete(id));\n else ids.forEach((id) => next.add(id));\n onSelectedChange([...next]);\n };\n const focusRow = (i: number) => {\n const clamped = Math.min(Math.max(i, 0), rows.length - 1);\n setActive(clamped);\n frameRef.current\n ?.querySelectorAll<HTMLElement>('[data-slot=\"table-body\"] > [data-slot=\"table-row\"]')\n [clamped]?.focus();\n };\n\n const onRowKeyDown = (event: React.KeyboardEvent<HTMLTableRowElement>, i: number, row: T) => {\n if (event.target !== event.currentTarget) return;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n focusRow(i + 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n focusRow(i - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusRow(0);\n break;\n case 'End':\n event.preventDefault();\n focusRow(rows.length - 1);\n break;\n case 'Enter':\n if (onRowAction) {\n event.preventDefault();\n onRowAction(row);\n }\n break;\n case ' ':\n if (selectable) {\n event.preventDefault();\n toggle(getRowId(row));\n }\n break;\n }\n };\n\n const showToolbar = Boolean(onFilterChange || toolbar || (selectable && selectedSet.size));\n let body: React.ReactNode;\n if (error) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"h-auto py-8 text-center whitespace-normal\">\n <div role=\"alert\" className=\"grid justify-items-center gap-3 text-sm\">\n <span className=\"text-destructive\">{error}</span>\n {onRetry && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n );\n } else if (loading && rows.length === 0) {\n body = Array.from({ length: 3 }, (_, r) => (\n <TableRow key={`loading-${r}`} className=\"hover:bg-transparent\" aria-hidden>\n {Array.from({ length: colCount }, (_, c) => (\n <TableCell key={c}>\n <Skeleton className=\"h-3.5 w-full max-w-40\" />\n </TableCell>\n ))}\n </TableRow>\n ));\n } else if (rows.length === 0) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount}\n className=\"h-auto py-10 text-center whitespace-normal text-muted-foreground\"\n >\n {empty}\n </TableCell>\n </TableRow>\n );\n } else {\n body = rows.map((row, i) => {\n const id = getRowId(row);\n const isSelected = selectedSet.has(id);\n const label = getRowLabel?.(row) ?? id;\n return (\n <TableRow\n key={id}\n selected={isSelected}\n tabIndex={interactive ? (i === activeRow ? 0 : -1) : undefined}\n onFocus={() => setActive(i)}\n onKeyDown={interactive ? (e) => onRowKeyDown(e, i, row) : undefined}\n onClick={\n onRowAction\n ? (e) => {\n if ((e.target as HTMLElement).closest(INTERACTIVE)) return;\n onRowAction(row);\n }\n : undefined\n }\n className={cn(\n interactive &&\n 'outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]',\n onRowAction && 'cursor-pointer',\n )}\n >\n {selectable && (\n <TableCell className=\"w-10\">\n <Checkbox\n checked={isSelected}\n onCheckedChange={() => toggle(id)}\n aria-label={`Select ${label}`}\n tabIndex={-1}\n />\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={col.id} numeric={col.numeric} className={col.className}>\n {col.cell(row)}\n </TableCell>\n ))}\n {rowActions && <TableCell className=\"w-12 text-right\">{rowActions(row)}</TableCell>}\n </TableRow>\n );\n });\n }\n\n return (\n <div data-slot=\"data-table\" className={cn('grid min-w-0 gap-3', className)} {...props}>\n {showToolbar && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {onFilterChange && (\n <div className=\"relative w-full max-w-xs min-w-0 flex-1\">\n <Search\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n type=\"search\"\n value={filter ?? ''}\n onChange={(e) => onFilterChange(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={`Search ${ariaLabel.toLowerCase()}`}\n className=\"pl-8\"\n />\n </div>\n )}\n {selectable && (\n <span aria-live=\"polite\" className=\"text-xs text-muted-foreground tabular-nums\">\n {selectedSet.size ? `${selectedSet.size} selected` : ''}\n </span>\n )}\n {toolbar && <div className=\"ml-auto flex flex-wrap items-center gap-2\">{toolbar}</div>}\n </div>\n )}\n <div ref={frameRef} className=\"rounded-lg border bg-surface\">\n <Table\n aria-label={ariaLabel}\n scrollLabel={ariaLabel}\n aria-busy={loading || undefined}\n density={density}\n stickyHeader={stickyHeader}\n className={cn(loading && rows.length > 0 && 'opacity-60 transition-opacity')}\n >\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n {selectable && (\n <TableHead className=\"w-10\">\n <Checkbox\n checked={allState}\n onCheckedChange={toggleAll}\n disabled={rows.length === 0}\n aria-label=\"Select all rows on this page\"\n />\n </TableHead>\n )}\n {columns.map((col) => {\n const sorted = sort?.columnId === col.id ? sort.direction : undefined;\n return (\n <TableHead\n key={col.id}\n numeric={col.numeric}\n className={col.className}\n aria-sort={\n col.sortable\n ? sorted === 'asc'\n ? 'ascending'\n : sorted === 'desc'\n ? 'descending'\n : 'none'\n : undefined\n }\n >\n {col.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, col.id))}\n className={cn(\n '-mx-1.5 inline-flex h-7 items-center gap-1 rounded-sm px-1.5 uppercase outline-none hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20',\n sorted && 'text-foreground',\n col.numeric && 'flex-row-reverse',\n )}\n >\n {col.header}\n {sorted === 'asc' ? (\n <ArrowUp className=\"size-3.5\" aria-hidden />\n ) : sorted === 'desc' ? (\n <ArrowDown className=\"size-3.5\" aria-hidden />\n ) : (\n <ChevronsUpDown className=\"size-3.5 opacity-50\" aria-hidden />\n )}\n </button>\n ) : (\n col.header\n )}\n </TableHead>\n );\n })}\n {rowActions && (\n <TableHead className=\"w-12\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n )}\n </TableRow>\n </TableHeader>\n <TableBody>{body}</TableBody>\n </Table>\n </div>\n {onPageChange && page !== undefined && pageCount !== undefined && pageCount > 0 && (\n <Pagination\n page={page}\n pageCount={pageCount}\n onPageChange={onPageChange}\n summary={pageSummary}\n />\n )}\n </div>\n );\n}\n\nexport { DataTable };\n"],"mappings":"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"}
|
|
@@ -105,7 +105,7 @@ function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
|
105
105
|
return /* @__PURE__ */ jsxs(DropdownMenu.SubTrigger, {
|
|
106
106
|
"data-slot": "dropdown-menu-sub-trigger",
|
|
107
107
|
"data-inset": inset || void 0,
|
|
108
|
-
className: cn(menuItemClasses, "data-[state=open]:bg-
|
|
108
|
+
className: cn(menuItemClasses, "data-[state=open]:bg-secondary", className),
|
|
109
109
|
...props,
|
|
110
110
|
children: [children, /* @__PURE__ */ jsx(ChevronRight, {
|
|
111
111
|
className: "ml-auto size-4",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.js","names":["DropdownMenu","DropdownMenuPrimitive","itemClasses"],"sources":["../../src/components/dropdown-menu.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { menuItemClasses as itemClasses, surfaceClasses } from './popover';\n\nfunction DropdownMenu(props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuTrigger(props: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return <DropdownMenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto p-1',\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = 'default',\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n itemClasses,\n 'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(itemClasses, 'pl-8', className)}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Check className=\"size-4 text-foreground\" aria-hidden />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n );\n}\n\nfunction DropdownMenuRadioGroup(\n props: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>,\n) {\n return <DropdownMenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />;\n}\n\nfunction DropdownMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <span className=\"block size-2 rounded-full bg-foreground\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n );\n}\n\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot=\"dropdown-menu-label\"\n data-inset={inset || undefined}\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub(props: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(itemClasses, 'data-[state=open]:bg-
|
|
1
|
+
{"version":3,"file":"dropdown-menu.js","names":["DropdownMenu","DropdownMenuPrimitive","itemClasses"],"sources":["../../src/components/dropdown-menu.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { menuItemClasses as itemClasses, surfaceClasses } from './popover';\n\nfunction DropdownMenu(props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuTrigger(props: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return <DropdownMenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto p-1',\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = 'default',\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n itemClasses,\n 'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(itemClasses, 'pl-8', className)}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Check className=\"size-4 text-foreground\" aria-hidden />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n );\n}\n\nfunction DropdownMenuRadioGroup(\n props: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>,\n) {\n return <DropdownMenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />;\n}\n\nfunction DropdownMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <span className=\"block size-2 rounded-full bg-foreground\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n );\n}\n\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot=\"dropdown-menu-label\"\n data-inset={inset || undefined}\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub(props: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(itemClasses, 'data-[state=open]:bg-secondary', className)}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto size-4\" aria-hidden />\n </DropdownMenuPrimitive.SubTrigger>\n );\n}\n\nfunction DropdownMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n surfaceClasses,\n 'min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden p-1',\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"],"mappings":"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"}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Keyboard focus for anything that is a text field, or a box that contains
|
|
4
|
+
* one (Composer, a bordered Command): the border turns the ring colour and a
|
|
5
|
+
* 1px inset ring doubles it to 2px. It sits inside the border box, so it
|
|
6
|
+
* follows the radius exactly and no overflow can clip it. The outline is
|
|
7
|
+
* suppressed with !important: an app's unlayered `:focus-visible { outline }`
|
|
8
|
+
* would otherwise beat the utilities layer and draw a second, offset box.
|
|
9
|
+
* outline-hidden keeps a transparent outline, which forced-colors mode shows.
|
|
10
|
+
*/
|
|
11
|
+
export declare const fieldFocusClasses = "outline-hidden! focus-visible:border-ring focus-visible:inset-ring-1 focus-visible:inset-ring-ring aria-invalid:focus-visible:inset-ring-destructive";
|
|
12
|
+
/**
|
|
13
|
+
* Fields and field-like triggers: 10px radius (radius-md). Popovers are 12px
|
|
14
|
+
* (radius-lg) and their rows 8px (radius-sm: 12px less the 4px inset).
|
|
15
|
+
*/
|
|
16
|
+
export declare const fieldClasses = "w-full min-w-0 rounded-md border border-input bg-surface px-3 text-base sm:text-sm text-foreground shadow-xs transition-[border-color,box-shadow,background-color] placeholder:text-muted-foreground/80 hover:border-border-strong outline-hidden! focus-visible:border-ring focus-visible:inset-ring-1 focus-visible:inset-ring-ring aria-invalid:focus-visible:inset-ring-destructive disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive dark:bg-surface-raised";
|
|
3
17
|
declare const Input: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
4
18
|
export { Input };
|