@tangle-network/ui 11.19.0 → 11.21.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a69c72f: Add `Pagination` (zero-based `page`, `pageCount`, `onPageChange`; a constant number of page slots with first and last always reachable) and `Alert` with `AlertTitle` and `AlertDescription` (Brand status tones plus `neutral`). `SegmentedControl` options take `testId`, and the group takes `data-testid`.
8
+
9
+ ## 11.20.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 24ea445: IconTile gains `xl` (64px) and `2xl` (80px) sizes for page and profile headers, so an app can show an entity's face at header scale without a local avatar component.
14
+
3
15
  ## 11.19.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -76,6 +76,8 @@ import { Heading, HeadingProps, HeadingVariant } from "./primitives/heading.js";
76
76
  import { PageShell, PageShellProps } from "./primitives/page-shell.js";
77
77
  import { PageHeader, PageHeaderProps } from "./primitives/page-header.js";
78
78
  import { StatusPill, StatusPillProps, StatusTone } from "./primitives/status-pill.js";
79
+ import { Alert, AlertDescription, AlertProps, AlertTitle, AlertTone } from "./primitives/alert.js";
80
+ import { Pagination, PaginationProps } from "./primitives/pagination.js";
79
81
  import { CATEGORY_TONES, CategoryTone, TONE_CLASSES, Tone, ToneClasses, toneFor } from "./primitives/tone.js";
80
82
  import { Tag, TagProps } from "./primitives/tag.js";
81
83
  import { Chip, ChipProps } from "./primitives/chip.js";
@@ -112,4 +114,4 @@ import { QuestionPreview, QuestionPreviewProps } from "./tool-previews/question-
112
114
  import { DiffPreview, DiffPreviewProps } from "./tool-previews/diff-preview.js";
113
115
  import { RedactedDocSegment, RedactedDocument, RedactedDocumentData, RedactedDocumentProps, RevealResult } from "./redaction/redacted-document.js";
114
116
  import "./redaction.js";
115
- export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AppendUserMessageOptions, ApplySdkEventOptions, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, CustomToolRenderer, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, FileArtifactPane, type FileArtifactPaneProps, type FileFormat, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, copyText, createAuthFetcher, detectFileFormat, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, messagesAtom, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
117
+ export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, AppendUserMessageOptions, ApplySdkEventOptions, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, CustomToolRenderer, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, FileArtifactPane, type FileArtifactPaneProps, type FileFormat, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, copyText, createAuthFetcher, detectFileFormat, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, messagesAtom, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
package/dist/index.js CHANGED
@@ -39,6 +39,8 @@ import { InlineCode } from "./primitives/code-block.js";
39
39
  import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
40
40
  import { PageShell } from "./primitives/page-shell.js";
41
41
  import { PageHeader } from "./primitives/page-header.js";
42
+ import { Alert, AlertDescription, AlertTitle } from "./primitives/alert.js";
43
+ import { Pagination } from "./primitives/pagination.js";
42
44
  import { StatusPill } from "./primitives/status-pill.js";
43
45
  import { CATEGORY_TONES, TONE_CLASSES, toneFor } from "./primitives/tone.js";
44
46
  import { Tag } from "./primitives/tag.js";
