@adea-ai/ui 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -6
- package/dist/LICENSE +201 -0
- package/dist/NOTICE +50 -0
- package/dist/components/composites/account-menu/index.js +2 -0
- package/dist/components/composites/list-row/index.js +2 -0
- package/dist/components/composites/settings/index.js +2 -0
- package/dist/components/composites/stat/index.js +2 -0
- package/dist/components/composites/update-dialog/index.js +2 -0
- package/dist/components/composites/workspace-mark/index.js +2 -0
- package/dist/components/conversation/index.js +6 -0
- package/dist/components/layout/app-shell/index.js +2 -0
- package/dist/components/layout/page/index.js +2 -0
- package/dist/components/layout/panel/index.js +2 -0
- package/dist/components/layout/side-rail/index.js +3 -0
- package/dist/components/layout/sidebar-nav/index.js +2 -0
- package/dist/components/layout/status-bar/index.js +2 -0
- package/dist/components/layout/top-bar/index.js +2 -0
- package/dist/components/motion/index.js +2 -2
- package/dist/components/theme/index.js +5 -0
- package/dist/components/ui/accordion/index.js +2 -0
- package/dist/components/ui/alert/index.js +2 -0
- package/dist/components/ui/alert-dialog/index.js +2 -0
- package/dist/components/ui/aspect-ratio/index.js +2 -0
- package/dist/components/ui/avatar/index.js +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/board/index.js +2 -0
- package/dist/components/ui/breadcrumb/index.js +2 -0
- package/dist/components/ui/button/index.js +2 -0
- package/dist/components/ui/button-group/index.js +2 -0
- package/dist/components/ui/calendar/index.js +2 -0
- package/dist/components/ui/card/index.js +2 -0
- package/dist/components/ui/carousel/index.js +2 -0
- package/dist/components/ui/chart/index.js +2 -0
- package/dist/components/ui/checkbox/index.js +2 -0
- package/dist/components/ui/code-block/index.js +2 -0
- package/dist/components/ui/collapsible/index.js +2 -0
- package/dist/components/ui/combobox/index.js +2 -0
- package/dist/components/ui/command/index.js +2 -0
- package/dist/components/ui/context-menu/index.js +2 -0
- package/dist/components/ui/detail-panel/index.js +2 -0
- package/dist/components/ui/dialog/index.js +2 -0
- package/dist/components/ui/diff-block/index.js +2 -0
- package/dist/components/ui/drawer/index.js +2 -0
- package/dist/components/ui/dropdown-menu/index.js +2 -0
- package/dist/components/ui/empty/index.js +2 -0
- package/dist/components/ui/entity-icon/index.js +2 -0
- package/dist/components/ui/field/index.js +2 -0
- package/dist/components/ui/hover-card/index.js +2 -0
- package/dist/components/ui/input/index.js +2 -0
- package/dist/components/ui/input-group/index.js +2 -0
- package/dist/components/ui/input-otp/index.js +2 -0
- package/dist/components/ui/item/index.js +2 -0
- package/dist/components/ui/kbd/index.js +2 -0
- package/dist/components/ui/label/index.js +2 -0
- package/dist/components/ui/menubar/index.js +2 -0
- package/dist/components/ui/modal-dialog/index.js +2 -0
- package/dist/components/ui/navigation-menu/index.js +2 -0
- package/dist/components/ui/pagination/index.js +2 -0
- package/dist/components/ui/popover/index.js +2 -0
- package/dist/components/ui/progress/index.js +2 -0
- package/dist/components/ui/radio-group/index.js +2 -0
- package/dist/components/ui/resizable/index.js +2 -0
- package/dist/components/ui/scroll-area/index.js +2 -0
- package/dist/components/ui/select/index.js +2 -0
- package/dist/components/ui/separator/index.js +2 -0
- package/dist/components/ui/sheet/index.js +2 -0
- package/dist/components/ui/skeleton/index.js +2 -0
- package/dist/components/ui/slider/index.js +2 -0
- package/dist/components/ui/spinner/index.js +2 -0
- package/dist/components/ui/status-chip/index.js +2 -0
- package/dist/components/ui/switch/index.js +2 -0
- package/dist/components/ui/table/index.js +2 -0
- package/dist/components/ui/tabs/index.js +2 -0
- package/dist/components/ui/textarea/index.js +2 -0
- package/dist/components/ui/toast/index.js +2 -0
- package/dist/components/ui/toggle/index.js +2 -0
- package/dist/components/ui/toggle-group/index.js +2 -0
- package/dist/components/ui/tooltip/index.js +2 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/r/accordion.json +3 -1
- package/dist/r/account-menu.json +2 -1
- package/dist/r/alert-dialog.json +3 -1
- package/dist/r/alert.json +3 -1
- package/dist/r/app-shell.json +3 -1
- package/dist/r/aspect-ratio.json +3 -1
- package/dist/r/avatar.json +3 -1
- package/dist/r/badge.json +3 -1
- package/dist/r/board.json +3 -1
- package/dist/r/breadcrumb.json +3 -1
- package/dist/r/button-group.json +2 -1
- package/dist/r/button.json +3 -1
- package/dist/r/calendar.json +2 -1
- package/dist/r/card.json +3 -1
- package/dist/r/carousel.json +2 -1
- package/dist/r/chart.json +3 -1
- package/dist/r/checkbox.json +3 -1
- package/dist/r/code-block.json +2 -1
- package/dist/r/collapsible.json +3 -1
- package/dist/r/combobox.json +3 -1
- package/dist/r/command.json +3 -1
- package/dist/r/context-menu.json +3 -1
- package/dist/r/conversation.json +2 -1
- package/dist/r/detail-panel.json +2 -1
- package/dist/r/dialog.json +3 -1
- package/dist/r/diff-block.json +3 -1
- package/dist/r/drawer.json +3 -1
- package/dist/r/dropdown-menu.json +3 -1
- package/dist/r/empty.json +3 -1
- package/dist/r/entity-icon.json +3 -1
- package/dist/r/field.json +3 -1
- package/dist/r/hover-card.json +3 -1
- package/dist/r/input-group.json +3 -1
- package/dist/r/input-otp.json +3 -1
- package/dist/r/input.json +3 -1
- package/dist/r/item.json +3 -1
- package/dist/r/kbd.json +3 -1
- package/dist/r/label.json +3 -1
- package/dist/r/lib.json +50 -0
- package/dist/r/list-row.json +3 -1
- package/dist/r/menubar.json +3 -1
- package/dist/r/modal-dialog.json +2 -1
- package/dist/r/motion.json +22 -0
- package/dist/r/navigation-menu.json +3 -1
- package/dist/r/page.json +3 -1
- package/dist/r/pagination.json +3 -1
- package/dist/r/panel.json +3 -1
- package/dist/r/popover.json +3 -1
- package/dist/r/progress.json +3 -1
- package/dist/r/radio-group.json +3 -1
- package/dist/r/registry.json +320 -73
- package/dist/r/resizable.json +3 -1
- package/dist/r/scroll-area.json +3 -1
- package/dist/r/select.json +3 -1
- package/dist/r/separator.json +3 -1
- package/dist/r/settings.json +3 -1
- package/dist/r/sheet.json +2 -1
- package/dist/r/side-rail.json +3 -1
- package/dist/r/sidebar-nav.json +3 -1
- package/dist/r/skeleton.json +3 -1
- package/dist/r/slider.json +3 -1
- package/dist/r/spinner.json +3 -1
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +150 -0
- package/dist/r/src/components/composites/account-menu/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/list-row.tsx +116 -0
- package/dist/r/src/components/composites/settings/index.ts +1 -0
- package/dist/r/src/components/composites/settings/settings.tsx +142 -0
- package/dist/r/src/components/composites/stat/index.ts +9 -0
- package/dist/r/src/components/composites/stat/stat.tsx +155 -0
- package/dist/r/src/components/composites/update-dialog/index.ts +7 -0
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +529 -0
- package/dist/r/src/components/composites/workspace-mark/index.ts +1 -0
- package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +72 -0
- package/dist/r/src/components/conversation/conversation-avatar.tsx +60 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +107 -0
- package/dist/r/src/components/conversation/index.ts +25 -0
- package/dist/r/src/components/conversation/message-composer.tsx +239 -0
- package/dist/r/src/components/conversation/message-row.tsx +387 -0
- package/dist/r/src/components/conversation/thread-panel.tsx +141 -0
- package/dist/r/src/components/layout/app-shell/app-shell.tsx +92 -0
- package/dist/r/src/components/layout/app-shell/index.ts +1 -0
- package/dist/r/src/components/layout/page/index.ts +9 -0
- package/dist/r/src/components/layout/page/page.tsx +122 -0
- package/dist/r/src/components/layout/panel/index.ts +12 -0
- package/dist/r/src/components/layout/panel/panel.tsx +166 -0
- package/dist/r/src/components/layout/side-rail/index.ts +12 -0
- package/dist/r/src/components/layout/side-rail/side-rail-context.ts +42 -0
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +405 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +14 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +244 -0
- package/dist/r/src/components/layout/status-bar/index.ts +1 -0
- package/dist/r/src/components/layout/status-bar/status-bar.tsx +73 -0
- package/dist/r/src/components/layout/top-bar/index.ts +10 -0
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +166 -0
- package/dist/r/src/components/motion/index.ts +2 -0
- package/dist/r/src/components/motion/presence.tsx +32 -0
- package/dist/r/src/components/theme/appearance-panel.tsx +179 -0
- package/dist/r/src/components/theme/index.ts +22 -0
- package/dist/r/src/components/theme/theme-picker.tsx +174 -0
- package/dist/r/src/components/theme/theme-provider.tsx +309 -0
- package/dist/r/src/components/theme/theme-toggle.tsx +64 -0
- package/dist/r/src/components/ui/accordion/accordion.tsx +66 -0
- package/dist/r/src/components/ui/accordion/index.ts +1 -0
- package/dist/r/src/components/ui/alert/alert.tsx +95 -0
- package/dist/r/src/components/ui/alert/index.ts +1 -0
- package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +176 -0
- package/dist/r/src/components/ui/alert-dialog/index.ts +13 -0
- package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +38 -0
- package/dist/r/src/components/ui/aspect-ratio/index.ts +1 -0
- package/dist/r/src/components/ui/avatar/avatar.tsx +114 -0
- package/dist/r/src/components/ui/avatar/index.ts +9 -0
- package/dist/r/src/components/ui/badge/badge.tsx +59 -0
- package/dist/r/src/components/ui/badge/index.ts +1 -0
- package/dist/r/src/components/ui/board/board.tsx +254 -0
- package/dist/r/src/components/ui/board/index.ts +10 -0
- package/dist/r/src/components/ui/breadcrumb/breadcrumb.tsx +117 -0
- package/dist/r/src/components/ui/breadcrumb/index.ts +9 -0
- package/dist/r/src/components/ui/button/button.tsx +89 -0
- package/dist/r/src/components/ui/button/index.ts +1 -0
- package/dist/r/src/components/ui/button-group/button-group.tsx +92 -0
- package/dist/r/src/components/ui/button-group/index.ts +6 -0
- package/dist/r/src/components/ui/calendar/calendar.tsx +277 -0
- package/dist/r/src/components/ui/calendar/index.ts +1 -0
- package/dist/r/src/components/ui/card/card.tsx +82 -0
- package/dist/r/src/components/ui/card/index.ts +10 -0
- package/dist/r/src/components/ui/carousel/carousel.tsx +209 -0
- package/dist/r/src/components/ui/carousel/index.ts +1 -0
- package/dist/r/src/components/ui/chart/chart.tsx +413 -0
- package/dist/r/src/components/ui/chart/index.ts +17 -0
- package/dist/r/src/components/ui/checkbox/checkbox.tsx +99 -0
- package/dist/r/src/components/ui/checkbox/index.ts +1 -0
- package/dist/r/src/components/ui/code-block/code-block.tsx +238 -0
- package/dist/r/src/components/ui/code-block/index.ts +1 -0
- package/dist/r/src/components/ui/collapsible/collapsible.tsx +54 -0
- package/dist/r/src/components/ui/collapsible/index.ts +1 -0
- package/dist/r/src/components/ui/combobox/combobox.tsx +142 -0
- package/dist/r/src/components/ui/combobox/index.ts +10 -0
- package/dist/r/src/components/ui/command/command.tsx +214 -0
- package/dist/r/src/components/ui/command/index.ts +12 -0
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +163 -0
- package/dist/r/src/components/ui/context-menu/index.ts +15 -0
- package/dist/r/src/components/ui/detail-panel/detail-panel.tsx +161 -0
- package/dist/r/src/components/ui/detail-panel/index.ts +9 -0
- package/dist/r/src/components/ui/dialog/dialog.tsx +138 -0
- package/dist/r/src/components/ui/dialog/index.ts +14 -0
- package/dist/r/src/components/ui/diff-block/diff-block.tsx +286 -0
- package/dist/r/src/components/ui/diff-block/index.ts +11 -0
- package/dist/r/src/components/ui/drawer/drawer.tsx +144 -0
- package/dist/r/src/components/ui/drawer/index.ts +13 -0
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +203 -0
- package/dist/r/src/components/ui/dropdown-menu/index.ts +18 -0
- package/dist/r/src/components/ui/empty/empty.tsx +104 -0
- package/dist/r/src/components/ui/empty/index.ts +10 -0
- package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +154 -0
- package/dist/r/src/components/ui/entity-icon/index.ts +1 -0
- package/dist/r/src/components/ui/field/field.tsx +131 -0
- package/dist/r/src/components/ui/field/index.ts +11 -0
- package/dist/r/src/components/ui/hover-card/hover-card.tsx +48 -0
- package/dist/r/src/components/ui/hover-card/index.ts +1 -0
- package/dist/r/src/components/ui/input/index.ts +1 -0
- package/dist/r/src/components/ui/input/input.tsx +41 -0
- package/dist/r/src/components/ui/input-group/index.ts +8 -0
- package/dist/r/src/components/ui/input-group/input-group.tsx +111 -0
- package/dist/r/src/components/ui/input-otp/index.ts +1 -0
- package/dist/r/src/components/ui/input-otp/input-otp.tsx +108 -0
- package/dist/r/src/components/ui/item/index.ts +10 -0
- package/dist/r/src/components/ui/item/item.tsx +146 -0
- package/dist/r/src/components/ui/kbd/index.ts +1 -0
- package/dist/r/src/components/ui/kbd/kbd.tsx +42 -0
- package/dist/r/src/components/ui/label/index.ts +1 -0
- package/dist/r/src/components/ui/label/label.tsx +36 -0
- package/dist/r/src/components/ui/menubar/index.ts +16 -0
- package/dist/r/src/components/ui/menubar/menubar.tsx +190 -0
- package/dist/r/src/components/ui/modal-dialog/index.ts +1 -0
- package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +95 -0
- package/dist/r/src/components/ui/navigation-menu/index.ts +13 -0
- package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +229 -0
- package/dist/r/src/components/ui/pagination/index.ts +8 -0
- package/dist/r/src/components/ui/pagination/pagination.tsx +144 -0
- package/dist/r/src/components/ui/popover/index.ts +10 -0
- package/dist/r/src/components/ui/popover/popover.tsx +81 -0
- package/dist/r/src/components/ui/progress/index.ts +1 -0
- package/dist/r/src/components/ui/progress/progress.tsx +67 -0
- package/dist/r/src/components/ui/radio-group/index.ts +1 -0
- package/dist/r/src/components/ui/radio-group/radio-group.tsx +78 -0
- package/dist/r/src/components/ui/resizable/index.ts +6 -0
- package/dist/r/src/components/ui/resizable/resizable.tsx +74 -0
- package/dist/r/src/components/ui/scroll-area/index.ts +1 -0
- package/dist/r/src/components/ui/scroll-area/scroll-area.tsx +66 -0
- package/dist/r/src/components/ui/select/index.ts +11 -0
- package/dist/r/src/components/ui/select/select.tsx +188 -0
- package/dist/r/src/components/ui/separator/index.ts +1 -0
- package/dist/r/src/components/ui/separator/separator.tsx +40 -0
- package/dist/r/src/components/ui/sheet/index.ts +13 -0
- package/dist/r/src/components/ui/sheet/sheet.tsx +132 -0
- package/dist/r/src/components/ui/skeleton/index.ts +1 -0
- package/dist/r/src/components/ui/skeleton/skeleton.tsx +28 -0
- package/dist/r/src/components/ui/slider/index.ts +1 -0
- package/dist/r/src/components/ui/slider/slider.tsx +81 -0
- package/dist/r/src/components/ui/spinner/index.ts +1 -0
- package/dist/r/src/components/ui/spinner/spinner.tsx +58 -0
- package/dist/r/src/components/ui/status-chip/index.ts +8 -0
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +142 -0
- package/dist/r/src/components/ui/switch/index.ts +1 -0
- package/dist/r/src/components/ui/switch/switch.tsx +100 -0
- package/dist/r/src/components/ui/table/index.ts +11 -0
- package/dist/r/src/components/ui/table/table.tsx +125 -0
- package/dist/r/src/components/ui/tabs/index.ts +8 -0
- package/dist/r/src/components/ui/tabs/tabs.tsx +77 -0
- package/dist/r/src/components/ui/textarea/index.ts +1 -0
- package/dist/r/src/components/ui/textarea/textarea.tsx +36 -0
- package/dist/r/src/components/ui/toast/index.ts +1 -0
- package/dist/r/src/components/ui/toast/toast.tsx +220 -0
- package/dist/r/src/components/ui/toggle/index.ts +1 -0
- package/dist/r/src/components/ui/toggle/toggle.tsx +60 -0
- package/dist/r/src/components/ui/toggle-group/index.ts +6 -0
- package/dist/r/src/components/ui/toggle-group/toggle-group.tsx +66 -0
- package/dist/r/src/components/ui/tooltip/index.ts +9 -0
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +102 -0
- package/dist/r/src/lib/keyed-rows.ts +68 -0
- package/dist/r/src/lib/overlay.ts +70 -0
- package/dist/r/src/lib/themes.ts +490 -0
- package/dist/r/src/lib/tokens.ts +688 -0
- package/dist/r/src/lib/utils.ts +76 -0
- package/dist/r/src/lib/variants.ts +62 -0
- package/dist/r/src/lib/version-notes.ts +65 -0
- package/dist/r/src/styles/base.css +644 -0
- package/dist/r/src/styles/fonts.css +26 -0
- package/dist/r/src/styles/globals.css +18 -0
- package/dist/r/src/styles/theme.css +864 -0
- package/dist/r/stat.json +3 -1
- package/dist/r/status-bar.json +3 -1
- package/dist/r/status-chip.json +2 -1
- package/dist/r/switch.json +3 -1
- package/dist/r/table.json +3 -1
- package/dist/r/tabs.json +3 -1
- package/dist/r/textarea.json +3 -1
- package/dist/r/theming.json +42 -0
- package/dist/r/toast.json +3 -1
- package/dist/r/toggle-group.json +2 -1
- package/dist/r/toggle.json +3 -1
- package/dist/r/tooltip.json +3 -1
- package/dist/r/top-bar.json +3 -1
- package/dist/r/update-dialog.json +2 -1
- package/dist/r/workspace-mark.json +2 -1
- package/package.json +10 -9
- package/registry.json +320 -73
- package/src/index.ts +2 -4
- package/dist/components/ui/diff-block/diff-block.test.d.ts +0 -2
- package/dist/components/ui/diff-block/diff-block.test.d.ts.map +0 -1
- package/dist/lib/keyed-rows.test.d.ts +0 -2
- package/dist/lib/keyed-rows.test.d.ts.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
2
|
+
import { Show, splitProps } from 'solid-js'
|
|
3
|
+
import { cn } from '../../../lib/utils'
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip/tooltip'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WorkspaceMark.
|
|
8
|
+
*
|
|
9
|
+
* The mark for one workspace in a rail: an initial or a glyph, a ring while it is
|
|
10
|
+
* active, and a badge for something unread. It is deliberately not an `Avatar` —
|
|
11
|
+
* a workspace is not a person, it does not need an image or initials derived from a
|
|
12
|
+
* name, and a rail of them needs an *active* treatment an avatar does not have.
|
|
13
|
+
*
|
|
14
|
+
* The mark is square with a rounded corner rather than round, which is what
|
|
15
|
+
* distinguishes a workspace from a person at a glance in a rail that holds both.
|
|
16
|
+
*/
|
|
17
|
+
export type WorkspaceMarkProps = ComponentProps<'button'> & {
|
|
18
|
+
/** The workspace's name. Used for the label, the initial, and the tooltip. */
|
|
19
|
+
name: string
|
|
20
|
+
/** Draw the active ring. */
|
|
21
|
+
active?: boolean
|
|
22
|
+
/** A count or a dot, drawn at the corner. */
|
|
23
|
+
badge?: JSX.Element
|
|
24
|
+
/** Replace the initial with a glyph. */
|
|
25
|
+
children?: JSX.Element
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function WorkspaceMark(props: WorkspaceMarkProps) {
|
|
29
|
+
const [local, rest] = splitProps(props, ['class', 'name', 'active', 'badge', 'children'])
|
|
30
|
+
|
|
31
|
+
const mark = (
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
aria-label={local.name}
|
|
35
|
+
aria-current={local.active ? 'true' : undefined}
|
|
36
|
+
data-active={local.active ? '' : undefined}
|
|
37
|
+
class={cn(
|
|
38
|
+
'relative flex size-9 shrink-0 items-center justify-center rounded-lg border text-sm font-semibold',
|
|
39
|
+
'transition-[color,background-color,border-color,transform] ease-out outline-none select-none',
|
|
40
|
+
'hover:scale-105 active:scale-95',
|
|
41
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
|
|
42
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
43
|
+
local.active
|
|
44
|
+
? 'border-primary bg-primary-subtle text-primary'
|
|
45
|
+
: 'border-border bg-card text-muted-foreground hover:text-foreground',
|
|
46
|
+
local.class
|
|
47
|
+
)}
|
|
48
|
+
{...rest}
|
|
49
|
+
>
|
|
50
|
+
<Show
|
|
51
|
+
when={local.children}
|
|
52
|
+
fallback={<span aria-hidden="true">{local.name.charAt(0).toUpperCase()}</span>}
|
|
53
|
+
>
|
|
54
|
+
{local.children}
|
|
55
|
+
</Show>
|
|
56
|
+
<Show when={local.badge}>
|
|
57
|
+
<span class="absolute -end-0.5 -top-0.5 flex items-center justify-center">
|
|
58
|
+
{local.badge}
|
|
59
|
+
</span>
|
|
60
|
+
</Show>
|
|
61
|
+
</button>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Tooltip placement="right">
|
|
66
|
+
<TooltipTrigger as="span" class="contents">
|
|
67
|
+
{mark}
|
|
68
|
+
</TooltipTrigger>
|
|
69
|
+
<TooltipContent>{local.name}</TooltipContent>
|
|
70
|
+
</Tooltip>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComponentProps } from 'solid-js'
|
|
2
|
+
import { Show, splitProps } from 'solid-js'
|
|
3
|
+
import { Dynamic } from 'solid-js/web'
|
|
4
|
+
import { Bot, Sparkles, User } from 'lucide-solid'
|
|
5
|
+
import { cn } from '../../lib/utils'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ConversationAvatar.
|
|
9
|
+
*
|
|
10
|
+
* The speaker's mark in a transcript. Three kinds, and the distinction matters
|
|
11
|
+
* visually: a person, an agent, and the system itself are different *voices*, and a
|
|
12
|
+
* transcript where all three look alike is one the reader has to decode line by
|
|
13
|
+
* line.
|
|
14
|
+
*
|
|
15
|
+
* The avatar is decorative — the speaker's name is in the message's accessible name
|
|
16
|
+
* — so it is `aria-hidden` and carries no text of its own. A caller with a real
|
|
17
|
+
* image passes it as children.
|
|
18
|
+
*/
|
|
19
|
+
export type ConversationAvatarKind = 'user' | 'agent' | 'system'
|
|
20
|
+
|
|
21
|
+
export type ConversationAvatarProps = ComponentProps<'span'> & {
|
|
22
|
+
kind: ConversationAvatarKind
|
|
23
|
+
/** Draw the mark larger, for the sender's own messages. */
|
|
24
|
+
size?: 'md' | 'lg'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const GLYPHS = { user: User, agent: Bot, system: Sparkles } as const
|
|
28
|
+
|
|
29
|
+
export function ConversationAvatar(props: ConversationAvatarProps) {
|
|
30
|
+
const [local, rest] = splitProps(props, ['class', 'kind', 'size', 'children'])
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<span
|
|
34
|
+
data-slot="conversation-avatar"
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
class={cn(
|
|
37
|
+
'grid shrink-0 place-items-center rounded-full border border-border',
|
|
38
|
+
local.size === 'lg' ? 'size-8' : 'size-7',
|
|
39
|
+
{
|
|
40
|
+
'bg-primary-subtle text-primary': local.kind === 'user',
|
|
41
|
+
'bg-card text-foreground': local.kind === 'agent',
|
|
42
|
+
'bg-muted text-muted-foreground': local.kind === 'system',
|
|
43
|
+
},
|
|
44
|
+
local.class
|
|
45
|
+
)}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
<Show
|
|
49
|
+
when={local.children}
|
|
50
|
+
fallback={
|
|
51
|
+
<span class="grid size-4 place-items-center [&_svg]:size-4">
|
|
52
|
+
<Dynamic component={GLYPHS[local.kind]} />
|
|
53
|
+
</span>
|
|
54
|
+
}
|
|
55
|
+
>
|
|
56
|
+
{local.children}
|
|
57
|
+
</Show>
|
|
58
|
+
</span>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ArrowDown } from 'lucide-solid'
|
|
2
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
3
|
+
import { Show, createSignal, onCleanup, splitProps } from 'solid-js'
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
import { Button } from '../ui/button/button'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ConversationSurface.
|
|
9
|
+
*
|
|
10
|
+
* The scrolling transcript. Its job is small and it is the difference between a
|
|
11
|
+
* transcript that stays readable as it grows and one that fights the reader:
|
|
12
|
+
*
|
|
13
|
+
* - **Stick to the bottom.** New messages scroll into view only when the reader is
|
|
14
|
+
* already at the bottom. Someone reading back through history must not be yanked
|
|
15
|
+
* to the end by an arriving message, and that is the single most common way a
|
|
16
|
+
* chat surface is made unusable.
|
|
17
|
+
* - **Offer a way back.** Once the reader is not at the bottom, a control appears
|
|
18
|
+
* that returns them and says how much they have missed.
|
|
19
|
+
* - **`overscroll-contain`.** The transcript scrolls to its end and stops, rather
|
|
20
|
+
* than dragging the pane behind it — which in a desktop shell means the whole
|
|
21
|
+
* window moves.
|
|
22
|
+
*/
|
|
23
|
+
export type ConversationSurfaceProps = ComponentProps<'div'> & {
|
|
24
|
+
/** How far from the bottom still counts as "at the bottom", in px. */
|
|
25
|
+
threshold?: number
|
|
26
|
+
/** Rendered above the transcript when there is nothing in it. */
|
|
27
|
+
empty?: JSX.Element
|
|
28
|
+
/** A header that scrolls with the transcript, e.g. a day divider or a banner. */
|
|
29
|
+
header?: JSX.Element
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** How far the reader is from the end of the transcript, in px. */
|
|
33
|
+
function distanceFromBottom(element: HTMLDivElement): number {
|
|
34
|
+
return element.scrollHeight - element.scrollTop - element.clientHeight
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ConversationSurface(props: ConversationSurfaceProps) {
|
|
38
|
+
const [local, rest] = splitProps(props, ['class', 'threshold', 'empty', 'header', 'children'])
|
|
39
|
+
|
|
40
|
+
const [atBottom, setAtBottom] = createSignal(true)
|
|
41
|
+
const [missed, setMissed] = createSignal(0)
|
|
42
|
+
let scroller: HTMLDivElement | undefined
|
|
43
|
+
|
|
44
|
+
const onScroll = () => {
|
|
45
|
+
if (!scroller) return
|
|
46
|
+
const distance = distanceFromBottom(scroller)
|
|
47
|
+
const threshold = local.threshold ?? 80
|
|
48
|
+
setAtBottom(distance <= threshold)
|
|
49
|
+
setMissed(distance <= threshold ? 0 : Math.round(distance / 96))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const jumpToLatest = () => {
|
|
53
|
+
scroller?.scrollTo({ top: scroller.scrollHeight, behavior: 'smooth' })
|
|
54
|
+
setAtBottom(true)
|
|
55
|
+
setMissed(0)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// An arriving message while the reader is at the bottom should follow it. While
|
|
59
|
+
// they are not, it must not.
|
|
60
|
+
const observer = () => {
|
|
61
|
+
if (!scroller) return
|
|
62
|
+
const mutation = new MutationObserver(() => {
|
|
63
|
+
if (atBottom()) scroller?.scrollTo({ top: scroller.scrollHeight })
|
|
64
|
+
else onScroll()
|
|
65
|
+
})
|
|
66
|
+
mutation.observe(scroller, { childList: true, subtree: true })
|
|
67
|
+
onCleanup(() => mutation.disconnect())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="conversation-surface"
|
|
73
|
+
class={cn('relative flex min-h-0 flex-1 flex-col', local.class)}
|
|
74
|
+
>
|
|
75
|
+
<div
|
|
76
|
+
ref={(element) => {
|
|
77
|
+
scroller = element
|
|
78
|
+
observer()
|
|
79
|
+
}}
|
|
80
|
+
onScroll={onScroll}
|
|
81
|
+
class="min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
82
|
+
{...rest}
|
|
83
|
+
>
|
|
84
|
+
<Show when={local.header}>{local.header}</Show>
|
|
85
|
+
<Show when={local.children} fallback={<div class="p-4">{local.empty}</div>}>
|
|
86
|
+
{local.children}
|
|
87
|
+
</Show>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<Show when={!atBottom()}>
|
|
91
|
+
<div class="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">
|
|
92
|
+
<Button
|
|
93
|
+
size="sm"
|
|
94
|
+
variant="secondary"
|
|
95
|
+
class="pointer-events-auto gap-1.5 shadow-md"
|
|
96
|
+
onClick={jumpToLatest}
|
|
97
|
+
>
|
|
98
|
+
<ArrowDown />
|
|
99
|
+
<Show when={missed() > 0} fallback={<>Jump to latest</>}>
|
|
100
|
+
{missed()} new
|
|
101
|
+
</Show>
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
</Show>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ConversationAvatar,
|
|
3
|
+
type ConversationAvatarKind,
|
|
4
|
+
type ConversationAvatarProps,
|
|
5
|
+
} from './conversation-avatar'
|
|
6
|
+
export { ConversationSurface, type ConversationSurfaceProps } from './conversation-surface'
|
|
7
|
+
export {
|
|
8
|
+
ComposerAttachmentButton,
|
|
9
|
+
ComposerHint,
|
|
10
|
+
MessageComposer,
|
|
11
|
+
type MessageComposerProps,
|
|
12
|
+
} from './message-composer'
|
|
13
|
+
export {
|
|
14
|
+
MessageBody,
|
|
15
|
+
MessageDayDivider,
|
|
16
|
+
MessageGroup,
|
|
17
|
+
MessageRow,
|
|
18
|
+
type MessageRowProps,
|
|
19
|
+
} from './message-row'
|
|
20
|
+
export {
|
|
21
|
+
AttachmentCard,
|
|
22
|
+
ThreadPanel,
|
|
23
|
+
type AttachmentCardProps,
|
|
24
|
+
type ThreadPanelProps,
|
|
25
|
+
} from './thread-panel'
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { CornerDownLeft, Paperclip, Send, X } from 'lucide-solid'
|
|
2
|
+
import type { ComponentProps, JSX } from 'solid-js'
|
|
3
|
+
import { Show, createSignal, splitProps } from 'solid-js'
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
import { Button } from '../ui/button/button'
|
|
6
|
+
import { Kbd } from '../ui/kbd/kbd'
|
|
7
|
+
import { Spinner } from '../ui/spinner/spinner'
|
|
8
|
+
import { Textarea } from '../ui/textarea/textarea'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MessageComposer.
|
|
12
|
+
*
|
|
13
|
+
* The field a message is written in. It is a form, and it behaves like one: Enter
|
|
14
|
+
* sends, Shift+Enter breaks the line, Escape clears the error.
|
|
15
|
+
*
|
|
16
|
+
* ## What it does not know
|
|
17
|
+
*
|
|
18
|
+
* No mention model, no attachment model, no dictation, no idempotency keys. The
|
|
19
|
+
* first version of this composer was written against adea's agent and artifact
|
|
20
|
+
* types, which meant it could only ever be used by adea. Here the attachment control
|
|
21
|
+
* is a *slot*, the mention menu is a slot, and a caller that has an agent model
|
|
22
|
+
* renders one; a caller that does not gets a composer without it.
|
|
23
|
+
*
|
|
24
|
+
* ## The three things it does own
|
|
25
|
+
*
|
|
26
|
+
* **Enter sends, Shift+Enter breaks the line.** That is the convention every chat
|
|
27
|
+
* application shares, and getting it wrong is the single most irritating thing a
|
|
28
|
+
* composer can do. `⌘`/`Ctrl`+Enter is accepted too, because someone arriving from an
|
|
29
|
+
* editor will try it.
|
|
30
|
+
*
|
|
31
|
+
* **The send button is disabled when there is nothing to send.** A control that
|
|
32
|
+
* accepts a press and does nothing is worse than one that says it cannot.
|
|
33
|
+
*
|
|
34
|
+
* **The status region is `aria-live`.** "Sending…" and a failure both land there, so
|
|
35
|
+
* a screen reader hears the outcome without the composer stealing focus.
|
|
36
|
+
*/
|
|
37
|
+
export type MessageComposerProps = Omit<ComponentProps<'form'>, 'onSubmit'> & {
|
|
38
|
+
/** The draft. Controlled: the caller owns the text, because it usually persists. */
|
|
39
|
+
value: string
|
|
40
|
+
onValueChange: (value: string) => void
|
|
41
|
+
/** Called on send. A rejection is surfaced as an error and the draft is kept. */
|
|
42
|
+
onSubmit: () => void | Promise<void>
|
|
43
|
+
placeholder?: string
|
|
44
|
+
/** Disable the whole composer, e.g. while the channel is read-only. */
|
|
45
|
+
disabled?: boolean
|
|
46
|
+
/** Label the send action for a context other than a message, e.g. a reply. */
|
|
47
|
+
submitLabel?: string
|
|
48
|
+
/** Show the keyboard hint under the field. */
|
|
49
|
+
showHint?: boolean
|
|
50
|
+
/** A leading control group, e.g. an attach button. */
|
|
51
|
+
leading?: JSX.Element
|
|
52
|
+
/** A trailing group before the send button, e.g. a dictation toggle. */
|
|
53
|
+
trailing?: JSX.Element
|
|
54
|
+
/** A menu rendered above the field, e.g. mention suggestions. */
|
|
55
|
+
menu?: JSX.Element
|
|
56
|
+
/** The reply target, drawn as a strip above the field. */
|
|
57
|
+
replyTo?: { label: string; onDismiss: () => void }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function MessageComposer(props: MessageComposerProps) {
|
|
61
|
+
const [local, rest] = splitProps(props, [
|
|
62
|
+
'class',
|
|
63
|
+
'value',
|
|
64
|
+
'onValueChange',
|
|
65
|
+
'onSubmit',
|
|
66
|
+
'placeholder',
|
|
67
|
+
'disabled',
|
|
68
|
+
'submitLabel',
|
|
69
|
+
'showHint',
|
|
70
|
+
'leading',
|
|
71
|
+
'trailing',
|
|
72
|
+
'menu',
|
|
73
|
+
'replyTo',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
const [sending, setSending] = createSignal(false)
|
|
77
|
+
const [error, setError] = createSignal<string | null>(null)
|
|
78
|
+
|
|
79
|
+
const canSend = () => local.value.trim().length > 0 && !local.disabled && !sending()
|
|
80
|
+
|
|
81
|
+
const send = async () => {
|
|
82
|
+
if (!canSend()) return
|
|
83
|
+
setSending(true)
|
|
84
|
+
setError(null)
|
|
85
|
+
try {
|
|
86
|
+
await local.onSubmit()
|
|
87
|
+
} catch {
|
|
88
|
+
// The draft is deliberately left in place. A send that fails and clears the
|
|
89
|
+
// field loses the user's work, which is the worst outcome available here.
|
|
90
|
+
setError('Message not sent. Your draft is still here; retry when the connection recovers.')
|
|
91
|
+
} finally {
|
|
92
|
+
setSending(false)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const onKeyDown: JSX.EventHandlerUnion<HTMLTextAreaElement, KeyboardEvent> = (event) => {
|
|
97
|
+
if (event.key === 'Escape' && error()) {
|
|
98
|
+
setError(null)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (event.key !== 'Enter') return
|
|
102
|
+
// Shift breaks the line; a bare Enter, or a modified one, sends.
|
|
103
|
+
if (event.shiftKey && !event.metaKey && !event.ctrlKey) return
|
|
104
|
+
event.preventDefault()
|
|
105
|
+
void send()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<form
|
|
110
|
+
data-slot="message-composer"
|
|
111
|
+
class={cn('flex flex-col gap-2', local.class)}
|
|
112
|
+
onSubmit={(event) => {
|
|
113
|
+
event.preventDefault()
|
|
114
|
+
void send()
|
|
115
|
+
}}
|
|
116
|
+
{...rest}
|
|
117
|
+
>
|
|
118
|
+
<Show when={local.menu}>
|
|
119
|
+
<div>{local.menu}</div>
|
|
120
|
+
</Show>
|
|
121
|
+
|
|
122
|
+
<Show when={local.replyTo}>
|
|
123
|
+
{(reply) => (
|
|
124
|
+
<div class="bg-muted text-muted-foreground flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs">
|
|
125
|
+
<span class="min-w-0 flex-1 truncate">Replying to {reply().label}</span>
|
|
126
|
+
<Button
|
|
127
|
+
type="button"
|
|
128
|
+
variant="ghost"
|
|
129
|
+
size="icon-2xs"
|
|
130
|
+
aria-label="Cancel reply"
|
|
131
|
+
onClick={() => reply().onDismiss()}
|
|
132
|
+
>
|
|
133
|
+
<X />
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</Show>
|
|
138
|
+
|
|
139
|
+
<div
|
|
140
|
+
class={cn(
|
|
141
|
+
'border-input bg-card flex flex-col gap-1 rounded-lg border p-2',
|
|
142
|
+
'transition-[color,box-shadow,border-color] ease-out',
|
|
143
|
+
'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',
|
|
144
|
+
local.disabled && 'opacity-50'
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<Textarea
|
|
148
|
+
value={local.value}
|
|
149
|
+
onInput={(event) => local.onValueChange(event.currentTarget.value)}
|
|
150
|
+
onKeyDown={onKeyDown}
|
|
151
|
+
placeholder={local.placeholder ?? 'Write a message…'}
|
|
152
|
+
disabled={local.disabled}
|
|
153
|
+
rows={1}
|
|
154
|
+
aria-label={local.submitLabel ? `${local.submitLabel} message` : 'Message'}
|
|
155
|
+
class="min-h-9 resize-none border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0"
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
<div class="flex items-center gap-1">
|
|
159
|
+
<Show when={local.leading}>{local.leading}</Show>
|
|
160
|
+
<div class="ms-auto flex items-center gap-1">
|
|
161
|
+
<Show when={local.trailing}>{local.trailing}</Show>
|
|
162
|
+
<Button
|
|
163
|
+
type="submit"
|
|
164
|
+
size="icon-sm"
|
|
165
|
+
disabled={!canSend()}
|
|
166
|
+
aria-label={sending() ? 'Sending message' : 'Send message'}
|
|
167
|
+
>
|
|
168
|
+
<Show when={sending()} fallback={<Send />}>
|
|
169
|
+
<Spinner size="sm" label={false} class="text-primary-foreground" />
|
|
170
|
+
</Show>
|
|
171
|
+
</Button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<Show when={local.showHint}>
|
|
177
|
+
<p class="text-muted-foreground flex items-center gap-1.5 text-2xs">
|
|
178
|
+
<Kbd>Enter</Kbd> to send
|
|
179
|
+
<span aria-hidden="true">·</span>
|
|
180
|
+
<Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line
|
|
181
|
+
</p>
|
|
182
|
+
</Show>
|
|
183
|
+
|
|
184
|
+
{/* A live region, so the outcome is announced without moving focus out of the
|
|
185
|
+
field the user is typing in. */}
|
|
186
|
+
<div aria-live="polite" class="min-h-0">
|
|
187
|
+
<Show when={error()}>
|
|
188
|
+
{(message) => <p class="text-destructive text-xs">{message()}</p>}
|
|
189
|
+
</Show>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The attachment control.
|
|
197
|
+
*
|
|
198
|
+
* A slot rather than a built-in, because what can be attached is the application's
|
|
199
|
+
* business — but the *button* is shared, so it looks and behaves the same in both.
|
|
200
|
+
* The count is announced, since a paperclip alone does not say whether anything is
|
|
201
|
+
* attached.
|
|
202
|
+
*/
|
|
203
|
+
export function ComposerAttachmentButton(
|
|
204
|
+
props: ComponentProps<typeof Button> & { count?: number; open?: boolean }
|
|
205
|
+
) {
|
|
206
|
+
const [local, rest] = splitProps(props, ['count', 'open', 'children'])
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<Button
|
|
210
|
+
type="button"
|
|
211
|
+
variant="ghost"
|
|
212
|
+
size="icon-sm"
|
|
213
|
+
aria-label={local.count ? `${local.count} attached, add an attachment` : 'Add an attachment'}
|
|
214
|
+
aria-expanded={local.open}
|
|
215
|
+
class="text-muted-foreground"
|
|
216
|
+
{...rest}
|
|
217
|
+
>
|
|
218
|
+
{local.children ?? <Paperclip />}
|
|
219
|
+
</Button>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The hint a composer shows when the field is empty, as a one-line prompt. */
|
|
224
|
+
export function ComposerHint(props: ComponentProps<'p'>) {
|
|
225
|
+
const [local, rest] = splitProps(props, ['class', 'children'])
|
|
226
|
+
return (
|
|
227
|
+
<p
|
|
228
|
+
class={cn('text-muted-foreground flex items-center gap-1.5 text-2xs', local.class)}
|
|
229
|
+
{...rest}
|
|
230
|
+
>
|
|
231
|
+
{local.children ?? (
|
|
232
|
+
<>
|
|
233
|
+
<CornerDownLeft aria-hidden="true" class="size-3" />
|
|
234
|
+
<Kbd>Enter</Kbd> to send
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
</p>
|
|
238
|
+
)
|
|
239
|
+
}
|