@burtson-labs/ui 0.14.0 → 0.14.1
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/LICENSES/DEPENDENCIES.txt +1 -1
- package/README.md +15 -11
- package/dist/components/accordion.d.ts +3 -3
- package/dist/components/accordion.js +13 -10
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/alert-dialog.d.ts +12 -10
- package/dist/components/alert-dialog.js +29 -20
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/alert.d.ts +5 -3
- package/dist/components/alert.js +11 -7
- package/dist/components/alert.js.map +1 -1
- package/dist/components/app-shell.d.ts +7 -6
- package/dist/components/app-shell.js +21 -14
- package/dist/components/app-shell.js.map +1 -1
- package/dist/components/attachment.d.ts +3 -3
- package/dist/components/attachment.js +10 -7
- package/dist/components/attachment.js.map +1 -1
- package/dist/components/audio-player.d.ts +2 -2
- package/dist/components/audio-player.js +8 -6
- package/dist/components/audio-player.js.map +1 -1
- package/dist/components/avatar.d.ts +3 -3
- package/dist/components/avatar.js +10 -7
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/badge.js +12 -10
- package/dist/components/badge.js.map +1 -1
- package/dist/components/breadcrumb.d.ts +8 -8
- package/dist/components/breadcrumb.js +24 -17
- package/dist/components/breadcrumb.js.map +1 -1
- package/dist/components/button.d.ts +8 -1
- package/dist/components/button.js +12 -3
- package/dist/components/button.js.map +1 -1
- package/dist/components/card.d.ts +8 -8
- package/dist/components/card.js +32 -23
- package/dist/components/card.js.map +1 -1
- package/dist/components/chat-history.d.ts +1 -1
- package/dist/components/chat-history.js +10 -9
- package/dist/components/chat-history.js.map +1 -1
- package/dist/components/chat-layout.d.ts +1 -1
- package/dist/components/chat-layout.js +7 -3
- package/dist/components/chat-layout.js.map +1 -1
- package/dist/components/checkbox-card.d.ts +2 -2
- package/dist/components/checkbox-card.js +9 -7
- package/dist/components/checkbox-card.js.map +1 -1
- package/dist/components/checkbox.d.ts +23 -2
- package/dist/components/checkbox.js +48 -6
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/collapsible.d.ts +2 -2
- package/dist/components/collapsible.js +7 -5
- package/dist/components/collapsible.js.map +1 -1
- package/dist/components/combobox.d.ts +14 -4
- package/dist/components/combobox.js +18 -8
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/command.d.ts +68 -12
- package/dist/components/command.js +38 -25
- package/dist/components/command.js.map +1 -1
- package/dist/components/composer.d.ts +2 -2
- package/dist/components/composer.js +9 -7
- package/dist/components/composer.js.map +1 -1
- package/dist/components/connection-status.d.ts +3 -3
- package/dist/components/connection-status.js +18 -14
- package/dist/components/connection-status.js.map +1 -1
- package/dist/components/context-menu.d.ts +14 -13
- package/dist/components/context-menu.js +33 -23
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/conversation.d.ts +1 -1
- package/dist/components/conversation.js +6 -5
- package/dist/components/conversation.js.map +1 -1
- package/dist/components/copy-button.d.ts +1 -1
- package/dist/components/copy-button.js +3 -2
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table.d.ts +7 -2
- package/dist/components/data-table.js +16 -42
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dialog.d.ts +18 -9
- package/dist/components/dialog.js +38 -22
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/dropdown-menu.d.ts +14 -13
- package/dist/components/dropdown-menu.js +33 -23
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/editor-tabs.d.ts +1 -1
- package/dist/components/editor-tabs.js +6 -5
- package/dist/components/editor-tabs.js.map +1 -1
- package/dist/components/empty-state.d.ts +5 -5
- package/dist/components/empty-state.js +16 -11
- package/dist/components/empty-state.js.map +1 -1
- package/dist/components/error-summary.d.ts +21 -0
- package/dist/components/error-summary.js +62 -0
- package/dist/components/error-summary.js.map +1 -0
- package/dist/components/field.d.ts +66 -7
- package/dist/components/field.js +151 -20
- package/dist/components/field.js.map +1 -1
- package/dist/components/form-actions.d.ts +18 -0
- package/dist/components/form-actions.js +30 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/icon-button.d.ts +3 -1
- package/dist/components/icon-button.js +5 -3
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/input.d.ts +42 -7
- package/dist/components/input.js +53 -10
- package/dist/components/input.js.map +1 -1
- package/dist/components/kbd.d.ts +2 -2
- package/dist/components/kbd.js +7 -5
- package/dist/components/kbd.js.map +1 -1
- package/dist/components/label.d.ts +6 -1
- package/dist/components/label.js +10 -4
- package/dist/components/label.js.map +1 -1
- package/dist/components/markdown.d.ts +4 -3
- package/dist/components/markdown.js +7 -6
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/menubar.d.ts +16 -14
- package/dist/components/menubar.js +39 -27
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/message-actions.d.ts +3 -3
- package/dist/components/message-actions.js +15 -12
- package/dist/components/message-actions.js.map +1 -1
- package/dist/components/message.d.ts +1 -1
- package/dist/components/message.js +5 -3
- package/dist/components/message.js.map +1 -1
- package/dist/components/mobile-nav.d.ts +1 -1
- package/dist/components/mobile-nav.js +6 -5
- package/dist/components/mobile-nav.js.map +1 -1
- package/dist/components/native-select.d.ts +16 -0
- package/dist/components/native-select.js +33 -0
- package/dist/components/native-select.js.map +1 -0
- package/dist/components/navigation-menu.d.ts +12 -11
- package/dist/components/navigation-menu.js +32 -23
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/number-input.d.ts +23 -0
- package/dist/components/number-input.js +45 -0
- package/dist/components/number-input.js.map +1 -0
- package/dist/components/onboarding-checklist.d.ts +1 -1
- package/dist/components/onboarding-checklist.js +4 -3
- package/dist/components/onboarding-checklist.js.map +1 -1
- package/dist/components/page-header.d.ts +7 -6
- package/dist/components/page-header.js +22 -15
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/pagination.d.ts +1 -1
- package/dist/components/pagination.js +3 -2
- package/dist/components/pagination.js.map +1 -1
- package/dist/components/popover.d.ts +13 -10
- package/dist/components/popover.js +23 -17
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/progress.js +6 -5
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-group.d.ts +22 -3
- package/dist/components/radio-group.js +51 -10
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/reasoning.d.ts +3 -3
- package/dist/components/reasoning.js +9 -7
- package/dist/components/reasoning.js.map +1 -1
- package/dist/components/scroll-area.d.ts +3 -2
- package/dist/components/scroll-area.js +10 -7
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/secret-input.d.ts +3 -2
- package/dist/components/secret-input.js +6 -4
- package/dist/components/secret-input.js.map +1 -1
- package/dist/components/select.d.ts +11 -6
- package/dist/components/select.js +24 -18
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.d.ts +1 -1
- package/dist/components/separator.js +4 -3
- package/dist/components/separator.js.map +1 -1
- package/dist/components/sheet.d.ts +10 -8
- package/dist/components/sheet.js +31 -20
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/skeleton.d.ts +2 -1
- package/dist/components/skeleton.js +6 -4
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/slider.d.ts +10 -6
- package/dist/components/slider.js +15 -7
- package/dist/components/slider.js.map +1 -1
- package/dist/components/source.d.ts +2 -2
- package/dist/components/source.js +9 -7
- package/dist/components/source.js.map +1 -1
- package/dist/components/spinner.d.ts +4 -2
- package/dist/components/spinner.js +4 -3
- package/dist/components/spinner.js.map +1 -1
- package/dist/components/stat-card.d.ts +27 -2
- package/dist/components/stat-card.js +69 -9
- package/dist/components/stat-card.js.map +1 -1
- package/dist/components/status.d.ts +7 -6
- package/dist/components/status.js +11 -5
- package/dist/components/status.js.map +1 -1
- package/dist/components/steps.d.ts +1 -1
- package/dist/components/steps.js +4 -3
- package/dist/components/steps.js.map +1 -1
- package/dist/components/switch.d.ts +46 -2
- package/dist/components/switch.js +85 -8
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.d.ts +29 -12
- package/dist/components/table.js +79 -34
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.d.ts +9 -4
- package/dist/components/tabs.js +22 -13
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/textarea.d.ts +6 -1
- package/dist/components/textarea.js +3 -3
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/toast.d.ts +10 -6
- package/dist/components/toast.js +27 -18
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toaster.d.ts +4 -2
- package/dist/components/toaster.js +6 -2
- package/dist/components/toaster.js.map +1 -1
- package/dist/components/tool-call.d.ts +2 -2
- package/dist/components/tool-call.js +10 -8
- package/dist/components/tool-call.js.map +1 -1
- package/dist/components/toolbar.d.ts +9 -4
- package/dist/components/toolbar.js +14 -9
- package/dist/components/toolbar.js.map +1 -1
- package/dist/components/tooltip.d.ts +3 -2
- package/dist/components/tooltip.js +7 -4
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/tour.js +2 -2
- package/dist/components/tour.js.map +1 -1
- package/dist/components/tree-view.d.ts +1 -1
- package/dist/components/tree-view.js +9 -5
- package/dist/components/tree-view.js.map +1 -1
- package/dist/components/voice-recorder.d.ts +1 -1
- package/dist/components/voice-recorder.js +4 -2
- package/dist/components/voice-recorder.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +17 -12
- package/dist/lib/media.d.ts +18 -0
- package/dist/lib/media.js +34 -0
- package/dist/lib/media.js.map +1 -0
- package/dist/lib/utils.d.ts +32 -0
- package/dist/lib/utils.js +33 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/mui/index.js +20 -8
- package/dist/mui/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme.css +36 -17
- package/dist/tokens.d.ts +78 -36
- package/dist/tokens.js +63 -32
- package/dist/tokens.js.map +1 -1
- package/package.json +5 -3
|
@@ -5,10 +5,10 @@ import { Progress } from "./progress.js";
|
|
|
5
5
|
import { Spinner } from "./spinner.js";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import X from "@burtson-labs/icons/react/x";
|
|
8
9
|
import AlertCircle from "@burtson-labs/icons/react/alert-circle";
|
|
9
10
|
import FileText from "@burtson-labs/icons/react/file-text";
|
|
10
11
|
import RotateCw from "@burtson-labs/icons/react/rotate-cw";
|
|
11
|
-
import X from "@burtson-labs/icons/react/x";
|
|
12
12
|
//#region src/components/attachment.tsx
|
|
13
13
|
var stateText = {
|
|
14
14
|
queued: "Waiting",
|
|
@@ -40,12 +40,13 @@ var InTray = React.createContext(false);
|
|
|
40
40
|
* One attached file: name, size, state, progress, and remove/retry. State
|
|
41
41
|
* changes are announced politely; a failure says why.
|
|
42
42
|
*/
|
|
43
|
-
function AttachmentItem({ name, size, state = "ready", progress, error, preview, icon, onRemove, onRetry, layout = "chip", className, ...props }) {
|
|
43
|
+
var AttachmentItem = React.forwardRef(function AttachmentItem({ name, size, state = "ready", progress, error, preview, icon, onRemove, onRetry, layout = "chip", className, ...props }, ref) {
|
|
44
44
|
const inTray = React.useContext(InTray);
|
|
45
45
|
const busy = state === "uploading" || state === "parsing";
|
|
46
46
|
const failed = state === "failed";
|
|
47
47
|
const meta = [size !== void 0 ? formatBytes(size) : null, stateText[state]].filter(Boolean).join(" · ");
|
|
48
48
|
return /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
ref,
|
|
49
50
|
"data-slot": "attachment-item",
|
|
50
51
|
"data-state": state,
|
|
51
52
|
"data-layout": layout,
|
|
@@ -112,12 +113,13 @@ function AttachmentItem({ name, size, state = "ready", progress, error, preview,
|
|
|
112
113
|
})
|
|
113
114
|
]
|
|
114
115
|
});
|
|
115
|
-
}
|
|
116
|
+
});
|
|
116
117
|
/** Attachments waiting to send, in a row that scrolls sideways when full. */
|
|
117
|
-
function AttachmentTray({ label = "Attachments", className, children, ...props }) {
|
|
118
|
+
var AttachmentTray = React.forwardRef(function AttachmentTray({ label = "Attachments", className, children, ...props }, ref) {
|
|
118
119
|
return /* @__PURE__ */ jsx(InTray.Provider, {
|
|
119
120
|
value: true,
|
|
120
121
|
children: /* @__PURE__ */ jsx("div", {
|
|
122
|
+
ref,
|
|
121
123
|
"data-slot": "attachment-tray",
|
|
122
124
|
role: "list",
|
|
123
125
|
"aria-label": label,
|
|
@@ -126,14 +128,15 @@ function AttachmentTray({ label = "Attachments", className, children, ...props }
|
|
|
126
128
|
children
|
|
127
129
|
})
|
|
128
130
|
});
|
|
129
|
-
}
|
|
131
|
+
});
|
|
130
132
|
/** A list of uploads with a summary: "2 of 4 ready, 1 failed". */
|
|
131
|
-
function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, className, ...props }) {
|
|
133
|
+
var UploadQueue = React.forwardRef(function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, className, ...props }, ref) {
|
|
132
134
|
const headingId = React.useId();
|
|
133
135
|
const ready = files.filter((f) => f.state === "ready").length;
|
|
134
136
|
const failed = files.filter((f) => f.state === "failed").length;
|
|
135
137
|
const summary = files.length ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ""}` : "";
|
|
136
138
|
return /* @__PURE__ */ jsxs("section", {
|
|
139
|
+
ref,
|
|
137
140
|
"data-slot": "upload-queue",
|
|
138
141
|
"aria-labelledby": headingId,
|
|
139
142
|
className: cn("grid gap-2", className),
|
|
@@ -170,7 +173,7 @@ function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, class
|
|
|
170
173
|
})
|
|
171
174
|
})]
|
|
172
175
|
});
|
|
173
|
-
}
|
|
176
|
+
});
|
|
174
177
|
//#endregion
|
|
175
178
|
export { AttachmentItem, AttachmentTray, UploadQueue, formatBytes };
|
|
176
179
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.js","names":[],"sources":["../../src/components/attachment.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport RotateCw from '@burtson-labs/icons/react/rotate-cw';\nimport X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Progress } from './progress';\nimport { Spinner } from './spinner';\n\n/**\n * Where a file is on its way into a message. The app owns the upload and the\n * parsing; these components only show the state it reports.\n */\nexport type AttachmentState = 'queued' | 'uploading' | 'parsing' | 'ready' | 'failed';\n\nconst stateText: Record<AttachmentState, string> = {\n queued: 'Waiting',\n uploading: 'Uploading',\n parsing: 'Reading',\n ready: 'Ready',\n failed: 'Failed',\n};\n\n/** 1536 → \"1.5 KB\". */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst InTray = React.createContext(false);\n\nexport interface AttachmentItemProps extends Omit<React.ComponentProps<'div'>, 'children'> {\n name: string;\n /** Bytes. */\n size?: number;\n state?: AttachmentState;\n /** 0–100 while uploading or reading. Leave out for an indeterminate bar. */\n progress?: number;\n /** Why it failed, in words the person can act on. */\n error?: string;\n /** A thumbnail URL for images. */\n preview?: string;\n /** Replaces the file icon. */\n icon?: React.ReactNode;\n onRemove?: () => void;\n onRetry?: () => void;\n /** `chip` sits in a composer; `row` fills an upload list. */\n layout?: 'chip' | 'row';\n}\n\n/**\n * One attached file: name, size, state, progress, and remove/retry. State\n * changes are announced politely; a failure says why.\n */\nfunction AttachmentItem({\n name,\n size,\n state = 'ready',\n progress,\n error,\n preview,\n icon,\n onRemove,\n onRetry,\n layout = 'chip',\n className,\n ...props\n}: AttachmentItemProps) {\n const inTray = React.useContext(InTray);\n const busy = state === 'uploading' || state === 'parsing';\n const failed = state === 'failed';\n const meta = [size !== undefined ? formatBytes(size) : null, stateText[state]]\n .filter(Boolean)\n .join(' · ');\n return (\n <div\n data-slot=\"attachment-item\"\n data-state={state}\n data-layout={layout}\n role={inTray ? 'listitem' : undefined}\n aria-busy={busy || undefined}\n className={cn(\n 'group/attachment relative flex min-w-0 items-center gap-2.5 rounded-md border bg-surface text-sm',\n layout === 'chip' ? 'w-60 max-w-full shrink-0 p-1.5 pr-1' : 'w-full p-2.5',\n failed && 'border-destructive/40 bg-destructive/5',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'grid shrink-0 place-items-center overflow-hidden rounded-sm bg-muted text-muted-foreground [&_svg]:size-4',\n layout === 'chip' ? 'size-9' : 'size-10',\n )}\n >\n {preview ? (\n <img src={preview} alt=\"\" className=\"size-full object-cover\" />\n ) : failed ? (\n <AlertCircle className=\"text-destructive\" aria-hidden />\n ) : busy ? (\n <Spinner className=\"size-4\" role=\"presentation\" aria-hidden />\n ) : (\n (icon ?? <FileText aria-hidden />)\n )}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate font-medium text-foreground\" title={name}>\n {name}\n </span>\n <span\n aria-live=\"polite\"\n className={cn('truncate text-xs', failed ? 'text-destructive' : 'text-muted-foreground')}\n >\n <span className=\"sr-only\">{name}: </span>\n {failed && error ? error : meta}\n </span>\n {busy && (\n <Progress value={progress} aria-label={`${stateText[state]} ${name}`} className=\"h-1\" />\n )}\n </div>\n {failed && onRetry && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Retry ${name}`}\n title=\"Retry\"\n onClick={onRetry}\n >\n <RotateCw />\n </Button>\n )}\n {onRemove && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Remove ${name}`}\n title=\"Remove\"\n onClick={onRemove}\n >\n <X />\n </Button>\n )}\n </div>\n );\n}\n\nexport interface AttachmentTrayProps extends React.ComponentProps<'div'> {\n /** Accessible name for the list. */\n label?: string;\n}\n\n/** Attachments waiting to send, in a row that scrolls sideways when full. */\nfunction AttachmentTray({\n label = 'Attachments',\n className,\n children,\n ...props\n}: AttachmentTrayProps) {\n return (\n <InTray.Provider value={true}>\n <div\n data-slot=\"attachment-tray\"\n role=\"list\"\n aria-label={label}\n className={cn('flex max-w-full gap-1.5 overflow-x-auto pb-0.5', className)}\n {...props}\n >\n {children}\n </div>\n </InTray.Provider>\n );\n}\n\nexport interface UploadQueueFile {\n id: string;\n name: string;\n size?: number;\n state: AttachmentState;\n progress?: number;\n error?: string;\n}\n\nexport interface UploadQueueProps extends Omit<React.ComponentProps<'section'>, 'title'> {\n files: UploadQueueFile[];\n title?: React.ReactNode;\n onRemove?: (id: string) => void;\n onRetry?: (id: string) => void;\n /** Shown when there are no files. */\n empty?: React.ReactNode;\n}\n\n/** A list of uploads with a summary: \"2 of 4 ready, 1 failed\". */\nfunction UploadQueue({\n files,\n title = 'Uploads',\n onRemove,\n onRetry,\n empty,\n className,\n ...props\n}: UploadQueueProps) {\n const headingId = React.useId();\n const ready = files.filter((f) => f.state === 'ready').length;\n const failed = files.filter((f) => f.state === 'failed').length;\n const summary = files.length\n ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ''}`\n : '';\n return (\n <section\n data-slot=\"upload-queue\"\n aria-labelledby={headingId}\n className={cn('grid gap-2', className)}\n {...props}\n >\n <div className=\"flex items-baseline justify-between gap-3\">\n <h3 id={headingId} className=\"text-sm font-semibold\">\n {title}\n </h3>\n <span className=\"text-xs text-muted-foreground tabular-nums\">{summary}</span>\n </div>\n {files.length === 0 ? (\n empty ? (\n <div className=\"text-sm text-muted-foreground\">{empty}</div>\n ) : null\n ) : (\n <InTray.Provider value={true}>\n <div role=\"list\" aria-labelledby={headingId} className=\"grid gap-1.5\">\n {files.map((f) => (\n <AttachmentItem\n key={f.id}\n layout=\"row\"\n name={f.name}\n size={f.size}\n state={f.state}\n progress={f.progress}\n error={f.error}\n onRemove={onRemove ? () => onRemove(f.id) : undefined}\n onRetry={onRetry ? () => onRetry(f.id) : undefined}\n />\n ))}\n </div>\n </InTray.Provider>\n )}\n </section>\n );\n}\n\nexport { AttachmentItem, AttachmentTray, UploadQueue };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"attachment.js","names":[],"sources":["../../src/components/attachment.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport RotateCw from '@burtson-labs/icons/react/rotate-cw';\nimport X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Progress } from './progress';\nimport { Spinner } from './spinner';\n\n/**\n * Where a file is on its way into a message. The app owns the upload and the\n * parsing; these components only show the state it reports.\n */\nexport type AttachmentState = 'queued' | 'uploading' | 'parsing' | 'ready' | 'failed';\n\nconst stateText: Record<AttachmentState, string> = {\n queued: 'Waiting',\n uploading: 'Uploading',\n parsing: 'Reading',\n ready: 'Ready',\n failed: 'Failed',\n};\n\n/** 1536 → \"1.5 KB\". */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst InTray = React.createContext(false);\n\nexport interface AttachmentItemProps extends Omit<React.ComponentProps<'div'>, 'children'> {\n name: string;\n /** Bytes. */\n size?: number;\n state?: AttachmentState;\n /** 0–100 while uploading or reading. Leave out for an indeterminate bar. */\n progress?: number;\n /** Why it failed, in words the person can act on. */\n error?: string;\n /** A thumbnail URL for images. */\n preview?: string;\n /** Replaces the file icon. */\n icon?: React.ReactNode;\n onRemove?: () => void;\n onRetry?: () => void;\n /** `chip` sits in a composer; `row` fills an upload list. */\n layout?: 'chip' | 'row';\n}\n\n/**\n * One attached file: name, size, state, progress, and remove/retry. State\n * changes are announced politely; a failure says why.\n */\nconst AttachmentItem = React.forwardRef<HTMLDivElement, AttachmentItemProps>(\n function AttachmentItem(\n {\n name,\n size,\n state = 'ready',\n progress,\n error,\n preview,\n icon,\n onRemove,\n onRetry,\n layout = 'chip',\n className,\n ...props\n },\n ref,\n ) {\n const inTray = React.useContext(InTray);\n const busy = state === 'uploading' || state === 'parsing';\n const failed = state === 'failed';\n const meta = [size !== undefined ? formatBytes(size) : null, stateText[state]]\n .filter(Boolean)\n .join(' · ');\n return (\n <div\n ref={ref}\n data-slot=\"attachment-item\"\n data-state={state}\n data-layout={layout}\n role={inTray ? 'listitem' : undefined}\n aria-busy={busy || undefined}\n className={cn(\n 'group/attachment relative flex min-w-0 items-center gap-2.5 rounded-md border bg-surface text-sm',\n layout === 'chip' ? 'w-60 max-w-full shrink-0 p-1.5 pr-1' : 'w-full p-2.5',\n failed && 'border-destructive/40 bg-destructive/5',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'grid shrink-0 place-items-center overflow-hidden rounded-sm bg-muted text-muted-foreground [&_svg]:size-4',\n layout === 'chip' ? 'size-9' : 'size-10',\n )}\n >\n {preview ? (\n <img src={preview} alt=\"\" className=\"size-full object-cover\" />\n ) : failed ? (\n <AlertCircle className=\"text-destructive\" aria-hidden />\n ) : busy ? (\n <Spinner className=\"size-4\" role=\"presentation\" aria-hidden />\n ) : (\n (icon ?? <FileText aria-hidden />)\n )}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate font-medium text-foreground\" title={name}>\n {name}\n </span>\n <span\n aria-live=\"polite\"\n className={cn(\n 'truncate text-xs',\n failed ? 'text-destructive' : 'text-muted-foreground',\n )}\n >\n <span className=\"sr-only\">{name}: </span>\n {failed && error ? error : meta}\n </span>\n {busy && (\n <Progress value={progress} aria-label={`${stateText[state]} ${name}`} className=\"h-1\" />\n )}\n </div>\n {failed && onRetry && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Retry ${name}`}\n title=\"Retry\"\n onClick={onRetry}\n >\n <RotateCw />\n </Button>\n )}\n {onRemove && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Remove ${name}`}\n title=\"Remove\"\n onClick={onRemove}\n >\n <X />\n </Button>\n )}\n </div>\n );\n },\n);\n\nexport interface AttachmentTrayProps extends React.ComponentProps<'div'> {\n /** Accessible name for the list. */\n label?: string;\n}\n\n/** Attachments waiting to send, in a row that scrolls sideways when full. */\nconst AttachmentTray = React.forwardRef<HTMLDivElement, AttachmentTrayProps>(\n function AttachmentTray({ label = 'Attachments', className, children, ...props }, ref) {\n return (\n <InTray.Provider value={true}>\n <div\n ref={ref}\n data-slot=\"attachment-tray\"\n role=\"list\"\n aria-label={label}\n className={cn('flex max-w-full gap-1.5 overflow-x-auto pb-0.5', className)}\n {...props}\n >\n {children}\n </div>\n </InTray.Provider>\n );\n },\n);\n\nexport interface UploadQueueFile {\n id: string;\n name: string;\n size?: number;\n state: AttachmentState;\n progress?: number;\n error?: string;\n}\n\nexport interface UploadQueueProps extends Omit<React.ComponentProps<'section'>, 'title'> {\n files: UploadQueueFile[];\n title?: React.ReactNode;\n onRemove?: (id: string) => void;\n onRetry?: (id: string) => void;\n /** Shown when there are no files. */\n empty?: React.ReactNode;\n}\n\n/** A list of uploads with a summary: \"2 of 4 ready, 1 failed\". */\nconst UploadQueue = React.forwardRef<HTMLElement, UploadQueueProps>(function UploadQueue(\n { files, title = 'Uploads', onRemove, onRetry, empty, className, ...props },\n ref,\n) {\n const headingId = React.useId();\n const ready = files.filter((f) => f.state === 'ready').length;\n const failed = files.filter((f) => f.state === 'failed').length;\n const summary = files.length\n ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ''}`\n : '';\n return (\n <section\n ref={ref}\n data-slot=\"upload-queue\"\n aria-labelledby={headingId}\n className={cn('grid gap-2', className)}\n {...props}\n >\n <div className=\"flex items-baseline justify-between gap-3\">\n <h3 id={headingId} className=\"text-sm font-semibold\">\n {title}\n </h3>\n <span className=\"text-xs text-muted-foreground tabular-nums\">{summary}</span>\n </div>\n {files.length === 0 ? (\n empty ? (\n <div className=\"text-sm text-muted-foreground\">{empty}</div>\n ) : null\n ) : (\n <InTray.Provider value={true}>\n <div role=\"list\" aria-labelledby={headingId} className=\"grid gap-1.5\">\n {files.map((f) => (\n <AttachmentItem\n key={f.id}\n layout=\"row\"\n name={f.name}\n size={f.size}\n state={f.state}\n progress={f.progress}\n error={f.error}\n onRemove={onRemove ? () => onRemove(f.id) : undefined}\n onRetry={onRetry ? () => onRetry(f.id) : undefined}\n />\n ))}\n </div>\n </InTray.Provider>\n )}\n </section>\n );\n});\n\nexport { AttachmentItem, AttachmentTray, UploadQueue };\n"],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ export interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'tit
|
|
|
33
33
|
* time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a
|
|
34
34
|
* slider for screen readers.
|
|
35
35
|
*/
|
|
36
|
-
declare
|
|
36
|
+
declare const AudioPlayer: React.ForwardRefExoticComponent<Omit<AudioPlayerProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
37
37
|
/** A voice note inside a message: the compact AudioPlayer. */
|
|
38
|
-
declare
|
|
38
|
+
declare const VoiceMessage: React.ForwardRefExoticComponent<Omit<Omit<AudioPlayerProps, "variant">, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
39
39
|
export { AudioPlayer, VoiceMessage };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { cn, focusRingClasses } from "../lib/utils.js";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -63,7 +63,7 @@ var SPEEDS = [
|
|
|
63
63
|
* time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a
|
|
64
64
|
* slider for screen readers.
|
|
65
65
|
*/
|
|
66
|
-
function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, transcript, downloadName, variant = "default", onPlayChange, className, ...props }) {
|
|
66
|
+
var AudioPlayer = React.forwardRef(function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, transcript, downloadName, variant = "default", onPlayChange, className, ...props }, ref) {
|
|
67
67
|
const audio = React.useRef(null);
|
|
68
68
|
const [playing, setPlaying] = React.useState(false);
|
|
69
69
|
const [current, setCurrent] = React.useState(0);
|
|
@@ -117,6 +117,7 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
|
|
|
117
117
|
};
|
|
118
118
|
const bars = peaks && peaks.length ? peaks : null;
|
|
119
119
|
return /* @__PURE__ */ jsxs("div", {
|
|
120
|
+
ref,
|
|
120
121
|
"data-slot": "audio-player",
|
|
121
122
|
"data-variant": variant,
|
|
122
123
|
className: cn("grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground", compact ? "w-full max-w-sm p-2" : "p-3", className),
|
|
@@ -169,7 +170,7 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
|
|
|
169
170
|
if (!rect.width || !total) return;
|
|
170
171
|
seek((e.clientX - rect.left) / rect.width * total);
|
|
171
172
|
},
|
|
172
|
-
className: cn("relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm
|
|
173
|
+
className: cn("relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm", focusRingClasses, compact ? "h-7" : "h-9", "pointer-coarse:h-11"),
|
|
173
174
|
children: bars ? /* @__PURE__ */ jsx("div", {
|
|
174
175
|
"aria-hidden": true,
|
|
175
176
|
className: "flex h-full w-full items-center gap-[2px]",
|
|
@@ -236,15 +237,16 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
|
|
|
236
237
|
})
|
|
237
238
|
]
|
|
238
239
|
});
|
|
239
|
-
}
|
|
240
|
+
});
|
|
240
241
|
/** A voice note inside a message: the compact AudioPlayer. */
|
|
241
|
-
function VoiceMessage(props) {
|
|
242
|
+
var VoiceMessage = React.forwardRef(function VoiceMessage(props, ref) {
|
|
242
243
|
return /* @__PURE__ */ jsx(AudioPlayer, {
|
|
244
|
+
ref,
|
|
243
245
|
title: "Voice message",
|
|
244
246
|
...props,
|
|
245
247
|
variant: "compact"
|
|
246
248
|
});
|
|
247
|
-
}
|
|
249
|
+
});
|
|
248
250
|
//#endregion
|
|
249
251
|
export { AudioPlayer, VoiceMessage, computePeaks, formatDuration, peaksFromChannelData };
|
|
250
252
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-player.js","names":[],"sources":["../../src/components/audio-player.tsx"],"sourcesContent":["import Download from '@burtson-labs/icons/react/download';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\n/** 83.4 → \"1:23\"; 3723 → \"1:02:03\". */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = String(s % 60).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${sec}` : `${m}:${sec}`;\n}\n\n/**\n * Reduce decoded audio to `bars` peak heights between 0 and 1 (the loudest\n * sample in each slice, normalised to the loudest slice).\n */\nexport function peaksFromChannelData(data: Float32Array, bars = 48): number[] {\n if (!data.length || bars < 1) return [];\n const size = Math.max(1, Math.floor(data.length / bars));\n const peaks: number[] = [];\n for (let b = 0; b < bars; b++) {\n let max = 0;\n const end = Math.min(data.length, (b + 1) * size);\n for (let i = b * size; i < end; i++) {\n const v = Math.abs(data[i] ?? 0);\n if (v > max) max = v;\n }\n peaks.push(max);\n }\n const top = Math.max(...peaks) || 1;\n return peaks.map((p) => p / top);\n}\n\n/**\n * Decode an audio file or URL with Web Audio and return its peaks for\n * AudioPlayer. Browser only; for long files compute peaks on the server.\n */\nexport async function computePeaks(source: Blob | string, bars = 48): Promise<number[]> {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio is not available in this browser.');\n const buf =\n typeof source === 'string'\n ? await (await fetch(source)).arrayBuffer()\n : await source.arrayBuffer();\n const ctx = new Ctx();\n try {\n const audio = await ctx.decodeAudioData(buf);\n return peaksFromChannelData(audio.getChannelData(0), bars);\n } finally {\n void ctx.close();\n }\n}\n\nconst SPEEDS = [1, 1.5, 2] as const;\n\nexport interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n src: string;\n /** Waveform heights 0–1 (see computePeaks). Leave out for a plain progress bar. */\n peaks?: number[];\n /** Name read out with the controls, e.g. \"Voice note from Dana\". */\n title?: string;\n /** Known length in seconds, shown before the file has loaded. */\n duration?: number;\n /** Text of the recording; adds a Transcript toggle. */\n transcript?: React.ReactNode;\n /** Adds a download link with this file name. */\n downloadName?: string;\n /** `compact` fits inside a message bubble. */\n variant?: 'default' | 'compact';\n onPlayChange?: (playing: boolean) => void;\n}\n\n/**\n * Plays one recording: play and pause, a waveform you can click or drive\n * with the arrow keys (5 seconds a step, Home and End), elapsed and total\n * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a\n * slider for screen readers.\n */\nfunction AudioPlayer({\n src,\n peaks,\n title = 'Audio',\n duration: knownDuration,\n transcript,\n downloadName,\n variant = 'default',\n onPlayChange,\n className,\n ...props\n}: AudioPlayerProps) {\n const audio = React.useRef<HTMLAudioElement>(null);\n const [playing, setPlaying] = React.useState(false);\n const [current, setCurrent] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration ?? 0);\n const [speed, setSpeed] = React.useState<(typeof SPEEDS)[number]>(1);\n const [showTranscript, setShowTranscript] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const transcriptId = React.useId();\n const compact = variant === 'compact';\n const total = duration || knownDuration || 0;\n const ratio = total ? Math.min(1, current / total) : 0;\n\n React.useEffect(() => {\n if (audio.current) audio.current.playbackRate = speed;\n }, [speed]);\n\n const seek = (t: number) => {\n const el = audio.current;\n const clamped = Math.max(0, Math.min(total || t, t));\n if (el) el.currentTime = clamped;\n setCurrent(clamped);\n };\n\n const togglePlay = async () => {\n const el = audio.current;\n if (!el) return;\n if (el.paused) {\n try {\n await el.play();\n } catch {\n setFailed(true);\n }\n } else el.pause();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n const step = { ArrowRight: 5, ArrowUp: 5, ArrowLeft: -5, ArrowDown: -5 }[e.key];\n if (step !== undefined) {\n e.preventDefault();\n seek(current + step);\n } else if (e.key === 'Home') {\n e.preventDefault();\n seek(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n seek(total);\n } else if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n void togglePlay();\n }\n };\n\n const bars = peaks && peaks.length ? peaks : null;\n\n return (\n <div\n data-slot=\"audio-player\"\n data-variant={variant}\n className={cn(\n 'grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground',\n compact ? 'w-full max-w-sm p-2' : 'p-3',\n className,\n )}\n {...props}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- the transcript prop is the text alternative */}\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onLoadedMetadata={(e) => {\n const d = e.currentTarget.duration;\n if (Number.isFinite(d)) setDuration(d);\n }}\n onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime)}\n onPlay={() => {\n setPlaying(true);\n onPlayChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayChange?.(false);\n }}\n onEnded={() => setCurrent(0)}\n onError={() => setFailed(true)}\n />\n <div className=\"flex min-w-0 items-center gap-2\">\n <Button\n type=\"button\"\n size={compact ? 'icon-sm' : 'icon'}\n variant={playing ? 'secondary' : 'default'}\n aria-label={playing ? `Pause ${title}` : `Play ${title}`}\n onClick={() => void togglePlay()}\n disabled={failed}\n className=\"shrink-0 rounded-full pointer-coarse:size-11\"\n >\n {playing ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}\n </Button>\n <div\n role=\"slider\"\n tabIndex={failed ? -1 : 0}\n aria-label={`Seek ${title}`}\n aria-valuemin={0}\n aria-valuemax={Math.round(total)}\n aria-valuenow={Math.round(current)}\n aria-valuetext={`${formatDuration(current)} of ${formatDuration(total)}`}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n if (!rect.width || !total) return;\n seek(((e.clientX - rect.left) / rect.width) * total);\n }}\n className={cn(\n 'relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20',\n compact ? 'h-7' : 'h-9',\n )}\n >\n {bars ? (\n <div aria-hidden className=\"flex h-full w-full items-center gap-[2px]\">\n {bars.map((p, i) => (\n <span\n key={i}\n className={cn(\n 'min-w-[2px] flex-1 rounded-full transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)]',\n (i + 0.5) / bars.length <= ratio ? 'bg-brand' : 'bg-border-strong',\n )}\n style={{ height: `${Math.max(12, Math.round(p * 100))}%` }}\n />\n ))}\n </div>\n ) : (\n <div aria-hidden className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-brand\" style={{ width: `${ratio * 100}%` }} />\n </div>\n )}\n </div>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums\">\n {playing || current ? formatDuration(current) : formatDuration(total)}\n </span>\n </div>\n {failed ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n This recording could not be played.\n </p>\n ) : (\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={`Playback speed ${speed}×`}\n onClick={() => setSpeed(SPEEDS[(SPEEDS.indexOf(speed) + 1) % SPEEDS.length] ?? 1)}\n className=\"font-mono tabular-nums\"\n >\n {speed}×\n </Button>\n {transcript && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-expanded={showTranscript}\n aria-controls={transcriptId}\n onClick={() => setShowTranscript((v) => !v)}\n >\n <FileText /> Transcript\n </Button>\n )}\n {downloadName && (\n <Button variant=\"ghost\" size=\"xs\" asChild>\n <a href={src} download={downloadName}>\n <Download /> Download\n </a>\n </Button>\n )}\n </div>\n )}\n {transcript && showTranscript && (\n <div\n id={transcriptId}\n className=\"animate-in rounded-md bg-muted/60 p-2.5 text-sm leading-6 text-muted-foreground\"\n >\n {transcript}\n </div>\n )}\n </div>\n );\n}\n\n/** A voice note inside a message: the compact AudioPlayer. */\nfunction VoiceMessage(props: Omit<AudioPlayerProps, 'variant'>) {\n return <AudioPlayer title=\"Voice message\" {...props} variant=\"compact\" />;\n}\n\nexport { AudioPlayer, VoiceMessage };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audio-player.js","names":[],"sources":["../../src/components/audio-player.tsx"],"sourcesContent":["import Download from '@burtson-labs/icons/react/download';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses } from '../lib/utils';\n\nimport { Button } from './button';\n\n/** 83.4 → \"1:23\"; 3723 → \"1:02:03\". */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = String(s % 60).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${sec}` : `${m}:${sec}`;\n}\n\n/**\n * Reduce decoded audio to `bars` peak heights between 0 and 1 (the loudest\n * sample in each slice, normalised to the loudest slice).\n */\nexport function peaksFromChannelData(data: Float32Array, bars = 48): number[] {\n if (!data.length || bars < 1) return [];\n const size = Math.max(1, Math.floor(data.length / bars));\n const peaks: number[] = [];\n for (let b = 0; b < bars; b++) {\n let max = 0;\n const end = Math.min(data.length, (b + 1) * size);\n for (let i = b * size; i < end; i++) {\n const v = Math.abs(data[i] ?? 0);\n if (v > max) max = v;\n }\n peaks.push(max);\n }\n const top = Math.max(...peaks) || 1;\n return peaks.map((p) => p / top);\n}\n\n/**\n * Decode an audio file or URL with Web Audio and return its peaks for\n * AudioPlayer. Browser only; for long files compute peaks on the server.\n */\nexport async function computePeaks(source: Blob | string, bars = 48): Promise<number[]> {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio is not available in this browser.');\n const buf =\n typeof source === 'string'\n ? await (await fetch(source)).arrayBuffer()\n : await source.arrayBuffer();\n const ctx = new Ctx();\n try {\n const audio = await ctx.decodeAudioData(buf);\n return peaksFromChannelData(audio.getChannelData(0), bars);\n } finally {\n void ctx.close();\n }\n}\n\nconst SPEEDS = [1, 1.5, 2] as const;\n\nexport interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n src: string;\n /** Waveform heights 0–1 (see computePeaks). Leave out for a plain progress bar. */\n peaks?: number[];\n /** Name read out with the controls, e.g. \"Voice note from Dana\". */\n title?: string;\n /** Known length in seconds, shown before the file has loaded. */\n duration?: number;\n /** Text of the recording; adds a Transcript toggle. */\n transcript?: React.ReactNode;\n /** Adds a download link with this file name. */\n downloadName?: string;\n /** `compact` fits inside a message bubble. */\n variant?: 'default' | 'compact';\n onPlayChange?: (playing: boolean) => void;\n}\n\n/**\n * Plays one recording: play and pause, a waveform you can click or drive\n * with the arrow keys (5 seconds a step, Home and End), elapsed and total\n * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a\n * slider for screen readers.\n */\nconst AudioPlayer = React.forwardRef<HTMLDivElement, AudioPlayerProps>(function AudioPlayer(\n {\n src,\n peaks,\n title = 'Audio',\n duration: knownDuration,\n transcript,\n downloadName,\n variant = 'default',\n onPlayChange,\n className,\n ...props\n },\n ref,\n) {\n const audio = React.useRef<HTMLAudioElement>(null);\n const [playing, setPlaying] = React.useState(false);\n const [current, setCurrent] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration ?? 0);\n const [speed, setSpeed] = React.useState<(typeof SPEEDS)[number]>(1);\n const [showTranscript, setShowTranscript] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const transcriptId = React.useId();\n const compact = variant === 'compact';\n const total = duration || knownDuration || 0;\n const ratio = total ? Math.min(1, current / total) : 0;\n\n React.useEffect(() => {\n if (audio.current) audio.current.playbackRate = speed;\n }, [speed]);\n\n const seek = (t: number) => {\n const el = audio.current;\n const clamped = Math.max(0, Math.min(total || t, t));\n if (el) el.currentTime = clamped;\n setCurrent(clamped);\n };\n\n const togglePlay = async () => {\n const el = audio.current;\n if (!el) return;\n if (el.paused) {\n try {\n await el.play();\n } catch {\n setFailed(true);\n }\n } else el.pause();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n const step = { ArrowRight: 5, ArrowUp: 5, ArrowLeft: -5, ArrowDown: -5 }[e.key];\n if (step !== undefined) {\n e.preventDefault();\n seek(current + step);\n } else if (e.key === 'Home') {\n e.preventDefault();\n seek(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n seek(total);\n } else if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n void togglePlay();\n }\n };\n\n const bars = peaks && peaks.length ? peaks : null;\n\n return (\n <div\n ref={ref}\n data-slot=\"audio-player\"\n data-variant={variant}\n className={cn(\n 'grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground',\n compact ? 'w-full max-w-sm p-2' : 'p-3',\n className,\n )}\n {...props}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- the transcript prop is the text alternative */}\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onLoadedMetadata={(e) => {\n const d = e.currentTarget.duration;\n if (Number.isFinite(d)) setDuration(d);\n }}\n onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime)}\n onPlay={() => {\n setPlaying(true);\n onPlayChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayChange?.(false);\n }}\n onEnded={() => setCurrent(0)}\n onError={() => setFailed(true)}\n />\n <div className=\"flex min-w-0 items-center gap-2\">\n <Button\n type=\"button\"\n size={compact ? 'icon-sm' : 'icon'}\n variant={playing ? 'secondary' : 'default'}\n aria-label={playing ? `Pause ${title}` : `Play ${title}`}\n onClick={() => void togglePlay()}\n disabled={failed}\n className=\"shrink-0 rounded-full pointer-coarse:size-11\"\n >\n {playing ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}\n </Button>\n <div\n role=\"slider\"\n tabIndex={failed ? -1 : 0}\n aria-label={`Seek ${title}`}\n aria-valuemin={0}\n aria-valuemax={Math.round(total)}\n aria-valuenow={Math.round(current)}\n aria-valuetext={`${formatDuration(current)} of ${formatDuration(total)}`}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n if (!rect.width || !total) return;\n seek(((e.clientX - rect.left) / rect.width) * total);\n }}\n className={cn(\n 'relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm',\n focusRingClasses,\n compact ? 'h-7' : 'h-9',\n 'pointer-coarse:h-11',\n )}\n >\n {bars ? (\n <div aria-hidden className=\"flex h-full w-full items-center gap-[2px]\">\n {bars.map((p, i) => (\n <span\n key={i}\n className={cn(\n 'min-w-[2px] flex-1 rounded-full transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)]',\n (i + 0.5) / bars.length <= ratio ? 'bg-brand' : 'bg-border-strong',\n )}\n style={{ height: `${Math.max(12, Math.round(p * 100))}%` }}\n />\n ))}\n </div>\n ) : (\n <div aria-hidden className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-brand\" style={{ width: `${ratio * 100}%` }} />\n </div>\n )}\n </div>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums\">\n {playing || current ? formatDuration(current) : formatDuration(total)}\n </span>\n </div>\n {failed ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n This recording could not be played.\n </p>\n ) : (\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={`Playback speed ${speed}×`}\n onClick={() => setSpeed(SPEEDS[(SPEEDS.indexOf(speed) + 1) % SPEEDS.length] ?? 1)}\n className=\"font-mono tabular-nums\"\n >\n {speed}×\n </Button>\n {transcript && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-expanded={showTranscript}\n aria-controls={transcriptId}\n onClick={() => setShowTranscript((v) => !v)}\n >\n <FileText /> Transcript\n </Button>\n )}\n {downloadName && (\n <Button variant=\"ghost\" size=\"xs\" asChild>\n <a href={src} download={downloadName}>\n <Download /> Download\n </a>\n </Button>\n )}\n </div>\n )}\n {transcript && showTranscript && (\n <div\n id={transcriptId}\n className=\"animate-in rounded-md bg-muted/60 p-2.5 text-sm leading-6 text-muted-foreground\"\n >\n {transcript}\n </div>\n )}\n </div>\n );\n});\n\n/** A voice note inside a message: the compact AudioPlayer. */\nconst VoiceMessage = React.forwardRef<HTMLDivElement, Omit<AudioPlayerProps, 'variant'>>(\n function VoiceMessage(props, ref) {\n return <AudioPlayer ref={ref} title=\"Voice message\" {...props} variant=\"compact\" />;\n },\n);\n\nexport { AudioPlayer, VoiceMessage };\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';
|
|
3
|
-
declare
|
|
4
|
-
declare
|
|
5
|
-
declare
|
|
3
|
+
declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
4
|
+
declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
|
|
5
|
+
declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
6
6
|
export { Avatar, AvatarFallback, AvatarImage };
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { Avatar as Avatar$1, AvatarFallback as AvatarFallback$1, AvatarImage as AvatarImage$1 } from "../primitives/vendor/radix/react-avatar/avatar.js";
|
|
4
|
-
import "react";
|
|
4
|
+
import * as React from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/avatar.tsx
|
|
7
|
-
function Avatar({ className, ...props }) {
|
|
7
|
+
var Avatar = React.forwardRef(function Avatar({ className, ...props }, ref) {
|
|
8
8
|
return /* @__PURE__ */ jsx(Avatar$1, {
|
|
9
|
+
ref,
|
|
9
10
|
"data-slot": "avatar",
|
|
10
11
|
className: cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className),
|
|
11
12
|
...props
|
|
12
13
|
});
|
|
13
|
-
}
|
|
14
|
-
function AvatarImage({ className, ...props }) {
|
|
14
|
+
});
|
|
15
|
+
var AvatarImage = React.forwardRef(function AvatarImage({ className, ...props }, ref) {
|
|
15
16
|
return /* @__PURE__ */ jsx(AvatarImage$1, {
|
|
17
|
+
ref,
|
|
16
18
|
"data-slot": "avatar-image",
|
|
17
19
|
className: cn("aspect-square size-full object-cover", className),
|
|
18
20
|
...props
|
|
19
21
|
});
|
|
20
|
-
}
|
|
21
|
-
function AvatarFallback({ className, ...props }) {
|
|
22
|
+
});
|
|
23
|
+
var AvatarFallback = React.forwardRef(function AvatarFallback({ className, ...props }, ref) {
|
|
22
24
|
return /* @__PURE__ */ jsx(AvatarFallback$1, {
|
|
25
|
+
ref,
|
|
23
26
|
"data-slot": "avatar-fallback",
|
|
24
27
|
className: cn("flex size-full items-center justify-center rounded-full border border-brand/15 bg-brand-soft text-xs font-semibold text-brand-soft-foreground", className),
|
|
25
28
|
...props
|
|
26
29
|
});
|
|
27
|
-
}
|
|
30
|
+
});
|
|
28
31
|
//#endregion
|
|
29
32
|
export { Avatar, AvatarFallback, AvatarImage };
|
|
30
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.js","names":["AvatarPrimitive.Root","AvatarPrimitive.Image","AvatarPrimitive.Fallback"],"sources":["../../src/components/avatar.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';\n\
|
|
1
|
+
{"version":3,"file":"avatar.js","names":["AvatarPrimitive.Root","AvatarPrimitive.Image","AvatarPrimitive.Fallback"],"sources":["../../src/components/avatar.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';\n\nconst Avatar = React.forwardRef<HTMLSpanElement, React.ComponentProps<typeof AvatarPrimitive.Root>>(\n function Avatar({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Root\n ref={ref}\n data-slot=\"avatar\"\n className={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}\n {...props}\n />\n );\n },\n);\n\nconst AvatarImage = React.forwardRef<\n HTMLImageElement,\n React.ComponentProps<typeof AvatarPrimitive.Image>\n>(function AvatarImage({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Image\n ref={ref}\n data-slot=\"avatar-image\"\n className={cn('aspect-square size-full object-cover', className)}\n {...props}\n />\n );\n});\n\nconst AvatarFallback = React.forwardRef<\n HTMLSpanElement,\n React.ComponentProps<typeof AvatarPrimitive.Fallback>\n>(function AvatarFallback({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Fallback\n ref={ref}\n data-slot=\"avatar-fallback\"\n className={cn(\n 'flex size-full items-center justify-center rounded-full border border-brand/15 bg-brand-soft text-xs font-semibold text-brand-soft-foreground',\n className,\n )}\n {...props}\n />\n );\n});\n\nexport { Avatar, AvatarFallback, AvatarImage };\n"],"mappings":";;;;;;AAKA,IAAM,SAAS,MAAM,WACnB,SAAS,OAAO,EAAE,WAAW,GAAG,SAAS,KAAK;CAC5C,OACE,oBAACA,UAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,8DAA8D,SAAS;EACrF,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,cAAc,MAAM,WAGxB,SAAS,YAAY,EAAE,WAAW,GAAG,SAAS,KAAK;CACnD,OACE,oBAACC,eAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,wCAAwC,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL,CAAC;AAED,IAAM,iBAAiB,MAAM,WAG3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACtD,OACE,oBAACC,kBAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,iJACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CAAC"}
|
|
@@ -6,5 +6,5 @@ declare const badgeVariants: (props?: ({
|
|
|
6
6
|
export interface BadgeProps extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {
|
|
7
7
|
asChild?: boolean;
|
|
8
8
|
}
|
|
9
|
-
declare
|
|
9
|
+
declare const Badge: React.ForwardRefExoticComponent<Omit<BadgeProps, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
10
10
|
export { Badge, badgeVariants };
|
package/dist/components/badge.js
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { cn, focusRingClasses } from "../lib/utils.js";
|
|
3
3
|
import { Root as Slot } from "../primitives/vendor/radix/react-slot/slot.js";
|
|
4
|
-
import "react";
|
|
4
|
+
import * as React from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { cva } from "class-variance-authority";
|
|
7
7
|
//#region src/components/badge.tsx
|
|
8
|
-
var badgeVariants = cva(
|
|
8
|
+
var badgeVariants = cva(`inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap transition-colors ${focusRingClasses} [&>svg]:size-3`, {
|
|
9
9
|
variants: { variant: {
|
|
10
10
|
default: "border-border bg-surface-muted text-foreground",
|
|
11
11
|
secondary: "border-border bg-surface-muted text-foreground",
|
|
12
12
|
brand: "border-brand/20 bg-brand-soft text-brand-soft-foreground",
|
|
13
|
-
success: "border-success/20 bg-success/10 text-success",
|
|
14
|
-
warning: "border-warning/20 bg-warning/10 text-warning",
|
|
15
|
-
destructive: "border-destructive/20 bg-destructive/10 text-destructive",
|
|
16
|
-
info: "border-info/20 bg-info/10 text-info",
|
|
13
|
+
success: "border-success/20 bg-success/10 text-[color-mix(in_srgb,var(--success)_75%,var(--foreground))]",
|
|
14
|
+
warning: "border-warning/20 bg-warning/10 text-[color-mix(in_srgb,var(--warning)_75%,var(--foreground))]",
|
|
15
|
+
destructive: "border-destructive/20 bg-destructive/10 text-[color-mix(in_srgb,var(--destructive)_75%,var(--foreground))]",
|
|
16
|
+
info: "border-info/20 bg-info/10 text-[color-mix(in_srgb,var(--info)_75%,var(--foreground))]",
|
|
17
17
|
outline: "border-border-strong bg-transparent text-muted-foreground"
|
|
18
18
|
} },
|
|
19
19
|
defaultVariants: { variant: "default" }
|
|
20
20
|
});
|
|
21
|
-
function Badge({ className, variant, asChild = false, ...props }) {
|
|
21
|
+
var Badge = React.forwardRef(function Badge({ className, variant, asChild = false, ...props }, ref) {
|
|
22
22
|
return /* @__PURE__ */ jsx(asChild ? Slot : "span", {
|
|
23
|
+
ref,
|
|
23
24
|
"data-slot": "badge",
|
|
24
|
-
|
|
25
|
+
"data-variant": variant ?? "default",
|
|
26
|
+
className: cn(badgeVariants({ variant }), asChild && "relative pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2 pointer-coarse:before:content-['']", className),
|
|
25
27
|
...props
|
|
26
28
|
});
|
|
27
|
-
}
|
|
29
|
+
});
|
|
28
30
|
//#endregion
|
|
29
31
|
export { Badge, badgeVariants };
|
|
30
32
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge.js","names":["Slot.Root"],"sources":["../../src/components/badge.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst badgeVariants = cva(\n
|
|
1
|
+
{"version":3,"file":"badge.js","names":["Slot.Root"],"sources":["../../src/components/badge.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses, touchTargetRowClasses } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst badgeVariants = cva(\n `inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap transition-colors ${focusRingClasses} [&>svg]:size-3`,\n {\n variants: {\n variant: {\n default: 'border-border bg-surface-muted text-foreground',\n // 0.x name for the neutral tag; kept so existing apps keep compiling.\n secondary: 'border-border bg-surface-muted text-foreground',\n brand: 'border-brand/20 bg-brand-soft text-brand-soft-foreground',\n // Status text is the tone mixed a quarter toward the foreground: the\n // plain tone reads at 4.3–4.5:1 on its own tint, the mixed one at 6:1\n // (4.5:1 inside a terminal Card). Mixing toward the foreground darkens\n // it in light mode and lightens it in dark mode and in a terminal Card,\n // which re-points --foreground.\n success:\n 'border-success/20 bg-success/10 text-[color-mix(in_srgb,var(--success)_75%,var(--foreground))]',\n warning:\n 'border-warning/20 bg-warning/10 text-[color-mix(in_srgb,var(--warning)_75%,var(--foreground))]',\n destructive:\n 'border-destructive/20 bg-destructive/10 text-[color-mix(in_srgb,var(--destructive)_75%,var(--foreground))]',\n info: 'border-info/20 bg-info/10 text-[color-mix(in_srgb,var(--info)_75%,var(--foreground))]',\n outline: 'border-border-strong bg-transparent text-muted-foreground',\n },\n },\n defaultVariants: { variant: 'default' },\n },\n);\n\nexport interface BadgeProps\n extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {\n asChild?: boolean;\n}\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, variant, asChild = false, ...props },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'span';\n return (\n <Comp\n ref={ref}\n data-slot=\"badge\"\n data-variant={variant ?? 'default'}\n // A badge that is a link or button (asChild) gets a 44px hit area on touch screens.\n className={cn(badgeVariants({ variant }), asChild && touchTargetRowClasses, className)}\n {...props}\n />\n );\n});\n\nexport { Badge, badgeVariants };\n"],"mappings":";;;;;;;AAMA,IAAM,gBAAgB,IACpB,2JAA2J,iBAAiB,kBAC5K;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAET,WAAW;EACX,OAAO;EAMP,SACE;EACF,SACE;EACF,aACE;EACF,MAAM;EACN,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAOA,IAAM,QAAQ,MAAM,WAAwC,SAAS,MACnE,EAAE,WAAW,SAAS,UAAU,OAAO,GAAG,SAC1C,KACA;CAEA,OACE,oBAFW,UAAUA,OAAY,QAEjC;EACO;EACL,aAAU;EACV,gBAAc,WAAW;EAEzB,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,WAAA,8MAAkC,SAAS;EACrF,GAAI;CACL,CAAA;AAEL,CAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
declare
|
|
3
|
-
declare
|
|
4
|
-
declare
|
|
5
|
-
declare
|
|
2
|
+
declare const Breadcrumb: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
|
|
3
|
+
declare const BreadcrumbList: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>, "ref"> & React.RefAttributes<HTMLOListElement>>;
|
|
4
|
+
declare const BreadcrumbItem: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & React.RefAttributes<HTMLLIElement>>;
|
|
5
|
+
declare const BreadcrumbLink: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLAnchorElement> & React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
6
6
|
asChild?: boolean;
|
|
7
|
-
}
|
|
8
|
-
declare
|
|
9
|
-
declare
|
|
10
|
-
declare
|
|
7
|
+
}, "ref"> & React.RefAttributes<HTMLAnchorElement>>;
|
|
8
|
+
declare const BreadcrumbPage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
9
|
+
declare const BreadcrumbSeparator: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & React.RefAttributes<HTMLLIElement>>;
|
|
10
|
+
declare const BreadcrumbEllipsis: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
11
11
|
export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, };
|
|
@@ -1,41 +1,46 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { cn, focusRingClasses } from "../lib/utils.js";
|
|
3
3
|
import { Root as Slot } from "../primitives/vendor/radix/react-slot/slot.js";
|
|
4
|
-
import "react";
|
|
4
|
+
import * as React from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import ChevronRight from "@burtson-labs/icons/react/chevron-right";
|
|
7
7
|
import MoreHorizontal from "@burtson-labs/icons/react/more-horizontal";
|
|
8
8
|
//#region src/components/breadcrumb.tsx
|
|
9
|
-
function Breadcrumb(props) {
|
|
9
|
+
var Breadcrumb = React.forwardRef(function Breadcrumb(props, ref) {
|
|
10
10
|
return /* @__PURE__ */ jsx("nav", {
|
|
11
|
+
ref,
|
|
11
12
|
"aria-label": "Breadcrumb",
|
|
12
13
|
"data-slot": "breadcrumb",
|
|
13
14
|
...props
|
|
14
15
|
});
|
|
15
|
-
}
|
|
16
|
-
function BreadcrumbList({ className, ...props }) {
|
|
16
|
+
});
|
|
17
|
+
var BreadcrumbList = React.forwardRef(function BreadcrumbList({ className, ...props }, ref) {
|
|
17
18
|
return /* @__PURE__ */ jsx("ol", {
|
|
19
|
+
ref,
|
|
18
20
|
"data-slot": "breadcrumb-list",
|
|
19
21
|
className: cn("flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground", className),
|
|
20
22
|
...props
|
|
21
23
|
});
|
|
22
|
-
}
|
|
23
|
-
function BreadcrumbItem({ className, ...props }) {
|
|
24
|
+
});
|
|
25
|
+
var BreadcrumbItem = React.forwardRef(function BreadcrumbItem({ className, ...props }, ref) {
|
|
24
26
|
return /* @__PURE__ */ jsx("li", {
|
|
27
|
+
ref,
|
|
25
28
|
"data-slot": "breadcrumb-item",
|
|
26
29
|
className: cn("inline-flex items-center gap-1.5", className),
|
|
27
30
|
...props
|
|
28
31
|
});
|
|
29
|
-
}
|
|
30
|
-
function BreadcrumbLink({ asChild, className, ...props }) {
|
|
32
|
+
});
|
|
33
|
+
var BreadcrumbLink = React.forwardRef(function BreadcrumbLink({ asChild, className, ...props }, ref) {
|
|
31
34
|
return /* @__PURE__ */ jsx(asChild ? Slot : "a", {
|
|
35
|
+
ref,
|
|
32
36
|
"data-slot": "breadcrumb-link",
|
|
33
|
-
className: cn("transition-colors hover:text-foreground", className),
|
|
37
|
+
className: cn("rounded-xs transition-colors hover:text-foreground", focusRingClasses, className),
|
|
34
38
|
...props
|
|
35
39
|
});
|
|
36
|
-
}
|
|
37
|
-
function BreadcrumbPage({ className, ...props }) {
|
|
40
|
+
});
|
|
41
|
+
var BreadcrumbPage = React.forwardRef(function BreadcrumbPage({ className, ...props }, ref) {
|
|
38
42
|
return /* @__PURE__ */ jsx("span", {
|
|
43
|
+
ref,
|
|
39
44
|
"data-slot": "breadcrumb-page",
|
|
40
45
|
role: "link",
|
|
41
46
|
"aria-disabled": "true",
|
|
@@ -43,9 +48,10 @@ function BreadcrumbPage({ className, ...props }) {
|
|
|
43
48
|
className: cn("font-medium text-foreground", className),
|
|
44
49
|
...props
|
|
45
50
|
});
|
|
46
|
-
}
|
|
47
|
-
function BreadcrumbSeparator({ children, className, ...props }) {
|
|
51
|
+
});
|
|
52
|
+
var BreadcrumbSeparator = React.forwardRef(function BreadcrumbSeparator({ children, className, ...props }, ref) {
|
|
48
53
|
return /* @__PURE__ */ jsx("li", {
|
|
54
|
+
ref,
|
|
49
55
|
"data-slot": "breadcrumb-separator",
|
|
50
56
|
role: "presentation",
|
|
51
57
|
"aria-hidden": "true",
|
|
@@ -53,9 +59,10 @@ function BreadcrumbSeparator({ children, className, ...props }) {
|
|
|
53
59
|
...props,
|
|
54
60
|
children: children ?? /* @__PURE__ */ jsx(ChevronRight, {})
|
|
55
61
|
});
|
|
56
|
-
}
|
|
57
|
-
function BreadcrumbEllipsis({ className, ...props }) {
|
|
62
|
+
});
|
|
63
|
+
var BreadcrumbEllipsis = React.forwardRef(function BreadcrumbEllipsis({ className, ...props }, ref) {
|
|
58
64
|
return /* @__PURE__ */ jsxs("span", {
|
|
65
|
+
ref,
|
|
59
66
|
"data-slot": "breadcrumb-ellipsis",
|
|
60
67
|
role: "presentation",
|
|
61
68
|
"aria-hidden": "true",
|
|
@@ -66,7 +73,7 @@ function BreadcrumbEllipsis({ className, ...props }) {
|
|
|
66
73
|
children: "More"
|
|
67
74
|
})]
|
|
68
75
|
});
|
|
69
|
-
}
|
|
76
|
+
});
|
|
70
77
|
//#endregion
|
|
71
78
|
export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator };
|
|
72
79
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","names":["Slot.Root"],"sources":["../../src/components/breadcrumb.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","names":["Slot.Root"],"sources":["../../src/components/breadcrumb.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst Breadcrumb = React.forwardRef<HTMLElement, React.ComponentProps<'nav'>>(\n function Breadcrumb(props, ref) {\n return <nav ref={ref} aria-label=\"Breadcrumb\" data-slot=\"breadcrumb\" {...props} />;\n },\n);\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentProps<'ol'>>(\n function BreadcrumbList({ className, ...props }, ref) {\n return (\n <ol\n ref={ref}\n data-slot=\"breadcrumb-list\"\n className={cn(\n 'flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(\n function BreadcrumbItem({ className, ...props }, ref) {\n return (\n <li\n ref={ref}\n data-slot=\"breadcrumb-item\"\n className={cn('inline-flex items-center gap-1.5', className)}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & { asChild?: boolean }\n>(function BreadcrumbLink({ asChild, className, ...props }, ref) {\n const Comp = asChild ? Slot.Root : 'a';\n return (\n <Comp\n ref={ref}\n data-slot=\"breadcrumb-link\"\n className={cn(\n 'rounded-xs transition-colors hover:text-foreground',\n focusRingClasses,\n className,\n )}\n {...props}\n />\n );\n});\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentProps<'span'>>(\n function BreadcrumbPage({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n data-slot=\"breadcrumb-page\"\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('font-medium text-foreground', className)}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbSeparator = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(\n function BreadcrumbSeparator({ children, className, ...props }, ref) {\n return (\n <li\n ref={ref}\n data-slot=\"breadcrumb-separator\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('[&>svg]:size-3.5', className)}\n {...props}\n >\n {children ?? <ChevronRight />}\n </li>\n );\n },\n);\n\nconst BreadcrumbEllipsis = React.forwardRef<HTMLSpanElement, React.ComponentProps<'span'>>(\n function BreadcrumbEllipsis({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n data-slot=\"breadcrumb-ellipsis\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('flex size-9 items-center justify-center', className)}\n {...props}\n >\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">More</span>\n </span>\n );\n },\n);\n\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n};\n"],"mappings":";;;;;;;;AAOA,IAAM,aAAa,MAAM,WACvB,SAAS,WAAW,OAAO,KAAK;CAC9B,OAAO,oBAAC,OAAD;EAAU;EAAK,cAAW;EAAa,aAAU;EAAa,GAAI;CAAQ,CAAA;AACnF,CACF;AAEA,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,iFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,oCAAoC,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,iBAAiB,MAAM,WAG3B,SAAS,eAAe,EAAE,SAAS,WAAW,GAAG,SAAS,KAAK;CAE/D,OACE,oBAFW,UAAUA,OAAY,KAEjC;EACO;EACL,aAAU;EACV,WAAW,GACT,sDACA,kBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CAAC;AAED,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,QAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,iBAAc;EACd,gBAAa;EACb,WAAW,GAAG,+BAA+B,SAAS;EACtD,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,sBAAsB,MAAM,WAChC,SAAS,oBAAoB,EAAE,UAAU,WAAW,GAAG,SAAS,KAAK;CACnE,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,oBAAoB,SAAS;EAC3C,GAAI;EAEH,UAAA,YAAY,oBAAC,cAAD,CAAe,CAAA;CAC1B,CAAA;AAER,CACF;AAEA,IAAM,qBAAqB,MAAM,WAC/B,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAS,KAAK;CACxD,OACE,qBAAC,QAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;EANN,UAAA,CAQE,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA,GACpC,oBAAC,QAAD;GAAM,WAAU;GAAU,UAAA;EAAU,CAAA,CAChC;;AAEV,CACF"}
|
|
@@ -2,11 +2,18 @@ import { type VariantProps } from 'class-variance-authority';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
4
|
variant?: "secondary" | "destructive" | "brand" | "link" | "default" | "outline" | "soft" | "ghost" | null | undefined;
|
|
5
|
-
size?: "
|
|
5
|
+
size?: "sm" | "lg" | "default" | "icon" | "xs" | "icon-sm" | "icon-lg" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ComponentProps<'button'>, VariantProps<typeof buttonVariants> {
|
|
8
8
|
asChild?: boolean;
|
|
9
9
|
loading?: boolean;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The action primitive. On touch screens every button is at least 44px
|
|
13
|
+
* (`pointer-coarse:min-h-8` opts a button in a dense header out); link-styled
|
|
14
|
+
* buttons stay inline. `data-variant` and `data-size` are set for CSS that
|
|
15
|
+
* needs them. A trigger's `asChild` replaces `data-slot` with its own
|
|
16
|
+
* (`dialog-trigger`), which is why the sizing lives in the classes.
|
|
17
|
+
*/
|
|
11
18
|
declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
12
19
|
export { Button, buttonVariants };
|