@@ -96,4 +98,4 @@ import { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMap
96
98
  import { copyText } from "./utils/copy-text.js";
97
99
  import { timeAgo } from "./utils/time-ago.js";
98
100
  import { RedactedDocument } from "./redaction/redacted-document.js";
99
- export { AgentTimeline, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, FileArtifactPane, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageList, Metric, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunRowShell, RunRowStatusDot, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, WriteFilePreview, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, copyText, createAuthFetcher, detectFileFormat, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, messagesAtom, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
101
+ export { AgentTimeline, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, FileArtifactPane, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageList, Metric, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunRowShell, RunRowStatusDot, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, WriteFilePreview, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, copyText, createAuthFetcher, detectFileFormat, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, messagesAtom, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
@@ -0,0 +1,35 @@
1
+ import { StatusTone } from "./status-pill.js";
2
+ import * as React from "react";
3
+ //#region src/primitives/alert.d.ts
4
+ /**
5
+ * An inline message that stays on the page: a policy note above a form, a
6
+ * failed save beside the field it concerns. For a message that interrupts, use
7
+ * `AlertDialog`; for one that goes away by itself, use a toast.
8
+ *
9
+ * Each tone paints its fill, border and text from Brand's matched status
10
+ * triple, the same one `StatusPill` uses, so the text is solved against its
11
+ * own background on every plane. `neutral` is a card with body text.
12
+ *
13
+ * `danger` and `warning` announce themselves (`role="alert"`); the quieter
14
+ * tones are `role="status"` so a screen reader is not interrupted by a note.
15
+ */
16
+ export type AlertTone = Exclude<StatusTone, "running">;
17
+ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
18
+ tone?: AlertTone;
19
+ /** A leading icon. It takes the tone's colour and sits beside the title. */
20
+ icon?: React.ReactNode;
21
+ }
22
+ export declare const Alert: React.ForwardRefExoticComponent<AlertProps & React.RefAttributes<HTMLDivElement>>;
23
+ export declare function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>): React.JSX.Element;
24
+ export declare namespace AlertTitle {
25
+ var displayName: string;
26
+ }
27
+ /**
28
+ * Body text. Under a toned alert it keeps the tone's ink, which is the colour
29
+ * tested against the tone's fill; under `neutral` it is the muted tier.
30
+ */
31
+ export declare function AlertDescription({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
32
+ export declare namespace AlertDescription {
33
+ var displayName: string;
34
+ }
35
+ //#endregion
@@ -0,0 +1,47 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import * as React from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/primitives/alert.tsx
5
+ const TONE = {
6
+ neutral: "border-border bg-card text-card-foreground",
7
+ info: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
8
+ success: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
9
+ warning: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
10
+ danger: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]"
11
+ };
12
+ const Alert = React.forwardRef(({ tone = "neutral", icon, className, children, role, ...props }, ref) => /* @__PURE__ */ jsxs("div", {
13
+ ref,
14
+ role: role ?? (tone === "danger" || tone === "warning" ? "alert" : "status"),
15
+ "data-tone": tone,
16
+ className: cn("flex w-full min-w-0 gap-3 rounded-lg border px-4 py-3 text-sm", TONE[tone], className),
17
+ ...props,
18
+ children: [icon ? /* @__PURE__ */ jsx("span", {
19
+ "aria-hidden": "true",
20
+ className: "mt-0.5 flex shrink-0 [&_svg]:size-4",
21
+ children: icon
22
+ }) : null, /* @__PURE__ */ jsx("div", {
23
+ className: "grid min-w-0 flex-1 gap-1",
24
+ children
25
+ })]
26
+ }));
27
+ Alert.displayName = "Alert";
28
+ function AlertTitle({ className, ...props }) {
29
+ return /* @__PURE__ */ jsx("p", {
30
+ className: cn("font-medium leading-snug [overflow-wrap:anywhere]", className),
31
+ ...props
32
+ });
33
+ }
34
+ AlertTitle.displayName = "AlertTitle";
35
+ /**
36
+ * Body text. Under a toned alert it keeps the tone's ink, which is the colour
37
+ * tested against the tone's fill; under `neutral` it is the muted tier.
38
+ */
39
+ function AlertDescription({ className, ...props }) {
40
+ return /* @__PURE__ */ jsx("div", {
41
+ className: cn("leading-relaxed [overflow-wrap:anywhere] [[data-tone=neutral]_&]:text-muted-foreground", className),
42
+ ...props
43
+ });
44
+ }
45
+ AlertDescription.displayName = "AlertDescription";
46
+ //#endregion
47
+ export { Alert, AlertDescription, AlertTitle };
@@ -17,7 +17,12 @@ import * as React from "react";
17
17
  export interface IconTileProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
18
18
  /** Defaults to a tone derived from `name`, or violet when there is no name. */
19
19
  tone?: CategoryTone;
20
- size?: "xs" | "sm" | "md" | "lg";
20
+ /**
21
+ * `xs` to `lg` (20 to 40px) sit in rows and cards. `xl` (64px) and `2xl`
22
+ * (80px) are for a page or profile header, where the tile is the thing's
23
+ * face rather than a list marker.
24
+ */
25
+ size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
21
26
  src?: string;
22
27
  icon?: React.ReactNode;
23
28
  /** The thing's name. Supplies the initials and the derived tone. */
@@ -7,7 +7,9 @@ const SIZE = {
7
7
  xs: "size-5 rounded-[5px] text-[10px] [&_svg]:size-3",
8
8
  sm: "size-6 rounded-md text-[11px] [&_svg]:size-3.5",
9
9
  md: "size-8 rounded-md text-xs [&_svg]:size-4",
10
- lg: "size-10 rounded-lg text-sm [&_svg]:size-5"
10
+ lg: "size-10 rounded-lg text-sm [&_svg]:size-5",
11
+ xl: "size-16 rounded-2xl text-xl [&_svg]:size-8",
12
+ "2xl": "size-20 rounded-2xl text-2xl [&_svg]:size-10"
11
13
  };
12
14
  /** Up to two initials: the first letters of the first two words, or of the one word. */
13
15
  function initialsOf(name) {
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ //#region src/primitives/pagination.d.ts
3
+ /**
4
+ * Page controls for a paged list or table: previous, the page numbers around
5
+ * the current one with the first and last always reachable, and next.
6
+ *
7
+ * Pages are zero-based in the API and one-based on screen. With one page or
8
+ * none there is nothing to choose, so it renders nothing.
9
+ */
10
+ export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLElement>, "onChange" | "children"> {
11
+ /** The current page, zero-based. */
12
+ page: number;
13
+ pageCount: number;
14
+ onPageChange: (page: number) => void;
15
+ /** Pages shown on each side of the current one before an ellipsis. */
16
+ siblings?: number;
17
+ }
18
+ export declare function Pagination({ page, pageCount, onPageChange, siblings, className, "aria-label": ariaLabel, ...rest }: PaginationProps): React.JSX.Element | null;
19
+ //#endregion
@@ -0,0 +1,81 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { buttonVariants } from "./button.js";
3
+ import "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { ChevronLeft, ChevronRight } from "lucide-react";
6
+ //#region src/primitives/pagination.tsx
7
+ /**
8
+ * The visible slots for `page` of `pageCount`. The number of slots stays the
9
+ * same as the current page moves, so the controls do not shift under the
10
+ * pointer while a person clicks through.
11
+ */
12
+ function pageSlots(page, pageCount, siblings = 1) {
13
+ const last = pageCount - 1;
14
+ if (pageCount <= siblings * 2 + 5) return Array.from({ length: pageCount }, (_, i) => i);
15
+ const current = Math.min(Math.max(page, 0), last);
16
+ const start = Math.max(Math.min(current - siblings, last - siblings * 2 - 2), 2);
17
+ const end = Math.min(Math.max(current + siblings, siblings * 2 + 2), last - 2);
18
+ const slots = [0, start > 2 ? "gap-start" : 1];
19
+ for (let i = start; i <= end; i++) slots.push(i);
20
+ slots.push(end < last - 2 ? "gap-end" : last - 1, last);
21
+ return slots;
22
+ }
23
+ const pageButton = cn(buttonVariants({
24
+ variant: "ghost",
25
+ size: "sm"
26
+ }), "min-w-8 px-2 tabular-nums");
27
+ function Pagination({ page, pageCount, onPageChange, siblings = 1, className, "aria-label": ariaLabel = "Pagination", ...rest }) {
28
+ if (pageCount <= 1) return null;
29
+ const last = pageCount - 1;
30
+ const current = Math.min(Math.max(page, 0), last);
31
+ const go = (next) => {
32
+ const target = Math.min(Math.max(next, 0), last);
33
+ if (target !== current) onPageChange(target);
34
+ };
35
+ return /* @__PURE__ */ jsx("nav", {
36
+ "aria-label": ariaLabel,
37
+ className: cn("flex items-center", className),
38
+ ...rest,
39
+ children: /* @__PURE__ */ jsxs("ul", {
40
+ className: "flex flex-wrap items-center gap-1",
41
+ children: [
42
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
43
+ type: "button",
44
+ "aria-label": "Previous page",
45
+ disabled: current === 0,
46
+ onClick: () => go(current - 1),
47
+ className: cn(pageButton, "gap-1 pl-1.5"),
48
+ children: [/* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" }), /* @__PURE__ */ jsx("span", {
49
+ className: "hidden sm:inline",
50
+ children: "Previous"
51
+ })]
52
+ }) }),
53
+ pageSlots(current, pageCount, siblings).map((slot) => typeof slot === "number" ? /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
54
+ type: "button",
55
+ "aria-label": `Page ${slot + 1}`,
56
+ "aria-current": slot === current ? "page" : void 0,
57
+ onClick: () => go(slot),
58
+ className: cn(pageButton, slot === current && "border-border bg-card font-semibold text-foreground hover:bg-card"),
59
+ children: slot + 1
60
+ }) }, slot) : /* @__PURE__ */ jsx("li", {
61
+ "aria-hidden": "true",
62
+ className: "flex min-w-8 justify-center text-sm text-muted-foreground",
63
+ children: "…"
64
+ }, slot)),
65
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
66
+ type: "button",
67
+ "aria-label": "Next page",
68
+ disabled: current === last,
69
+ onClick: () => go(current + 1),
70
+ className: cn(pageButton, "gap-1 pr-1.5"),
71
+ children: [/* @__PURE__ */ jsx("span", {
72
+ className: "hidden sm:inline",
73
+ children: "Next"
74
+ }), /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })]
75
+ }) })
76
+ ]
77
+ })
78
+ });
79
+ }
80
+ //#endregion
81
+ export { Pagination, pageSlots };
@@ -23,6 +23,8 @@ export interface SegmentedControlOption<T extends string = string> {
23
23
  label: React.ReactNode;
24
24
  /** Rendered right of the label, typically a count or status pill. */
25
25
  adornment?: React.ReactNode;
26
+ /** Rendered as the option button's `data-testid`, for end-to-end selectors. */
27
+ testId?: string;
26
28
  }
