@adea-ai/ui 0.101.1 → 0.102.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/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { Heading, Text, headingElement, headingVariants, textElement, textVarian
6
6
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger } from "./components/ui/alert-dialog/alert-dialog.js";
7
7
  import { AspectRatio } from "./components/ui/aspect-ratio/aspect-ratio.js";
8
8
  import { Avatar, AvatarFallback, AvatarImage, initialsFrom } from "./components/ui/avatar/avatar.js";
9
- import { Board, BoardCardBody, BoardCardTitle, boardCardTitleVariants } from "./components/ui/board/board.js";
10
9
  import { Badge, badgeVariants } from "./components/ui/badge/badge.js";
10
+ import { Board, BoardCardBody, BoardCardTitle, BoardCardTrigger, boardCardTitleVariants } from "./components/ui/board/board.js";
11
11
  import { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from "./components/ui/breadcrumb/breadcrumb.js";
12
12
  import { Button, buttonVariants } from "./components/ui/button/button.js";
13
13
  import { Separator } from "./components/ui/separator/separator.js";
@@ -121,4 +121,4 @@ import { AboutDialog } from "./components/composites/about-dialog/about-dialog.j
121
121
  import { HelpCenter } from "./components/composites/help-center/help-center.js";
122
122
  import { WorkspaceMark } from "./components/composites/workspace-mark/workspace-mark.js";
123
123
  import { writeClipboardText } from "./lib/clipboard.js";
124
- export { AboutDialog, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AccountMenu, ActionButton, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellMain, AppearanceEditor, AppearancePanel, AppearancePopover, AspectRatio, AttachmentCard, Avatar, AvatarFallback, AvatarImage, Badge, Board, BoardCardBody, BoardCardTitle, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, BusySendButton, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CalendarSurface, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CatalogBrowser, CatalogDetail, CatalogDetailSection, ChatComposer, Checkbox, CheckboxDescription, CheckboxLabel, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxClear, ComboboxContent, ComboboxControl, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator, CommandShortcut, ComposerAttachmentButton, ComposerHint, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, ConversationAvatar, ConversationSurface, DatePicker, DetailPanel, DetailPanelBody, DetailPanelField, DetailPanelHeader, DetailPanelSection, Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffBlock, DiffSummary, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyState, EmptyTitle, EntityIcon, FLOATING_PREVIEW_CORNER_RADIUS, FLOATING_PREVIEW_EDGE_GAP, Field, FieldDescription, FieldError, FieldGroup, FieldInput, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTextArea, FloatingPreview, FormField, Heading, HelpCenter, HoverCard, HoverCardContent, HoverCardTrigger, InlineCode, Input, InputControl, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOtp, InputOtpCaret, Item, ItemDescription, ItemEmpty, ItemGroup, ItemGroupEntry, ItemTitle, Kbd, KbdGroup, Label, ListGroup, ListRow, ListRowControl, MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ModalDialog, Motion, NO_FLOATING_PREVIEW_OBSTACLES, NO_PREVIEW_MINI_PLAYER_OBSTACLES, NativeSelect, NavigationMenu, NavigationMenuContent, NavigationMenuGroup, NavigationMenuGroupLabel, NavigationMenuItem, NavigationMenuMenu, NavigationMenuSeparator, NavigationMenuTrigger, NavigationMenuViewport, NumberField, OrbitItem, OrbitLayout, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, PREVIEW_MINI_PLAYER_CORNER_RADIUS, PREVIEW_MINI_PLAYER_EDGE_GAP, Page, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderTitle, PageSection, Pagination, PaginationEllipsis, PaginationItem, PaginationNext, PaginationPrevious, PalettePreview, Panel, PanelActions, PanelBody, PanelCard, PanelDescription, PanelFooter, PanelHeader, PanelPlaceholder, PanelTitle, PanelToolbar, Popover, PopoverAnchor, PopoverCloseButton, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Presence, Progress, PropertyList, PropertyRow, PropertyTerm, PropertyValue, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, SceneControls, ScrollArea, SecretInputGroup, Select, SelectChevronsUpDown, SelectContent, SelectItem, SelectLabel, SelectSection, SelectTrigger, SelectValue, Separator, SettingsField, SettingsLayout, SettingsNavigation, SettingsPage, SettingsRow, SettingsSection, Sheet, SheetBody, SheetCloseButton, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SideRail, SideRailButton, SideRailContent, SideRailFooter, SideRailHeader, SideRailItem, SideRailSection, SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavLabel, SidebarNavResizeHandle, SidebarNavRow, SidebarNavSection, SidebarNavTitle, Skeleton, SkipLink, Slider, Spinner, SplitLayout, Stat, StatGroup, StatusBar, StatusBarItem, StatusBarSpacer, StatusChip, StatusList, Switch, SwitchDescription, SwitchLabel, TALL_CODE_BLOCK_PX, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableNumericCell, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, TextLink, Textarea, ThemeMiniature, ThemeMiniatureSplit, ThemeModeToggle, ThemePicker, ThemePreview, ThemeProvider, ThemeSwatch, ThemeToggle, ThreadPanel, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, TopBarBreadcrumb, TopBarPill, TopBarSearch, TopBarSection, TopBarTitle, TranscriptComposition, Tree, TreeRow, UpdateDialog, ValueCombobox, VirtualWindow, WorkspaceMark, accentPresets, alertVariants, allTokens, badgeVariants, boardCardTitleVariants, builtinThemes, buttonVariants, clampFloatingPreviewPosition, clampPreviewMiniPlayerPosition, closePane, cn, colorTokens, computeLayoutFrames, contrastRatio, controlInteractive, controlSize, controlSizeIcon, controlSizes, countLeaves, countLines, createAppMenuItems, createLayoutState, cva, defaultDarkThemeId, defaultLightThemeId, defaultThemeSelection, densityTokens, designTokens, diffLineVariants, diffStats, diffTargetPath, elevationTokens, entityIconVariants, expandAll, findTokenRanges, focusPane, fontOptions, formatBytes, formatReleaseDate, formatToken, headingElement, headingVariants, initialsFrom, inlineScriptLiteral, isPasteBlock, itemVariants, keyedRows, layoutDepth, listLeaves, monogram, motionTokens, movePane, neighborLeaf, nextSeq, normalizeLayout, parseDiff, plainTextFromMarkdown, pruneBlocks, radiusTokens, recollapsePastes, remapCarriedBlocks, resizeFloatingPreview, resizePreviewMiniPlayer, resizeSplit, resolveFloatingPreviewFrame, resolvePreviewMiniPlayerFrame, resolvedDensity, sheetVariants, shouldCollapse, sidebarNavItemClass, sidebarNavItemStateClass, splitPane, statusDotVariants, stripTrailingBlankLines, surfaceInteractive, swapPanes, textElement, textVariants, textareaVariants, themeById, themeCssVariables, themeFamilies, themeScript, themesForAppearance, toast, toaster, toggleVariants, tokenRangeAt, transcriptDisclosureKey, typographyTokens, undoClosePane, undocumentedTokenAliases, useSideRail, useTheme, validateTheme, validateThemeRegistry, writeClipboardText, zIndexTokens };
124
+ export { AboutDialog, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AccountMenu, ActionButton, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellMain, AppearanceEditor, AppearancePanel, AppearancePopover, AspectRatio, AttachmentCard, Avatar, AvatarFallback, AvatarImage, Badge, Board, BoardCardBody, BoardCardTitle, BoardCardTrigger, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, BusySendButton, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CalendarSurface, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CatalogBrowser, CatalogDetail, CatalogDetailSection, ChatComposer, Checkbox, CheckboxDescription, CheckboxLabel, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxClear, ComboboxContent, ComboboxControl, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator, CommandShortcut, ComposerAttachmentButton, ComposerHint, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, ConversationAvatar, ConversationSurface, DatePicker, DetailPanel, DetailPanelBody, DetailPanelField, DetailPanelHeader, DetailPanelSection, Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffBlock, DiffSummary, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyState, EmptyTitle, EntityIcon, FLOATING_PREVIEW_CORNER_RADIUS, FLOATING_PREVIEW_EDGE_GAP, Field, FieldDescription, FieldError, FieldGroup, FieldInput, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTextArea, FloatingPreview, FormField, Heading, HelpCenter, HoverCard, HoverCardContent, HoverCardTrigger, InlineCode, Input, InputControl, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOtp, InputOtpCaret, Item, ItemDescription, ItemEmpty, ItemGroup, ItemGroupEntry, ItemTitle, Kbd, KbdGroup, Label, ListGroup, ListRow, ListRowControl, MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ModalDialog, Motion, NO_FLOATING_PREVIEW_OBSTACLES, NO_PREVIEW_MINI_PLAYER_OBSTACLES, NativeSelect, NavigationMenu, NavigationMenuContent, NavigationMenuGroup, NavigationMenuGroupLabel, NavigationMenuItem, NavigationMenuMenu, NavigationMenuSeparator, NavigationMenuTrigger, NavigationMenuViewport, NumberField, OrbitItem, OrbitLayout, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, PREVIEW_MINI_PLAYER_CORNER_RADIUS, PREVIEW_MINI_PLAYER_EDGE_GAP, Page, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderTitle, PageSection, Pagination, PaginationEllipsis, PaginationItem, PaginationNext, PaginationPrevious, PalettePreview, Panel, PanelActions, PanelBody, PanelCard, PanelDescription, PanelFooter, PanelHeader, PanelPlaceholder, PanelTitle, PanelToolbar, Popover, PopoverAnchor, PopoverCloseButton, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Presence, Progress, PropertyList, PropertyRow, PropertyTerm, PropertyValue, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, SceneControls, ScrollArea, SecretInputGroup, Select, SelectChevronsUpDown, SelectContent, SelectItem, SelectLabel, SelectSection, SelectTrigger, SelectValue, Separator, SettingsField, SettingsLayout, SettingsNavigation, SettingsPage, SettingsRow, SettingsSection, Sheet, SheetBody, SheetCloseButton, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SideRail, SideRailButton, SideRailContent, SideRailFooter, SideRailHeader, SideRailItem, SideRailSection, SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavLabel, SidebarNavResizeHandle, SidebarNavRow, SidebarNavSection, SidebarNavTitle, Skeleton, SkipLink, Slider, Spinner, SplitLayout, Stat, StatGroup, StatusBar, StatusBarItem, StatusBarSpacer, StatusChip, StatusList, Switch, SwitchDescription, SwitchLabel, TALL_CODE_BLOCK_PX, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableNumericCell, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, TextLink, Textarea, ThemeMiniature, ThemeMiniatureSplit, ThemeModeToggle, ThemePicker, ThemePreview, ThemeProvider, ThemeSwatch, ThemeToggle, ThreadPanel, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, TopBarBreadcrumb, TopBarPill, TopBarSearch, TopBarSection, TopBarTitle, TranscriptComposition, Tree, TreeRow, UpdateDialog, ValueCombobox, VirtualWindow, WorkspaceMark, accentPresets, alertVariants, allTokens, badgeVariants, boardCardTitleVariants, builtinThemes, buttonVariants, clampFloatingPreviewPosition, clampPreviewMiniPlayerPosition, closePane, cn, colorTokens, computeLayoutFrames, contrastRatio, controlInteractive, controlSize, controlSizeIcon, controlSizes, countLeaves, countLines, createAppMenuItems, createLayoutState, cva, defaultDarkThemeId, defaultLightThemeId, defaultThemeSelection, densityTokens, designTokens, diffLineVariants, diffStats, diffTargetPath, elevationTokens, entityIconVariants, expandAll, findTokenRanges, focusPane, fontOptions, formatBytes, formatReleaseDate, formatToken, headingElement, headingVariants, initialsFrom, inlineScriptLiteral, isPasteBlock, itemVariants, keyedRows, layoutDepth, listLeaves, monogram, motionTokens, movePane, neighborLeaf, nextSeq, normalizeLayout, parseDiff, plainTextFromMarkdown, pruneBlocks, radiusTokens, recollapsePastes, remapCarriedBlocks, resizeFloatingPreview, resizePreviewMiniPlayer, resizeSplit, resolveFloatingPreviewFrame, resolvePreviewMiniPlayerFrame, resolvedDensity, sheetVariants, shouldCollapse, sidebarNavItemClass, sidebarNavItemStateClass, splitPane, statusDotVariants, stripTrailingBlankLines, surfaceInteractive, swapPanes, textElement, textVariants, textareaVariants, themeById, themeCssVariables, themeFamilies, themeScript, themesForAppearance, toast, toaster, toggleVariants, tokenRangeAt, transcriptDisclosureKey, typographyTokens, undoClosePane, undocumentedTokenAliases, useSideRail, useTheme, validateTheme, validateThemeRegistry, writeClipboardText, zIndexTokens };
package/dist/r/board.json CHANGED
@@ -5,6 +5,8 @@
5
5
  "description": "",
6
6
  "dependencies": [],
7
7
  "registryDependencies": [
8
+ "@adea-ai/ui/badge",
9
+ "@adea-ai/ui/typography",
8
10
  "lib"
9
11
  ],
10
12
  "files": [
@@ -152,6 +152,8 @@
152
152
  "description": "",
153
153
  "dependencies": [],
154
154
  "registryDependencies": [
155
+ "@adea-ai/ui/badge",
156
+ "@adea-ai/ui/typography",
155
157
  "lib"
156
158
  ],
157
159
  "files": [
@@ -1,8 +1,10 @@
1
1
  import {
2
+ type ComponentProps,
2
3
  createEffect,
3
4
  createMemo,
4
5
  createSignal,
5
6
  For,
7
+ Index,
6
8
  onCleanup,
7
9
  Show,
8
10
  splitProps,
@@ -10,6 +12,8 @@ import {
10
12
  } from 'solid-js'
11
13
  import { cva, type VariantProps } from '../../../lib/variants'
12
14
  import { cn } from '../../../lib/utils'
15
+ import { badgeVariants } from '../badge/badge'
16
+ import { headingVariants, textVariants } from '../typography'
13
17
 
14
18
  /**
15
19
  * Board.
@@ -37,13 +41,48 @@ import { cn } from '../../../lib/utils'
37
41
  * of drag props. That is deliberate: a card's contents are domain — a title, a
38
42
  * priority tag, an assignee — and a board that rendered them itself could only
39
43
  * ever serve one product.
44
+ *
45
+ * `collapseEmpty` folds a column with no cards to a strip one line of text wide,
46
+ * its label turned on its side. A board of seven lanes where three are empty
47
+ * otherwise spends half its width on nothing, and the lanes with work in them
48
+ * are the ones that need the room. A folded lane is still a full-height drop
49
+ * target and still accepts a keyboard move, so nothing about operating the
50
+ * board changes; it does not unfold under a drag, because a lane that grows
51
+ * under the pointer moves every lane after it and the drop lands somewhere else.
52
+ *
53
+ * Give the board a height (`class="h-full"`) and each lane scrolls on its own;
54
+ * leave it unconstrained and lanes grow with their cards and the page scrolls.
40
55
  */
41
56
 
57
+ /**
58
+ * A lane's identifying colour, drawn as a dot before its label. The chart hues,
59
+ * in the order the token set gives them, because a lane colour tells lanes apart
60
+ * like a series does; it carries no status. `neutral` is for a lane that should
61
+ * not draw the eye — a backlog, an archive.
62
+ */
63
+ export type BoardTone =
64
+ | 'neutral'
65
+ | 'chart-1'
66
+ | 'chart-2'
67
+ | 'chart-3'
68
+ | 'chart-4'
69
+ | 'chart-5'
70
+ | 'chart-6'
71
+
42
72
  export type BoardColumn = Readonly<{
43
73
  id: string
44
74
  label: string
45
- /** A count or a limit, shown beside the label. */
75
+ /** The number of cards, shown as an accent count beside the label. */
76
+ count?: number
77
+ /** A limit or any other note, shown after the count. */
46
78
  meta?: JSX.Element
79
+ /** The lane's dot colour. Omitted, the header has no dot. */
80
+ tone?: BoardTone
81
+ /**
82
+ * `false` keeps this lane open when it is empty under `collapseEmpty` — the
83
+ * inbox lane new cards arrive in, which should always show where they land.
84
+ */
85
+ collapsible?: boolean
47
86
  /** Dim the column and refuse drops — a lane that is closed or read-only. */
48
87
  disabled?: boolean
49
88
  }>
@@ -70,6 +109,8 @@ export type BoardProps<T> = {
70
109
  emptyColumn?: (column: BoardColumn) => JSX.Element
71
110
  /** The board's accessible name. Defaults to "Board". */
72
111
  label?: string
112
+ /** Fold columns with no cards to a narrow strip with a sideways label. */
113
+ collapseEmpty?: boolean
73
114
  class?: string
74
115
  }
75
116
 
@@ -78,6 +119,40 @@ const cardDragState = {
78
119
  dragging: 'opacity-40',
79
120
  } as const
80
121
 
122
+ const toneDot: Record<BoardTone, string> = {
123
+ neutral: 'bg-muted-foreground',
124
+ 'chart-1': 'bg-chart-1',
125
+ 'chart-2': 'bg-chart-2',
126
+ 'chart-3': 'bg-chart-3',
127
+ 'chart-4': 'bg-chart-4',
128
+ 'chart-5': 'bg-chart-5',
129
+ 'chart-6': 'bg-chart-6',
130
+ }
131
+
132
+ function BoardColumnDot(props: { tone?: BoardTone }) {
133
+ return (
134
+ <Show when={props.tone}>
135
+ {(tone) => (
136
+ <span aria-hidden="true" class={cn('size-2 shrink-0 rounded-full', toneDot[tone()])} />
137
+ )}
138
+ </Show>
139
+ )
140
+ }
141
+
142
+ function BoardColumnCount(props: { count?: number }) {
143
+ return (
144
+ <Show when={props.count !== undefined}>
145
+ {/* The Badge's classes, not the component: Board stays free of Kobalte. */}
146
+ <span
147
+ class={cn(badgeVariants({ variant: 'subtle', size: 'sm' }), 'tabular-nums')}
148
+ data-slot="board-column-count"
149
+ >
150
+ {props.count}
151
+ </span>
152
+ </Show>
153
+ )
154
+ }
155
+
81
156
  export function Board<T>(props: BoardProps<T>) {
82
157
  const [local] = splitProps(props, [
83
158
  'columns',
@@ -89,6 +164,7 @@ export function Board<T>(props: BoardProps<T>) {
89
164
  'children',
90
165
  'emptyColumn',
91
166
  'label',
167
+ 'collapseEmpty',
92
168
  'class',
93
169
  ])
94
170
 
@@ -135,9 +211,15 @@ export function Board<T>(props: BoardProps<T>) {
135
211
  })
136
212
  })
137
213
 
138
- const prepareFocus = (item: T, columnId: string, source?: HTMLElement) => {
214
+ /**
215
+ * `always` is for a pointer drop: dragging a card is a direct act on it, so it
216
+ * ends focused wherever focus was. Chromium also blurs a focused control
217
+ * inside a draggable once the native drag starts, so "was focus in the card"
218
+ * cannot be read at drop time.
219
+ */
220
+ const prepareFocus = (item: T, columnId: string, source?: HTMLElement, always = false) => {
139
221
  const card = source ?? cardElements.get(local.itemId(item))
140
- if (card?.contains(card.ownerDocument.activeElement)) {
222
+ if (card && (always || card.contains(card.ownerDocument.activeElement))) {
141
223
  setPendingFocus({ itemId: local.itemId(item), columnId, source: card })
142
224
  }
143
225
  }
@@ -163,7 +245,7 @@ export function Board<T>(props: BoardProps<T>) {
163
245
  setDragging(null)
164
246
  setOverColumn(null)
165
247
  if (!item || !from || from === column.id || !accepted) return
166
- prepareFocus(item, column.id)
248
+ prepareFocus(item, column.id, undefined, true)
167
249
  local.onMove({ itemId: local.itemId(item), from, to: column.id })
168
250
  }
169
251
 
@@ -198,97 +280,144 @@ export function Board<T>(props: BoardProps<T>) {
198
280
  with more columns than fit, has nothing focusable to scroll from.
199
281
  */
200
282
  <div
201
- class={cn('flex min-h-0 gap-4 overflow-x-auto pb-2', local.class)}
283
+ class={cn('flex min-h-0 gap-3 overflow-x-auto pb-2', local.class)}
202
284
  role="region"
203
285
  aria-label={local.label ?? 'Board'}
204
286
  tabindex="0"
205
287
  >
206
- <For each={local.columns}>
288
+ {/*
289
+ Index, not For: callers derive columns (a count, a tone) and hand over
290
+ fresh objects whenever a card moves. Keyed by identity, every lane would
291
+ remount on each move and take the moved card's restored focus with it.
292
+ */}
293
+ <Index each={local.columns}>
207
294
  {(column) => {
208
- const items = () => byColumn().get(column.id) ?? []
295
+ const items = () => byColumn().get(column().id) ?? []
296
+ const collapsed = () =>
297
+ Boolean(local.collapseEmpty) && column().collapsible !== false && items().length === 0
298
+ const over = () => overColumn() === column().id
209
299
  return (
210
300
  <section
211
301
  class={cn(
212
- 'flex w-72 shrink-0 flex-col gap-2 rounded-lg border border-border bg-muted/40 p-2',
213
- accepts(column) && 'border-ring bg-muted',
214
- overColumn() === column.id && !accepts(column) && 'border-dashed opacity-60',
215
- column.disabled && 'opacity-50'
302
+ 'flex shrink-0 flex-col rounded-lg border border-border bg-muted/40 transition-colors',
303
+ {
304
+ 'w-72': !collapsed(),
305
+ 'w-10 items-center': collapsed(),
306
+ 'border-dashed border-ring': accepts(column()) && !over(),
307
+ 'border-ring bg-primary-subtle': accepts(column()) && over(),
308
+ 'border-dashed opacity-60': over() && !accepts(column()),
309
+ 'opacity-50': column().disabled,
310
+ }
216
311
  )}
217
- aria-label={column.label}
312
+ aria-label={column().label}
313
+ data-collapsed={collapsed() ? '' : undefined}
218
314
  onDragOver={(event) => {
219
- setOverColumn(column.id)
220
- if (accepts(column)) event.preventDefault()
315
+ setOverColumn(column().id)
316
+ if (accepts(column())) event.preventDefault()
317
+ }}
318
+ onDragLeave={(event) => {
319
+ // Leaving for a child is not leaving the lane.
320
+ if (event.currentTarget.contains(event.relatedTarget as Node | null)) return
321
+ setOverColumn((current) => (current === column().id ? null : current))
221
322
  }}
222
- onDragLeave={() =>
223
- setOverColumn((current) => (current === column.id ? null : current))
224
- }
225
323
  onDrop={(event) => {
226
324
  event.preventDefault()
227
- drop(column)
325
+ drop(column())
228
326
  }}
229
327
  >
230
- <header class="flex items-center justify-between gap-2 px-1 py-0.5">
231
- <h3 class="text-xs font-semibold text-foreground">{column.label}</h3>
232
- <Show when={column.meta}>
233
- <span class="text-2xs text-muted-foreground">{column.meta}</span>
234
- </Show>
235
- </header>
236
- <div class="flex min-h-16 flex-col gap-2">
237
- <For each={items()}>
238
- {(item) => {
239
- const isDragging = () => {
240
- const current = dragging()
241
- return Boolean(current && local.itemId(current) === local.itemId(item))
242
- }
243
- return (
244
- <article
245
- ref={(element) => {
246
- const id = local.itemId(item)
247
- cardElements.set(id, element)
248
- onCleanup(() => {
249
- if (cardElements.get(id) === element) cardElements.delete(id)
250
- })
251
- }}
252
- class={cn(
253
- 'rounded-md border border-border bg-card text-sm shadow-xs',
254
- cardDragState[isDragging() ? 'dragging' : 'idle']
255
- )}
256
- // A board card is a control that can be moved, so it is
257
- // focusable and carries the chord it responds to.
258
- tabindex="0"
259
- aria-roledescription="Draggable card"
260
- draggable={true}
261
- onDragStart={() => setDragging(() => item)}
262
- onDragEnd={() => {
263
- setDragging(null)
264
- setOverColumn(null)
265
- }}
266
- onKeyDown={(event) => {
267
- if (!(event.ctrlKey || event.metaKey)) return
268
- if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
269
- event.preventDefault()
270
- moveByKeyboard(
271
- item,
272
- event.key === 'ArrowRight' ? 1 : -1,
273
- event.currentTarget
274
- )
275
- }}
276
- >
277
- {local.children(item, { dragging: isDragging() })}
278
- </article>
279
- )
280
- }}
281
- </For>
282
- <Show when={items().length === 0}>
283
- <p class="rounded-md border border-dashed border-border px-2 py-4 text-center text-2xs text-muted-foreground">
284
- {local.emptyColumn?.(column) ?? 'Nothing here'}
285
- </p>
286
- </Show>
287
- </div>
328
+ <Show
329
+ when={!collapsed()}
330
+ fallback={
331
+ <header class="flex flex-col items-center gap-2 py-3">
332
+ <BoardColumnDot tone={column().tone} />
333
+ <BoardColumnCount count={column().count} />
334
+ <h3
335
+ class={cn(
336
+ headingVariants({ size: 'subsection', leading: 'none', tone: 'muted' }),
337
+ 'text-vertical'
338
+ )}
339
+ >
340
+ {column().label}
341
+ </h3>
342
+ </header>
343
+ }
344
+ >
345
+ <header class="flex h-10 shrink-0 items-center gap-2 px-3">
346
+ <BoardColumnDot tone={column().tone} />
347
+ <h3
348
+ class={cn(
349
+ headingVariants({ size: 'subsection', leading: 'none', tone: 'foreground' }),
350
+ 'truncate'
351
+ )}
352
+ >
353
+ {column().label}
354
+ </h3>
355
+ <BoardColumnCount count={column().count} />
356
+ <Show when={column().meta}>
357
+ <span class="ms-auto text-2xs text-muted-foreground">{column().meta}</span>
358
+ </Show>
359
+ </header>
360
+ <div class="flex min-h-16 flex-1 flex-col gap-2 overflow-y-auto px-2 pb-2">
361
+ <For each={items()}>
362
+ {(item) => {
363
+ const isDragging = () => {
364
+ const current = dragging()
365
+ return Boolean(current && local.itemId(current) === local.itemId(item))
366
+ }
367
+ return (
368
+ <article
369
+ ref={(element) => {
370
+ const id = local.itemId(item)
371
+ cardElements.set(id, element)
372
+ onCleanup(() => {
373
+ if (cardElements.get(id) === element) cardElements.delete(id)
374
+ })
375
+ }}
376
+ class={cn(
377
+ 'relative shrink-0 cursor-grab rounded-md border border-border bg-card text-sm shadow-xs outline-none transition-colors hover:bg-surface-hover focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle active:cursor-grabbing',
378
+ cardDragState[isDragging() ? 'dragging' : 'idle']
379
+ )}
380
+ // A board card is a control that can be moved, so it is
381
+ // focusable and carries the chord it responds to.
382
+ tabindex="0"
383
+ aria-roledescription="Draggable card"
384
+ draggable={true}
385
+ onDragStart={(event) => {
386
+ event.dataTransfer?.setData('text/plain', local.itemId(item))
387
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'
388
+ setDragging(() => item)
389
+ }}
390
+ onDragEnd={() => {
391
+ setDragging(null)
392
+ setOverColumn(null)
393
+ }}
394
+ onKeyDown={(event) => {
395
+ if (!(event.ctrlKey || event.metaKey)) return
396
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
397
+ event.preventDefault()
398
+ moveByKeyboard(
399
+ item,
400
+ event.key === 'ArrowRight' ? 1 : -1,
401
+ event.currentTarget
402
+ )
403
+ }}
404
+ >
405
+ {local.children(item, { dragging: isDragging() })}
406
+ </article>
407
+ )
408
+ }}
409
+ </For>
410
+ <Show when={items().length === 0}>
411
+ <p class="rounded-md border border-dashed border-border px-2 py-4 text-center text-2xs text-muted-foreground">
412
+ {local.emptyColumn?.(column()) ?? 'Nothing here'}
413
+ </p>
414
+ </Show>
415
+ </div>
416
+ </Show>
288
417
  </section>
289
418
  )
290
419
  }}
291
- </For>
420
+ </Index>
292
421
  {/*
293
422
  The keyboard move's result, announced. A drag speaks for itself — the card
294
423
  visibly moves — while a keyboard move happens with the focus elsewhere.
@@ -320,3 +449,28 @@ export function BoardCardTitle(props: BoardCardTitleProps) {
320
449
  export function BoardCardBody(props: { class?: string; children: JSX.Element }) {
321
450
  return <div class={cn('grid gap-1.5 p-2.5', props.class)}>{props.children}</div>
322
451
  }
452
+
453
+ /**
454
+ * The card's open action: its title, as a real button whose hit area stretches
455
+ * over the whole card. A card is one target to a pointer — clicking anywhere on
456
+ * it opens it — while assistive technology still meets a named button rather
457
+ * than a clickable `article`. Other controls on the card sit above the stretched
458
+ * area by being positioned (`class="relative"`), so they keep their own click.
459
+ */
460
+ export function BoardCardTrigger(props: ComponentProps<'button'>) {
461
+ const [local, rest] = splitProps(props, ['class', 'type'])
462
+ return (
463
+ <button
464
+ type={local.type ?? 'button'}
465
+ data-slot="board-card-trigger"
466
+ class={cn(
467
+ textVariants({ variant: 'label', tone: 'foreground' }),
468
+ 'min-w-0 flex-1 truncate rounded-sm text-start outline-none',
469
+ 'after:absolute after:inset-0 after:rounded-md',
470
+ 'focus-visible:ring-3 focus-visible:ring-primary-subtle',
471
+ local.class
472
+ )}
473
+ {...rest}
474
+ />
475
+ )
476
+ }
@@ -2,9 +2,11 @@ export {
2
2
  Board,
3
3
  BoardCardBody,
4
4
  BoardCardTitle,
5
+ BoardCardTrigger,
5
6
  boardCardTitleVariants,
6
7
  type BoardCardTitleProps,
7
8
  type BoardColumn,
8
9
  type BoardMove,
9
10
  type BoardProps,
11
+ type BoardTone,
10
12
  } from './board'
@@ -244,6 +244,17 @@
244
244
  }
245
245
  }
246
246
 
247
+ /**
248
+ * A label set on its side, reading top to bottom (turned 90° clockwise). For a
249
+ * collapsed lane or rail section that must stay named while it is only as wide
250
+ * as a line of text. Letters keep their orientation for the reader's head tilt
251
+ * rather than stacking, which is unreadable past a few characters.
252
+ */
253
+ @utility text-vertical {
254
+ writing-mode: vertical-rl;
255
+ text-orientation: mixed;
256
+ }
257
+
247
258
  @utility no-scrollbar {
248
259
  -ms-overflow-style: none;
249
260
  scrollbar-width: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.101.1",
3
+ "version": "0.102.0",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/registry.json CHANGED
@@ -152,6 +152,8 @@
152
152
  "description": "",
153
153
  "dependencies": [],
154
154
  "registryDependencies": [
155
+ "@adea-ai/ui/badge",
156
+ "@adea-ai/ui/typography",
155
157
  "lib"
156
158
  ],
157
159
  "files": [