27
29
  export interface SegmentedControlProps<T extends string = string> extends Pick<React.HTMLAttributes<HTMLDivElement>, "id" | "className" | "aria-label" | "aria-labelledby"> {
28
30
  value: T;
@@ -35,6 +37,8 @@ export interface SegmentedControlProps<T extends string = string> extends Pick<R
35
37
  * reads as a classic tab (used on the Team page)
36
38
  */
37
39
  variant?: "row" | "tabs";
40
+ /** Rendered as the group's `data-testid`. */
41
+ "data-testid"?: string;
38
42
  }
39
43
  export declare function SegmentedControl<T extends string = string>({ value, onValueChange, options, variant, className, ...rest }: SegmentedControlProps<T>): React.JSX.Element;
40
44
  //#endregion
@@ -28,10 +28,12 @@ function SegmentedControl({ value, onValueChange, options, variant = "row", clas
28
28
  id: rest.id,
29
29
  "aria-label": rest["aria-label"],
30
30
  "aria-labelledby": rest["aria-labelledby"],
31
+ "data-testid": rest["data-testid"],
31
32
  onKeyDown: handleKeyDown,
32
33
  className: cn("flex gap-1", variant === "row" && "flex-wrap items-center rounded-lg border border-border bg-card p-1", variant === "tabs" && "flex-nowrap items-end border-b border-border pb-0 overflow-x-auto", className),
33
34
  children: options.map((option, i) => {
34
35
  const active = option.value === value;
36
+ const focusable = active || !hasMatch && i === 0;
35
37
  return /* @__PURE__ */ jsxs("button", {
36
38
  ref: (el) => {
37
39
  if (el) optionRefs.current.set(option.value, el);
@@ -40,7 +42,8 @@ function SegmentedControl({ value, onValueChange, options, variant = "row", clas
40
42
  type: "button",
41
43
  role: "radio",
42
44
  "aria-checked": active,
43
- tabIndex: active || !hasMatch && i === 0 ? 0 : -1,
45
+ "data-testid": option.testId,
46
+ tabIndex: focusable ? 0 : -1,
44
47
  onClick: () => {
45
48
  if (!active) onValueChange(option.value);
46
49
  },
@@ -38,6 +38,8 @@ import { Heading, HeadingProps, HeadingVariant } from "./primitives/heading.js";
38
38
  import { PageShell, PageShellProps } from "./primitives/page-shell.js";
39
39
  import { PageHeader, PageHeaderProps } from "./primitives/page-header.js";
40
40
  import { StatusPill, StatusPillProps, StatusTone } from "./primitives/status-pill.js";
41
+ import { Alert, AlertDescription, AlertProps, AlertTitle, AlertTone } from "./primitives/alert.js";
42
+ import { Pagination, PaginationProps } from "./primitives/pagination.js";
41
43
  import { CATEGORY_TONES, CategoryTone, TONE_CLASSES, Tone, ToneClasses, toneFor } from "./primitives/tone.js";
42
44
  import { Tag, TagProps } from "./primitives/tag.js";
43
45
  import { Chip, ChipProps } from "./primitives/chip.js";
@@ -45,4 +47,4 @@ import { IconTile, IconTileProps, initialsOf } from "./primitives/icon-tile.js";
45
47
  import { Metric, MetricProps, MetricStrip, MetricStripProps } from "./primitives/metric-strip.js";
46
48
  import { FilterField, FilterFieldProps, Toolbar, ToolbarProps } from "./primitives/toolbar.js";
47
49
  import { Logo, LogoProps, TangleKnot } from "@tangle-network/brand";
48
- export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Button, type ButtonProps, CATEGORY_TONES, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, buttonVariants, initialsOf, toneFor, useTheme, useToast };
50
+ export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Button, type ButtonProps, CATEGORY_TONES, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, buttonVariants, initialsOf, toneFor, useTheme, useToast };
@@ -37,6 +37,8 @@ import { InlineCode } from "./primitives/code-block.js";
37
37
  import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
38
38
  import { PageShell } from "./primitives/page-shell.js";
39
39
  import { PageHeader } from "./primitives/page-header.js";
40
+ import { Alert, AlertDescription, AlertTitle } from "./primitives/alert.js";
41
+ import { Pagination } from "./primitives/pagination.js";
40
42
  import { StatusPill } from "./primitives/status-pill.js";
41
43
  import { CATEGORY_TONES, TONE_CLASSES, toneFor } from "./primitives/tone.js";
42
44
  import { Tag } from "./primitives/tag.js";
@@ -45,4 +47,4 @@ import { IconTile, initialsOf } from "./primitives/icon-tile.js";
45
47
  import { Metric, MetricStrip } from "./primitives/metric-strip.js";
46
48
  import { FilterField, Toolbar } from "./primitives/toolbar.js";
47
49
  import { Logo, TangleKnot } from "@tangle-network/brand";
48
- export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, FilterField, Heading, IconTile, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, badgeVariants, buttonVariants, initialsOf, toneFor, useTheme, useToast };
50
+ export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, FilterField, Heading, IconTile, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, badgeVariants, buttonVariants, initialsOf, toneFor, useTheme, useToast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.19.0",
3
+ "version": "11.21.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -149,7 +149,7 @@
149
149
  "peerDependencies": {
150
150
  "@hocuspocus/provider": ">=2.15.0 <5.0.0",
151
151
  "@nanostores/react": ">=0.8.4 <3.0.0",
152
- "@tangle-network/brand": "^1.13.0",
152
+ "@tangle-network/brand": "^1.15.2",
153
153
  "@tiptap/core": ">=3.20.4 <4.0.0",
154
154
  "@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
155
155
  "@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
@@ -0,0 +1,80 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import type { StatusTone } from "./status-pill";
4
+
5
+ /**
6
+ * An inline message that stays on the page: a policy note above a form, a
7
+ * failed save beside the field it concerns. For a message that interrupts, use
8
+ * `AlertDialog`; for one that goes away by itself, use a toast.
9
+ *
10
+ * Each tone paints its fill, border and text from Brand's matched status
11
+ * triple, the same one `StatusPill` uses, so the text is solved against its
12
+ * own background on every plane. `neutral` is a card with body text.
13
+ *
14
+ * `danger` and `warning` announce themselves (`role="alert"`); the quieter
15
+ * tones are `role="status"` so a screen reader is not interrupted by a note.
16
+ */
17
+ export type AlertTone = Exclude<StatusTone, "running">;
18
+
19
+ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
20
+ tone?: AlertTone;
21
+ /** A leading icon. It takes the tone's colour and sits beside the title. */
22
+ icon?: React.ReactNode;
23
+ }
24
+
25
+ const TONE: Record<AlertTone, string> = {
26
+ neutral: "border-border bg-card text-card-foreground",
27
+ info: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
28
+ success:
29
+ "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
30
+ warning:
31
+ "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
32
+ danger:
33
+ "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]",
34
+ };
35
+
36
+ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
37
+ ({ tone = "neutral", icon, className, children, role, ...props }, ref) => (
38
+ <div
39
+ ref={ref}
40
+ role={role ?? (tone === "danger" || tone === "warning" ? "alert" : "status")}
41
+ data-tone={tone}
42
+ className={cn(
43
+ "flex w-full min-w-0 gap-3 rounded-lg border px-4 py-3 text-sm",
44
+ TONE[tone],
45
+ className,
46
+ )}
47
+ {...props}
48
+ >
49
+ {icon ? (
50
+ <span aria-hidden="true" className="mt-0.5 flex shrink-0 [&_svg]:size-4">
51
+ {icon}
52
+ </span>
53
+ ) : null}
54
+ <div className="grid min-w-0 flex-1 gap-1">{children}</div>
55
+ </div>
56
+ ),
57
+ );
58
+ Alert.displayName = "Alert";
59
+
60
+ export function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
61
+ return <p className={cn("font-medium leading-snug [overflow-wrap:anywhere]", className)} {...props} />;
62
+ }
63
+ AlertTitle.displayName = "AlertTitle";
64
+
65
+ /**
66
+ * Body text. Under a toned alert it keeps the tone's ink, which is the colour
67
+ * tested against the tone's fill; under `neutral` it is the muted tier.
68
+ */
69
+ export function AlertDescription({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
70
+ return (
71
+ <div
72
+ className={cn(
73
+ "leading-relaxed [overflow-wrap:anywhere] [[data-tone=neutral]_&]:text-muted-foreground",
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+ AlertDescription.displayName = "AlertDescription";
@@ -62,10 +62,10 @@ export const Sizes: Story = {
62
62
  render: () => (
63
63
  <BothThemes>
64
64
  <div className="flex items-end gap-3">
65
- {(["xs", "sm", "md", "lg"] as const).map((size) => (
65
+ {(["xs", "sm", "md", "lg", "xl", "2xl"] as const).map((size) => (
66
66
  <IconTile key={size} size={size} name="Tangle Network" />
67
67
  ))}
68
- {(["xs", "sm", "md", "lg"] as const).map((size) => (
68
+ {(["xs", "sm", "md", "lg", "xl", "2xl"] as const).map((size) => (
69
69
  <IconTile key={`g-${size}`} size={size} tone="cyan" icon={<Globe />} />
70
70
  ))}
71
71
  </div>
@@ -0,0 +1,39 @@
1
+ import { render } from "@testing-library/react";
2
+ import { Globe } from "lucide-react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { IconTile, initialsOf } from "./icon-tile";
5
+
6
+ describe("IconTile", () => {
7
+ it.each([
8
+ ["xs", "size-5"],
9
+ ["sm", "size-6"],
10
+ ["md", "size-8"],
11
+ ["lg", "size-10"],
12
+ ["xl", "size-16"],
13
+ ["2xl", "size-20"],
14
+ ] as const)("renders size %s as %s", (size, cls) => {
15
+ const { container } = render(<IconTile size={size} name="Tangle Network" />);
16
+ expect(container.firstElementChild).toHaveClass(cls);
17
+ });
18
+
19
+ it("scales the glyph with the large sizes", () => {
20
+ const { container } = render(<IconTile size="2xl" tone="cyan" icon={<Globe />} />);
21
+ expect(container.firstElementChild?.className).toContain("[&_svg]:size-10");
22
+ });
23
+
24
+ it("shows initials and is decorative without a label", () => {
25
+ const { container } = render(<IconTile size="xl" name="Acme Robotics" />);
26
+ const tile = container.firstElementChild;
27
+ expect(tile).toHaveTextContent("AR");
28
+ expect(tile).toHaveAttribute("aria-hidden", "true");
29
+ });
30
+
31
+ it("is an image with an accessible name when labelled", () => {
32
+ const { getByRole } = render(<IconTile size="xl" name="Acme" label="Acme" />);
33
+ expect(getByRole("img", { name: "Acme" })).toBeInTheDocument();
34
+ });
35
+
36
+ it("keeps an emoji whole in the initials", () => {
37
+ expect(initialsOf("🦊")).toBe("🦊");
38
+ });
39
+ });
@@ -18,7 +18,12 @@ import { type CategoryTone, TONE_CLASSES, toneFor } from "./tone";
18
18
  export interface IconTileProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
19
19
  /** Defaults to a tone derived from `name`, or violet when there is no name. */
20
20
  tone?: CategoryTone;
21
- size?: "xs" | "sm" | "md" | "lg";
21
+ /**
22
+ * `xs` to `lg` (20 to 40px) sit in rows and cards. `xl` (64px) and `2xl`
23
+ * (80px) are for a page or profile header, where the tile is the thing's
24
+ * face rather than a list marker.
25
+ */
26
+ size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
22
27
  src?: string;
23
28
  icon?: React.ReactNode;
24
29
  /** The thing's name. Supplies the initials and the derived tone. */
@@ -32,6 +37,8 @@ const SIZE = {
32
37
  sm: "size-6 rounded-md text-[11px] [&_svg]:size-3.5",
33
38
  md: "size-8 rounded-md text-xs [&_svg]:size-4",
34
39
  lg: "size-10 rounded-lg text-sm [&_svg]:size-5",
40
+ xl: "size-16 rounded-2xl text-xl [&_svg]:size-8",
41
+ "2xl": "size-20 rounded-2xl text-2xl [&_svg]:size-10",
35
42
  } as const;
36
43
 
37
44
  /** Up to two initials: the first letters of the first two words, or of the one word. */
@@ -190,6 +190,12 @@ export type { PageShellProps } from "./page-shell";
190
190
  export { PageHeader } from "./page-header";
191
191
  export type { PageHeaderProps } from "./page-header";
192
192
 
193
+ export { Alert, AlertDescription, AlertTitle } from "./alert";
194
+ export type { AlertProps, AlertTone } from "./alert";
195
+
196
+ export { Pagination } from "./pagination";
197
+ export type { PaginationProps } from "./pagination";
198
+
193
199
  export { StatusPill } from "./status-pill";
194
200
  export type { StatusPillProps, StatusTone } from "./status-pill";
195
201
 
@@ -0,0 +1,51 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Info, TriangleAlert } from 'lucide-react'
3
+ import * as React from 'react'
4
+ import { Alert, AlertDescription, AlertTitle } from './alert'
5
+ import { Pagination } from './pagination'
6
+
7
+ const meta: Meta = {
8
+ title: 'Primitives/Pagination and Alert',
9
+ }
10
+ export default meta
11
+
12
+ function PagedTable({ pageCount }: { pageCount: number }) {
13
+ const [page, setPage] = React.useState(0)
14
+ return <Pagination page={page} pageCount={pageCount} onPageChange={setPage} />
15
+ }
16
+
17
+ export const PaginationLengths: StoryObj = {
18
+ render: () => (
19
+ <div className="flex flex-col gap-6 bg-background p-6 text-foreground">
20
+ <PagedTable pageCount={1} />
21
+ <PagedTable pageCount={4} />
22
+ <PagedTable pageCount={48} />
23
+ <PagedTable pageCount={1200} />
24
+ </div>
25
+ ),
26
+ }
27
+
28
+ export const AlertTones: StoryObj = {
29
+ render: () => (
30
+ <div className="flex max-w-xl flex-col gap-3 bg-background p-6 text-foreground">
31
+ <Alert icon={<Info />}>
32
+ <AlertTitle>Public events</AlertTitle>
33
+ <AlertDescription>Anyone with the link can join a public event.</AlertDescription>
34
+ </Alert>
35
+ <Alert tone="info" icon={<Info />}>
36
+ <AlertTitle>Applies from the next billing cycle</AlertTitle>
37
+ </Alert>
38
+ <Alert tone="success">
39
+ <AlertDescription>Changes saved.</AlertDescription>
40
+ </Alert>
41
+ <Alert tone="warning" icon={<TriangleAlert />}>
42
+ <AlertTitle>A_very_long_policy_identifier_without_any_spaces_that_has_to_wrap_inside_the_alert</AlertTitle>
43
+ <AlertDescription>Seats above the limit are removed when the cycle closes.</AlertDescription>
44
+ </Alert>
45
+ <Alert tone="danger" icon={<TriangleAlert />}>
46
+ <AlertTitle>Could not save the policy</AlertTitle>
47
+ <AlertDescription>The server refused the change. Try again.</AlertDescription>
48
+ </Alert>
49
+ </div>
50
+ ),
51
+ }
@@ -0,0 +1,90 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { Alert, AlertDescription, AlertTitle } from "./alert";
5
+ import { Pagination, pageSlots } from "./pagination";
6
+
7
+ const PALETTE = /\b(?:bg|text|border)-(?:slate|zinc|gray|neutral|blue|green|red|amber|emerald|black|white)(?:-\d|\b)/;
8
+
9
+ describe("pageSlots", () => {
10
+ it("lists every page when they fit", () => {
11
+ expect(pageSlots(0, 1)).toEqual([0]);
12
+ expect(pageSlots(3, 7)).toEqual([0, 1, 2, 3, 4, 5, 6]);
13
+ });
14
+
15
+ it("keeps the same number of slots as the current page moves", () => {
16
+ expect(pageSlots(0, 10)).toEqual([0, 1, 2, 3, 4, "gap-end", 9]);
17
+ expect(pageSlots(5, 10)).toEqual([0, "gap-start", 4, 5, 6, "gap-end", 9]);
18
+ expect(pageSlots(9, 10)).toEqual([0, "gap-start", 5, 6, 7, 8, 9]);
19
+ for (let page = 0; page < 40; page++) expect(pageSlots(page, 40)).toHaveLength(7);
20
+ });
21
+
22
+ it("clamps a page outside the range", () => {
23
+ expect(pageSlots(-3, 10)).toEqual(pageSlots(0, 10));
24
+ expect(pageSlots(99, 10)).toEqual(pageSlots(9, 10));
25
+ });
26
+ });
27
+
28
+ describe("Pagination", () => {
29
+ it("renders nothing for a single page", () => {
30
+ const { container } = render(<Pagination page={0} pageCount={1} onPageChange={vi.fn()} />);
31
+ expect(container).toBeEmptyDOMElement();
32
+ });
33
+
34
+ it("marks the current page and moves with previous, next and a number", async () => {
35
+ const user = userEvent.setup();
36
+ const onPageChange = vi.fn();
37
+ render(<Pagination page={4} pageCount={12} onPageChange={onPageChange} />);
38
+
39
+ expect(screen.getByRole("navigation", { name: "Pagination" })).toBeInTheDocument();
40
+ const current = screen.getByRole("button", { name: "Page 5" });
41
+ expect(current).toHaveAttribute("aria-current", "page");
42
+ expect(current.className).not.toMatch(PALETTE);
43
+
44
+ await user.click(screen.getByRole("button", { name: "Previous page" }));
45
+ await user.click(screen.getByRole("button", { name: "Next page" }));
46
+ await user.click(screen.getByRole("button", { name: "Page 12" }));
47
+ expect(onPageChange.mock.calls).toEqual([[3], [5], [11]]);
48
+ });
49
+
50
+ it("disables previous on the first page and next on the last", () => {
51
+ const { rerender } = render(<Pagination page={0} pageCount={3} onPageChange={vi.fn()} />);
52
+ expect(screen.getByRole("button", { name: "Previous page" })).toBeDisabled();
53
+ rerender(<Pagination page={2} pageCount={3} onPageChange={vi.fn()} />);
54
+ expect(screen.getByRole("button", { name: "Next page" })).toBeDisabled();
55
+ });
56
+
57
+ it("does not report the page that is already current", async () => {
58
+ const user = userEvent.setup();
59
+ const onPageChange = vi.fn();
60
+ render(<Pagination page={1} pageCount={3} onPageChange={onPageChange} />);
61
+ await user.click(screen.getByRole("button", { name: "Page 2" }));
62
+ expect(onPageChange).not.toHaveBeenCalled();
63
+ });
64
+ });
65
+
66
+ describe("Alert", () => {
67
+ it("paints a tone from the matched Brand status triple", () => {
68
+ render(
69
+ <Alert tone="warning">
70
+ <AlertTitle>Policy is changing</AlertTitle>
71
+ <AlertDescription>Seats above the limit are removed next cycle.</AlertDescription>
72
+ </Alert>,
73
+ );
74
+ const alert = screen.getByRole("alert");
75
+ expect(alert.className).toContain("bg-[var(--surface-warning-bg)]");
76
+ expect(alert.className).toContain("text-[var(--surface-warning-text)]");
77
+ expect(alert.className).not.toMatch(PALETTE);
78
+ });
79
+
80
+ it("uses role status for quiet tones and keeps an explicit role", () => {
81
+ const { rerender } = render(<Alert tone="info">Saved</Alert>);
82
+ expect(screen.getByRole("status")).toHaveTextContent("Saved");
83
+ rerender(
84
+ <Alert tone="neutral" role="note">
85
+ Note
86
+ </Alert>,
87
+ );
88
+ expect(screen.getByRole("note")).toHaveAttribute("data-tone", "neutral");
89
+ });
90
+ });
@@ -0,0 +1,117 @@
1
+ import { ChevronLeft, ChevronRight } from "lucide-react";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils";
4
+ import { buttonVariants } from "./button";
5
+
6
+ /**
7
+ * Page controls for a paged list or table: previous, the page numbers around
8
+ * the current one with the first and last always reachable, and next.
9
+ *
10
+ * Pages are zero-based in the API and one-based on screen. With one page or
11
+ * none there is nothing to choose, so it renders nothing.
12
+ */
13
+ export interface PaginationProps
14
+ extends Omit<React.HTMLAttributes<HTMLElement>, "onChange" | "children"> {
15
+ /** The current page, zero-based. */
16
+ page: number;
17
+ pageCount: number;
18
+ onPageChange: (page: number) => void;
19
+ /** Pages shown on each side of the current one before an ellipsis. */
20
+ siblings?: number;
21
+ }
22
+
23
+ type PageSlot = number | "gap-start" | "gap-end";
24
+
25
+ /**
26
+ * The visible slots for `page` of `pageCount`. The number of slots stays the
27
+ * same as the current page moves, so the controls do not shift under the
28
+ * pointer while a person clicks through.
29
+ */
30
+ export function pageSlots(page: number, pageCount: number, siblings = 1): PageSlot[] {
31
+ const last = pageCount - 1;
32
+ if (pageCount <= siblings * 2 + 5) return Array.from({ length: pageCount }, (_, i) => i);
33
+
34
+ const current = Math.min(Math.max(page, 0), last);
35
+ const start = Math.max(Math.min(current - siblings, last - siblings * 2 - 2), 2);
36
+ const end = Math.min(Math.max(current + siblings, siblings * 2 + 2), last - 2);
37
+
38
+ const slots: PageSlot[] = [0, start > 2 ? "gap-start" : 1];
39
+ for (let i = start; i <= end; i++) slots.push(i);
40
+ slots.push(end < last - 2 ? "gap-end" : last - 1, last);
41
+ return slots;
42
+ }
43
+
44
+ const pageButton = cn(buttonVariants({ variant: "ghost", size: "sm" }), "min-w-8 px-2 tabular-nums");
45
+
46
+ export function Pagination({
47
+ page,
48
+ pageCount,
49
+ onPageChange,
50
+ siblings = 1,
51
+ className,
52
+ "aria-label": ariaLabel = "Pagination",
53
+ ...rest
54
+ }: PaginationProps) {
55
+ if (pageCount <= 1) return null;
56
+
57
+ const last = pageCount - 1;
58
+ const current = Math.min(Math.max(page, 0), last);
59
+ const go = (next: number) => {
60
+ const target = Math.min(Math.max(next, 0), last);
61
+ if (target !== current) onPageChange(target);
62
+ };
63
+
64
+ return (
65
+ <nav aria-label={ariaLabel} className={cn("flex items-center", className)} {...rest}>
66
+ <ul className="flex flex-wrap items-center gap-1">
67
+ <li>
68
+ <button
69
+ type="button"
70
+ aria-label="Previous page"
71
+ disabled={current === 0}
72
+ onClick={() => go(current - 1)}
73
+ className={cn(pageButton, "gap-1 pl-1.5")}
74
+ >
75
+ <ChevronLeft aria-hidden="true" />
76
+ <span className="hidden sm:inline">Previous</span>
77
+ </button>
78
+ </li>
79
+ {pageSlots(current, pageCount, siblings).map((slot) =>
80
+ typeof slot === "number" ? (
81
+ <li key={slot}>
82
+ <button
83
+ type="button"
84
+ aria-label={`Page ${slot + 1}`}
85
+ aria-current={slot === current ? "page" : undefined}
86
+ onClick={() => go(slot)}
87
+ className={cn(
88
+ pageButton,
89
+ slot === current &&
90
+ "border-border bg-card font-semibold text-foreground hover:bg-card",
91
+ )}
92
+ >
93
+ {slot + 1}
94
+ </button>
95
+ </li>
96
+ ) : (
97
+ <li key={slot} aria-hidden="true" className="flex min-w-8 justify-center text-sm text-muted-foreground">
98
+ …
99
+ </li>
100
+ ),
101
+ )}
102
+ <li>
103
+ <button
104
+ type="button"
105
+ aria-label="Next page"
106
+ disabled={current === last}
107
+ onClick={() => go(current + 1)}
108
+ className={cn(pageButton, "gap-1 pr-1.5")}
109
+ >
110
+ <span className="hidden sm:inline">Next</span>
111
+ <ChevronRight aria-hidden="true" />
112
+ </button>
113
+ </li>
114
+ </ul>
115
+ </nav>
116
+ );
117
+ }
@@ -326,3 +326,23 @@ describe("SegmentedControl", () => {
326
326
  expect(screen.getByRole("radiogroup")).toHaveAttribute("id", "scope-control")
327
327
  })
328
328
  })
329
+
330
+ describe("SegmentedControl test ids", () => {
331
+ it("renders option and group test ids for end-to-end selectors", () => {
332
+ render(
333
+ <SegmentedControl
334
+ aria-label="Workbench view"
335
+ data-testid="workbench-tabs"
336
+ value="code"
337
+ onValueChange={vi.fn()}
338
+ options={[
339
+ { value: "code", label: "Code", testId: "workbench-tab-code" },
340
+ { value: "preview", label: "Preview", testId: "workbench-tab-preview" },
341
+ ]}
342
+ />,
343
+ )
344
+ expect(screen.getByTestId("workbench-tabs")).toHaveAttribute("role", "radiogroup")
345
+ expect(screen.getByTestId("workbench-tab-preview")).toHaveAttribute("aria-checked", "false")
346
+ expect(screen.getByTestId("workbench-tab-code")).toHaveAttribute("aria-checked", "true")
347
+ })
348
+ })
@@ -29,6 +29,8 @@ export interface SegmentedControlOption<T extends string = string> {
29
29
  label: React.ReactNode
30
30
  /** Rendered right of the label, typically a count or status pill. */
31
31
  adornment?: React.ReactNode
32
+ /** Rendered as the option button's `data-testid`, for end-to-end selectors. */
33
+ testId?: string
32
34
  }
33
35
 
34
36
  export interface SegmentedControlProps<T extends string = string>
@@ -43,6 +45,8 @@ export interface SegmentedControlProps<T extends string = string>
43
45
  * reads as a classic tab (used on the Team page)
44
46
  */
45
47
  variant?: "row" | "tabs"
48
+ /** Rendered as the group's `data-testid`. */
49
+ "data-testid"?: string
46
50
  }
47
51
 
48
52
  export function SegmentedControl<T extends string = string>({
@@ -93,6 +97,7 @@ export function SegmentedControl<T extends string = string>({
93
97
  id={rest.id}
94
98
  aria-label={rest["aria-label"]}
95
99
  aria-labelledby={rest["aria-labelledby"]}
100
+ data-testid={rest["data-testid"]}
96
101
  onKeyDown={handleKeyDown}
97
102
  className={cn(
98
103
  "flex gap-1",
@@ -116,6 +121,7 @@ export function SegmentedControl<T extends string = string>({
116
121
  type="button"
117
122
  role="radio"
118
123
  aria-checked={active}
124
+ data-testid={option.testId}
119
125
  tabIndex={focusable ? 0 : -1}
120
126
  onClick={() => {
121
127
  if (!active) onValueChange(option.value)