@calliopelabs/ui 0.7.0 → 0.12.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.
Files changed (124) hide show
  1. package/README.md +266 -3
  2. package/dist/appearance/AppearancePicker.js +4 -2
  3. package/dist/appearance/AppearanceProvider.d.ts +10 -2
  4. package/dist/appearance/AppearanceProvider.js +6 -2
  5. package/dist/appearance/AppearanceScript.d.ts +15 -4
  6. package/dist/appearance/AppearanceScript.js +11 -5
  7. package/dist/appearance/appearance.js +1 -1
  8. package/dist/appearance/boot.d.ts +13 -2
  9. package/dist/appearance/boot.js +35 -5
  10. package/dist/appearance/product.d.ts +13 -0
  11. package/dist/appearance/product.js +2 -2
  12. package/dist/appearance/runtime-brand.d.ts +106 -0
  13. package/dist/appearance/runtime-brand.js +420 -0
  14. package/dist/css/appearance.js +147 -0
  15. package/dist/css/calliope.css +1221 -0
  16. package/dist/css/components.css +729 -0
  17. package/dist/css/tokens.css +491 -0
  18. package/dist/data/DefinitionList.d.ts +16 -0
  19. package/dist/data/DefinitionList.js +9 -0
  20. package/dist/data/Stat.d.ts +25 -0
  21. package/dist/data/Stat.js +19 -0
  22. package/dist/data/Tag.d.ts +21 -0
  23. package/dist/data/Tag.js +18 -0
  24. package/dist/data/types.d.ts +3 -2
  25. package/dist/data/use-local-table.d.ts +36 -0
  26. package/dist/data/use-local-table.js +127 -0
  27. package/dist/index.d.ts +44 -3
  28. package/dist/index.js +40 -1
  29. package/dist/nav/Nav.d.ts +2 -1
  30. package/dist/nav/Nav.js +34 -15
  31. package/dist/nav/model.d.ts +21 -0
  32. package/dist/page/PageHeader.d.ts +5 -3
  33. package/dist/page/PageHeader.js +5 -4
  34. package/dist/page/Panel.d.ts +19 -0
  35. package/dist/page/Panel.js +12 -0
  36. package/dist/primitives/Avatar.d.ts +37 -0
  37. package/dist/primitives/Avatar.js +43 -0
  38. package/dist/primitives/Field.d.ts +33 -0
  39. package/dist/primitives/Field.js +30 -0
  40. package/dist/primitives/Segmented.d.ts +30 -0
  41. package/dist/primitives/Segmented.js +20 -0
  42. package/dist/primitives/Select.d.ts +40 -0
  43. package/dist/primitives/Select.js +47 -0
  44. package/dist/primitives/Switch.d.ts +7 -0
  45. package/dist/primitives/Switch.js +8 -0
  46. package/dist/primitives/Textarea.d.ts +3 -0
  47. package/dist/primitives/Textarea.js +6 -0
  48. package/dist/provider/messages.d.ts +430 -0
  49. package/dist/provider/messages.js +666 -0
  50. package/dist/shell/BrandMark.d.ts +25 -3
  51. package/dist/shell/BrandMark.js +34 -4
  52. package/dist/shell/RailPanel.js +2 -2
  53. package/dist/shell/RailTree.js +2 -2
  54. package/dist/shell/Sidebar.d.ts +9 -2
  55. package/dist/shell/Sidebar.js +10 -4
  56. package/dist/shell/UserMenu.d.ts +3 -1
  57. package/dist/shell/UserMenu.js +3 -11
  58. package/dist/shell/rail-boot.d.ts +28 -0
  59. package/dist/shell/rail-boot.js +45 -0
  60. package/dist/shell/rail-state.d.ts +6 -1
  61. package/dist/shell/rail-state.js +18 -2
  62. package/dist/styles/preset.css +4 -0
  63. package/dist/styles/theme.css +36 -13
  64. package/dist/theme/css-layer.d.ts +19 -0
  65. package/dist/theme/css-layer.js +171 -0
  66. package/dist/theme/css.d.ts +1 -0
  67. package/dist/theme/css.js +45 -9
  68. package/dist/workflow/ArtifactCard.d.ts +47 -0
  69. package/dist/workflow/ArtifactCard.js +45 -0
  70. package/dist/workflow/DocumentReview.d.ts +74 -0
  71. package/dist/workflow/DocumentReview.js +54 -0
  72. package/dist/workflow/FormReview.d.ts +53 -0
  73. package/dist/workflow/FormReview.js +83 -0
  74. package/dist/workflow/GateBadge.d.ts +11 -0
  75. package/dist/workflow/GateBadge.js +14 -0
  76. package/dist/workflow/MediaReview.d.ts +63 -0
  77. package/dist/workflow/MediaReview.js +166 -0
  78. package/dist/workflow/ReviewPanel.d.ts +41 -0
  79. package/dist/workflow/ReviewPanel.js +59 -0
  80. package/dist/workflow/RunTimeline.d.ts +25 -0
  81. package/dist/workflow/RunTimeline.js +56 -0
  82. package/dist/workflow/WorkflowCard.d.ts +39 -0
  83. package/dist/workflow/WorkflowCard.js +56 -0
  84. package/dist/workflow/WorkflowGallery.d.ts +43 -0
  85. package/dist/workflow/WorkflowGallery.js +69 -0
  86. package/dist/workflow/WorkflowQuestionCard.d.ts +18 -0
  87. package/dist/workflow/WorkflowQuestionCard.js +78 -0
  88. package/dist/workflow/canvas/WorkflowCanvas.d.ts +39 -0
  89. package/dist/workflow/canvas/WorkflowCanvas.js +648 -0
  90. package/dist/workflow/definition.d.ts +408 -0
  91. package/dist/workflow/definition.js +411 -0
  92. package/dist/workflow/editor/WorkflowDiff.d.ts +17 -0
  93. package/dist/workflow/editor/WorkflowDiff.js +25 -0
  94. package/dist/workflow/editor/WorkflowImportPreview.d.ts +19 -0
  95. package/dist/workflow/editor/WorkflowImportPreview.js +50 -0
  96. package/dist/workflow/editor/WorkflowInspector.d.ts +40 -0
  97. package/dist/workflow/editor/WorkflowInspector.js +349 -0
  98. package/dist/workflow/editor/WorkflowJsonView.d.ts +16 -0
  99. package/dist/workflow/editor/WorkflowJsonView.js +40 -0
  100. package/dist/workflow/editor/WorkflowPalette.d.ts +16 -0
  101. package/dist/workflow/editor/WorkflowPalette.js +36 -0
  102. package/dist/workflow/editor/WorkflowProblems.d.ts +20 -0
  103. package/dist/workflow/editor/WorkflowProblems.js +51 -0
  104. package/dist/workflow/editor/WorkflowWorkspace.d.ts +49 -0
  105. package/dist/workflow/editor/WorkflowWorkspace.js +154 -0
  106. package/dist/workflow/gallery.d.ts +21 -0
  107. package/dist/workflow/gallery.js +29 -0
  108. package/dist/workflow/layout.d.ts +49 -0
  109. package/dist/workflow/layout.js +162 -0
  110. package/dist/workflow/model.d.ts +50 -0
  111. package/dist/workflow/model.js +7 -0
  112. package/dist/workflow/palette.d.ts +34 -0
  113. package/dist/workflow/palette.js +70 -0
  114. package/dist/workflow/portable.d.ts +162 -0
  115. package/dist/workflow/portable.js +547 -0
  116. package/dist/workflow/problems.d.ts +38 -0
  117. package/dist/workflow/problems.js +409 -0
  118. package/dist/workflow/question.d.ts +26 -0
  119. package/dist/workflow/question.js +1 -0
  120. package/dist/workflow/run.d.ts +94 -0
  121. package/dist/workflow/run.js +31 -0
  122. package/dist/workflow/workspace.d.ts +33 -0
  123. package/dist/workflow/workspace.js +17 -0
  124. package/package.json +6 -1
@@ -0,0 +1,127 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { DEFAULT_PAGE_SIZE, PAGE_SIZES, } from "./types.js";
4
+ const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
5
+ function compareValues(a, b) {
6
+ if (a instanceof Date)
7
+ a = a.getTime();
8
+ if (b instanceof Date)
9
+ b = b.getTime();
10
+ if (typeof a === "string" && typeof b === "string")
11
+ return collator.compare(a, b);
12
+ if (typeof a === "number" && typeof b === "number")
13
+ return a - b;
14
+ if (typeof a === "boolean" && typeof b === "boolean")
15
+ return Number(a) - Number(b);
16
+ return collator.compare(String(a), String(b));
17
+ }
18
+ function toList(sort) {
19
+ if (!sort)
20
+ return [];
21
+ return Array.isArray(sort) ? [...sort] : [sort];
22
+ }
23
+ /**
24
+ * A TableController over rows already in memory, for DataTable: search,
25
+ * the app's filters, sort and paging, all over the whole set, so a page
26
+ * boundary is never wrong. For a list the server pages, implement the
27
+ * controller over the server (Astrolift's `useCursorTable`) instead.
28
+ */
29
+ export function useLocalTable(rows, options = {}) {
30
+ const { sortBy, initialSort, searchText, filter, onClearFilters, pageSizes, loading = false, error = null, retry, } = options;
31
+ const [search, setSearchState] = React.useState("");
32
+ const [sorts, setSorts] = React.useState(() => toList(initialSort));
33
+ const [pageSize, setPageSizeState] = React.useState(options.pageSize ?? DEFAULT_PAGE_SIZE);
34
+ const [page, setPage] = React.useState(0);
35
+ const matched = React.useMemo(() => {
36
+ const needle = search.trim().toLowerCase();
37
+ return rows.filter((row) => (!filter || filter(row)) &&
38
+ (!needle || !searchText || searchText(row).toLowerCase().includes(needle)));
39
+ }, [rows, search, searchText, filter]);
40
+ const sorted = React.useMemo(() => {
41
+ const active = sorts.filter((s) => sortBy?.[s.key]);
42
+ if (active.length === 0)
43
+ return matched;
44
+ return [...matched].sort((x, y) => {
45
+ for (const { key, dir } of active) {
46
+ const read = sortBy[key];
47
+ const [a, b] = [read(x), read(y)];
48
+ const aMissing = a === null || a === undefined;
49
+ const bMissing = b === null || b === undefined;
50
+ if (aMissing || bMissing) {
51
+ if (aMissing && bMissing)
52
+ continue;
53
+ return aMissing ? 1 : -1;
54
+ }
55
+ const order = compareValues(a, b);
56
+ if (order !== 0)
57
+ return dir === "asc" ? order : -order;
58
+ }
59
+ return 0;
60
+ });
61
+ }, [matched, sorts, sortBy]);
62
+ const pages = Math.max(1, Math.ceil(sorted.length / pageSize));
63
+ // Rows can shrink under the page on screen (a refetch, a filter): hold it in range.
64
+ const pageIndex = Math.min(page, pages - 1);
65
+ const pageRows = React.useMemo(() => sorted.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize), [sorted, pageIndex, pageSize]);
66
+ const isFiltered = Boolean(filter) || (Boolean(searchText) && search.trim() !== "");
67
+ const state = loading
68
+ ? "loading"
69
+ : error
70
+ ? "error"
71
+ : sorted.length > 0
72
+ ? "ready"
73
+ : isFiltered
74
+ ? "emptyFiltered"
75
+ : "empty";
76
+ const setSearch = React.useCallback((next) => {
77
+ setSearchState(next);
78
+ setPage(0);
79
+ }, []);
80
+ const setPageSize = React.useCallback((size) => {
81
+ setPageSizeState(size);
82
+ setPage(0);
83
+ }, []);
84
+ const toggleSort = React.useCallback((key, additive) => {
85
+ setSorts((current) => {
86
+ const at = current.findIndex((s) => s.key === key);
87
+ if (additive) {
88
+ if (at === -1)
89
+ return [...current, { key, dir: "asc" }];
90
+ return current.map((s, i) => i === at ? { key, dir: s.dir === "asc" ? "desc" : "asc" } : s);
91
+ }
92
+ const primary = current[0];
93
+ if (primary?.key === key)
94
+ return [{ key, dir: primary.dir === "asc" ? "desc" : "asc" }];
95
+ return [{ key, dir: "asc" }];
96
+ });
97
+ setPage(0);
98
+ }, []);
99
+ const clearFilters = React.useCallback(() => {
100
+ setSearch("");
101
+ onClearFilters?.();
102
+ }, [setSearch, onClearFilters]);
103
+ return {
104
+ rows: state === "ready" ? pageRows : [],
105
+ state,
106
+ error,
107
+ retry: retry ?? (() => { }),
108
+ totalCount: loading || error ? null : sorted.length,
109
+ pageIndex,
110
+ hasNext: pageIndex < pages - 1,
111
+ hasPrev: pageIndex > 0,
112
+ next: () => setPage(Math.min(pageIndex + 1, pages - 1)),
113
+ prev: () => setPage(Math.max(pageIndex - 1, 0)),
114
+ pageSize,
115
+ pageSizes: pageSizes ?? PAGE_SIZES,
116
+ setPageSize,
117
+ search,
118
+ setSearch,
119
+ searchEnabled: Boolean(searchText),
120
+ isStale: false,
121
+ sort: sorts.length === 0 ? undefined : sorts.length === 1 ? sorts[0] : sorts,
122
+ toggleSort,
123
+ sortEnabled: Boolean(sortBy && Object.keys(sortBy).length > 0),
124
+ isFiltered,
125
+ clearFilters,
126
+ };
127
+ }
package/dist/index.d.ts CHANGED
@@ -10,17 +10,19 @@ export { MIN_NON_TEXT_CONTRAST, MIN_TEXT_CONTRAST, contrastRatio, luminance, } f
10
10
  export { CORNERS, defineProduct, type BrandAccent, type Corners, type Product, type ProductAccent, type ProductConfig, type ProductTheme, } from "./appearance/product.js";
11
11
  export { astroliftBrand, chatStudioBrand, cykickBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "./appearance/brands.js";
12
12
  export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance, inkFor, isCustomAccent, modeFor, normalize, normalizePartial, parseCustomAccent, readAppearance, resolveAppearance, writeAppearance, type Appearance, type CustomAccent, type OrgAppearancePolicy, type ResolvedAppearance, } from "./appearance/appearance.js";
13
- export { appearanceScript, productStyles } from "./appearance/boot.js";
13
+ export { appearanceScript, bootConfig, productStyles, type BootConfig } from "./appearance/boot.js";
14
+ export { BRAND_ACCENT_KEY, brandStyleVars, brandStyles, htmlAttributes, resolveRuntimeBrand, withBrand, type ResolvedRuntimeBrand, type RuntimeBrand, } from "./appearance/runtime-brand.js";
14
15
  export { AppearanceScript, type AppearanceScriptProps } from "./appearance/AppearanceScript.js";
15
16
  export { AppearanceProvider, useAppearance, useDensity, useProduct, type AppearanceContextValue, type AppearanceProviderProps, } from "./appearance/AppearanceProvider.js";
16
17
  export { AppearancePicker, CustomAccentField, type CustomAccentFieldProps, } from "./appearance/AppearancePicker.js";
17
- export { activeFor, areaSwitcher, localizeNav, visibleNav, type CrumbSwitchOption, type NavActive, type NavArea, type NavFunction, type NavGroup, type NavIcon, type NavModel, } from "./nav/model.js";
18
+ export { activeFor, areaSwitcher, localizeNav, visibleNav, type CrumbSwitchOption, type NavActive, type NavArea, type NavBadge, type NavFunction, type NavGroup, type NavIcon, type NavModel, } from "./nav/model.js";
18
19
  export { Nav, type NavProps } from "./nav/Nav.js";
19
20
  export { AppShell, type AppShellProps } from "./shell/AppShell.js";
20
21
  export { Sidebar, type SidebarProps } from "./shell/Sidebar.js";
21
- export { BrandMark, type BrandMarkProps } from "./shell/BrandMark.js";
22
+ export { BrandMark, type BrandLogo, type BrandMarkProps } from "./shell/BrandMark.js";
22
23
  export { UserMenu, type UserMenuItem, type UserMenuProps } from "./shell/UserMenu.js";
23
24
  export { RailPanel, type RailPanelProps } from "./shell/RailPanel.js";
25
+ export { railAttribute, railStyles, type RailBoot } from "./shell/rail-boot.js";
24
26
  export { RailTree, type RailTreeGroup, type RailTreeItem, type RailTreeProps, } from "./shell/RailTree.js";
25
27
  export { useRailState, useRailWidth } from "./shell/rail-state.js";
26
28
  export { PageFrame, type PageFrameProps } from "./page/PageFrame.js";
@@ -28,9 +30,14 @@ export { PageHeader, type PageHeaderProps } from "./page/PageHeader.js";
28
30
  export { Breadcrumbs, type BreadcrumbsProps, type Crumb } from "./page/Breadcrumbs.js";
29
31
  export { TabNav, type TabNavItem, type TabNavProps } from "./page/TabNav.js";
30
32
  export { Section, type SectionProps } from "./page/Section.js";
33
+ export { Panel, type PanelProps } from "./page/Panel.js";
31
34
  export { DataTable, type DataTableProps } from "./data/DataTable.js";
32
35
  export { List, ListItem, type ListItemProps, type ListProps } from "./data/List.js";
33
36
  export { useRowSelection, type RowSelection } from "./data/use-row-selection.js";
37
+ export { useLocalTable, type LocalTableOptions, type SortValue } from "./data/use-local-table.js";
38
+ export { Stat, StatGrid, type StatProps } from "./data/Stat.js";
39
+ export { Tag, TagList, type TagProps } from "./data/Tag.js";
40
+ export { DefinitionList, type Definition, type DefinitionListProps, } from "./data/DefinitionList.js";
34
41
  export { DEFAULT_PAGE_SIZE, PAGE_SIZES, type Column, type EmptyStateSpec, type PageState, type SortDirection, type SortState, type TableController, } from "./data/types.js";
35
42
  export { StatusBadge, type StatusBadgeProps } from "./status/StatusBadge.js";
36
43
  export { StatusDot, type StatusDotProps } from "./status/StatusDot.js";
@@ -45,7 +52,41 @@ export { ConfirmDialog, type ConfirmDialogProps, type ConfirmReason, } from "./o
45
52
  export { Button, buttonVariants, type ButtonProps } from "./primitives/Button.js";
46
53
  export { Input } from "./primitives/Input.js";
47
54
  export { Checkbox } from "./primitives/Checkbox.js";
55
+ export { Textarea } from "./primitives/Textarea.js";
56
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, type SelectOption, type SelectProps, } from "./primitives/Select.js";
57
+ export { Switch, type SwitchProps } from "./primitives/Switch.js";
58
+ export { Avatar, AvatarStack, initials, type AvatarProps, type AvatarSize, type AvatarStackPerson, type AvatarStackProps, type AvatarSurface, } from "./primitives/Avatar.js";
59
+ export { Segmented, type SegmentedOption, type SegmentedProps } from "./primitives/Segmented.js";
60
+ export { Field, type FieldControlProps, type FieldProps } from "./primitives/Field.js";
48
61
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
49
62
  export { Popover, PopoverAnchor, PopoverContent, PopoverHeader, PopoverTrigger, } from "./primitives/Popover.js";
50
63
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
51
64
  export { Skeleton } from "./primitives/Skeleton.js";
65
+ export type { WorkflowAction, WorkflowAvailability, WorkflowModule, WorkflowPermissions, WorkflowScope, WorkflowStatus, WorkflowSummary, } from "./workflow/model.js";
66
+ export { WorkflowCard, WorkflowCardSkeleton, type WorkflowCardActions, type WorkflowCardProps, } from "./workflow/WorkflowCard.js";
67
+ export { WorkflowQuestionCard, type WorkflowQuestionCardProps, } from "./workflow/WorkflowQuestionCard.js";
68
+ export type { WorkflowQuestion, WorkflowQuestionField, WorkflowQuestionState, WorkflowQuestionAnswers, WorkflowQuestionSubmission, } from "./workflow/question.js";
69
+ export { ANY_FILTER, DEFAULT_WORKFLOW_FILTERS, filterWorkflows, isFiltered, type WorkflowGalleryFilters, } from "./workflow/gallery.js";
70
+ export { WorkflowGallery, type WorkflowGalleryProps, type WorkflowGalleryView, } from "./workflow/WorkflowGallery.js";
71
+ export { ARTIFACT_TONE, GATE_TONE, RUN_STAGE_TONE, VERDICT_TONE, type Artifact, type ArtifactStatus, type Gate, type GateStatus, type ReviewComment, type ReviewDecision, type ReviewVerdict, type RunStage, type RunStageStatus, type RunValue, } from "./workflow/run.js";
72
+ export { RunTimeline, type RunTimelineProps } from "./workflow/RunTimeline.js";
73
+ export { GateBadge, type GateBadgeProps } from "./workflow/GateBadge.js";
74
+ export { ArtifactCard, ArtifactList, type ArtifactCardProps, type ArtifactListProps, } from "./workflow/ArtifactCard.js";
75
+ export { ReviewPanel, type ReviewPanelProps } from "./workflow/ReviewPanel.js";
76
+ export { MediaReview, formatTimecode, type MediaAnchor, type MediaCaptionTrack, type MediaComment, type MediaRegion, type MediaReviewProps, } from "./workflow/MediaReview.js";
77
+ export { DocumentReview, type DocumentAnnotation, type DocumentBlock, type DocumentPage, type DocumentReviewProps, type DocumentSegment, type DocumentSignOff, type SignOffStatus, } from "./workflow/DocumentReview.js";
78
+ export { FormReview, type FormField, type FormFieldBox, type FormFieldStatus, type FormReviewProps, type FormSourcePage, } from "./workflow/FormReview.js";
79
+ export { IN, LOOP_IN, LOOP_OUT, OUT, WORKFLOW_DEFINITION_VERSION, WORKFLOW_NODE_KINDS, WORKFLOW_SELECTORS, addNode, canConnect, createNode, dataTypes, downstream, edgeId, exitLabel, exitsOf, findFact, findGateKind, findPreset, freshId, inputsOf, isJudge, moveNode, operatorsFor, outputsOf, portOf, removeEdge, removeNode, startOf, typeAccepts, updateEdge, updateNode, upstream, withFlow, type WorkflowCase, type WorkflowCheckNode, type WorkflowClassifierNode, type WorkflowClassifierOption, type WorkflowCondition, type WorkflowConditionNode, type WorkflowConnectReason, type WorkflowConnectResult, type WorkflowConnection, type WorkflowDataType, type WorkflowDefinition, type WorkflowEdge, type WorkflowEvaluatorNode, type WorkflowFact, type WorkflowFactType, type WorkflowFanoutNode, type WorkflowFlow, type WorkflowGateField, type WorkflowGateKind, type WorkflowGateNode, type WorkflowGatePolicy, type WorkflowGotoNode, type WorkflowJoinNode, type WorkflowJudgeNode, type WorkflowModuleSpec, type WorkflowNode, type WorkflowNodeKind, type WorkflowNodeOf, type WorkflowNodeTemplate, type WorkflowOperator, type WorkflowOption, type WorkflowPort, type WorkflowPosition, type WorkflowRegistry, type WorkflowSelection, type WorkflowSelector, type WorkflowStepNode, type WorkflowStepPreset, type WorkflowSubflowNode, type WorkflowVariant, type WorkflowVariantsNode, } from "./workflow/definition.js";
80
+ export { WORKFLOW_PROBLEM_CODES, blockingProblems, checkDefinition, definitionAvailability, nodeAvailability, type WorkflowProblem, type WorkflowProblemCode, type WorkflowProblemLevel, } from "./workflow/problems.js";
81
+ export { WORKFLOW_PACKAGE_FORMAT, WORKFLOW_PACKAGE_VERSION, diffDefinitions, exportWorkflowPackage, previewWorkflowImport, readWorkflowDefinition, readWorkflowPackage, requirementsOf, sameDefinition, type WorkflowDefinitionDiff, type WorkflowDiffEdge, type WorkflowDiffItem, type WorkflowDisabledNode, type WorkflowFlowDiff, type WorkflowImportSummary, type WorkflowModuleGap, type WorkflowPackage, type WorkflowReadError, type WorkflowReadResult, type WorkflowRequirements, } from "./workflow/portable.js";
82
+ export { NODE_HEADER, NODE_WIDTH, PORT_ROW, arrangeFlow, layoutFlow, nodeSize, portRows, ranksOf, regionsOf, type WorkflowRegion, } from "./workflow/layout.js";
83
+ export { insertPaletteItem, paletteOf, type WorkflowPaletteGroup, type WorkflowPaletteItem, } from "./workflow/palette.js";
84
+ export { READABLE_ZOOM, WorkflowCanvas, type WorkflowCanvasProps, } from "./workflow/canvas/WorkflowCanvas.js";
85
+ export { WorkflowPalette, type WorkflowPaletteProps } from "./workflow/editor/WorkflowPalette.js";
86
+ export { WorkflowProblems, problemTarget, type WorkflowProblemsProps, } from "./workflow/editor/WorkflowProblems.js";
87
+ export { ConditionEditor, GatePolicyEditor, WorkflowInspector, type WorkflowInspectorProps, } from "./workflow/editor/WorkflowInspector.js";
88
+ export { WorkflowJsonView, type WorkflowJsonViewProps, } from "./workflow/editor/WorkflowJsonView.js";
89
+ export { documentStatus, type WorkflowDocumentStatus, type WorkflowRevision, } from "./workflow/workspace.js";
90
+ export { WorkflowDiff, type WorkflowDiffProps } from "./workflow/editor/WorkflowDiff.js";
91
+ export { WorkflowImportPreview, type WorkflowImportPreviewProps, } from "./workflow/editor/WorkflowImportPreview.js";
92
+ export { WorkflowWorkspace, type WorkflowWorkspaceProps, } from "./workflow/editor/WorkflowWorkspace.js";
package/dist/index.js CHANGED
@@ -13,7 +13,8 @@ export { MIN_NON_TEXT_CONTRAST, MIN_TEXT_CONTRAST, contrastRatio, luminance, } f
13
13
  export { CORNERS, defineProduct, } from "./appearance/product.js";
14
14
  export { astroliftBrand, chatStudioBrand, cykickBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "./appearance/brands.js";
15
15
  export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance, inkFor, isCustomAccent, modeFor, normalize, normalizePartial, parseCustomAccent, readAppearance, resolveAppearance, writeAppearance, } from "./appearance/appearance.js";
16
- export { appearanceScript, productStyles } from "./appearance/boot.js";
16
+ export { appearanceScript, bootConfig, productStyles } from "./appearance/boot.js";
17
+ export { BRAND_ACCENT_KEY, brandStyleVars, brandStyles, htmlAttributes, resolveRuntimeBrand, withBrand, } from "./appearance/runtime-brand.js";
17
18
  export { AppearanceScript } from "./appearance/AppearanceScript.js";
18
19
  export { AppearanceProvider, useAppearance, useDensity, useProduct, } from "./appearance/AppearanceProvider.js";
19
20
  export { AppearancePicker, CustomAccentField, } from "./appearance/AppearancePicker.js";
@@ -26,6 +27,7 @@ export { Sidebar } from "./shell/Sidebar.js";
26
27
  export { BrandMark } from "./shell/BrandMark.js";
27
28
  export { UserMenu } from "./shell/UserMenu.js";
28
29
  export { RailPanel } from "./shell/RailPanel.js";
30
+ export { railAttribute, railStyles } from "./shell/rail-boot.js";
29
31
  export { RailTree, } from "./shell/RailTree.js";
30
32
  export { useRailState, useRailWidth } from "./shell/rail-state.js";
31
33
  // Page
@@ -34,10 +36,15 @@ export { PageHeader } from "./page/PageHeader.js";
34
36
  export { Breadcrumbs } from "./page/Breadcrumbs.js";
35
37
  export { TabNav } from "./page/TabNav.js";
36
38
  export { Section } from "./page/Section.js";
39
+ export { Panel } from "./page/Panel.js";
37
40
  // Data
38
41
  export { DataTable } from "./data/DataTable.js";
39
42
  export { List, ListItem } from "./data/List.js";
40
43
  export { useRowSelection } from "./data/use-row-selection.js";
44
+ export { useLocalTable } from "./data/use-local-table.js";
45
+ export { Stat, StatGrid } from "./data/Stat.js";
46
+ export { Tag, TagList } from "./data/Tag.js";
47
+ export { DefinitionList, } from "./data/DefinitionList.js";
41
48
  export { DEFAULT_PAGE_SIZE, PAGE_SIZES, } from "./data/types.js";
42
49
  // Status
43
50
  export { StatusBadge } from "./status/StatusBadge.js";
@@ -56,7 +63,39 @@ export { ConfirmDialog, } from "./overlays/ConfirmDialog.js";
56
63
  export { Button, buttonVariants } from "./primitives/Button.js";
57
64
  export { Input } from "./primitives/Input.js";
58
65
  export { Checkbox } from "./primitives/Checkbox.js";
66
+ export { Textarea } from "./primitives/Textarea.js";
67
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, } from "./primitives/Select.js";
68
+ export { Switch } from "./primitives/Switch.js";
69
+ export { Avatar, AvatarStack, initials, } from "./primitives/Avatar.js";
70
+ export { Segmented } from "./primitives/Segmented.js";
71
+ export { Field } from "./primitives/Field.js";
59
72
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
60
73
  export { Popover, PopoverAnchor, PopoverContent, PopoverHeader, PopoverTrigger, } from "./primitives/Popover.js";
61
74
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
62
75
  export { Skeleton } from "./primitives/Skeleton.js";
76
+ export { WorkflowCard, WorkflowCardSkeleton, } from "./workflow/WorkflowCard.js";
77
+ export { WorkflowQuestionCard, } from "./workflow/WorkflowQuestionCard.js";
78
+ export { ANY_FILTER, DEFAULT_WORKFLOW_FILTERS, filterWorkflows, isFiltered, } from "./workflow/gallery.js";
79
+ export { WorkflowGallery, } from "./workflow/WorkflowGallery.js";
80
+ export { ARTIFACT_TONE, GATE_TONE, RUN_STAGE_TONE, VERDICT_TONE, } from "./workflow/run.js";
81
+ export { RunTimeline } from "./workflow/RunTimeline.js";
82
+ export { GateBadge } from "./workflow/GateBadge.js";
83
+ export { ArtifactCard, ArtifactList, } from "./workflow/ArtifactCard.js";
84
+ export { ReviewPanel } from "./workflow/ReviewPanel.js";
85
+ export { MediaReview, formatTimecode, } from "./workflow/MediaReview.js";
86
+ export { DocumentReview, } from "./workflow/DocumentReview.js";
87
+ export { FormReview, } from "./workflow/FormReview.js";
88
+ export { IN, LOOP_IN, LOOP_OUT, OUT, WORKFLOW_DEFINITION_VERSION, WORKFLOW_NODE_KINDS, WORKFLOW_SELECTORS, addNode, canConnect, createNode, dataTypes, downstream, edgeId, exitLabel, exitsOf, findFact, findGateKind, findPreset, freshId, inputsOf, isJudge, moveNode, operatorsFor, outputsOf, portOf, removeEdge, removeNode, startOf, typeAccepts, updateEdge, updateNode, upstream, withFlow, } from "./workflow/definition.js";
89
+ export { WORKFLOW_PROBLEM_CODES, blockingProblems, checkDefinition, definitionAvailability, nodeAvailability, } from "./workflow/problems.js";
90
+ export { WORKFLOW_PACKAGE_FORMAT, WORKFLOW_PACKAGE_VERSION, diffDefinitions, exportWorkflowPackage, previewWorkflowImport, readWorkflowDefinition, readWorkflowPackage, requirementsOf, sameDefinition, } from "./workflow/portable.js";
91
+ export { NODE_HEADER, NODE_WIDTH, PORT_ROW, arrangeFlow, layoutFlow, nodeSize, portRows, ranksOf, regionsOf, } from "./workflow/layout.js";
92
+ export { insertPaletteItem, paletteOf, } from "./workflow/palette.js";
93
+ export { READABLE_ZOOM, WorkflowCanvas, } from "./workflow/canvas/WorkflowCanvas.js";
94
+ export { WorkflowPalette } from "./workflow/editor/WorkflowPalette.js";
95
+ export { WorkflowProblems, problemTarget, } from "./workflow/editor/WorkflowProblems.js";
96
+ export { ConditionEditor, GatePolicyEditor, WorkflowInspector, } from "./workflow/editor/WorkflowInspector.js";
97
+ export { WorkflowJsonView, } from "./workflow/editor/WorkflowJsonView.js";
98
+ export { documentStatus, } from "./workflow/workspace.js";
99
+ export { WorkflowDiff } from "./workflow/editor/WorkflowDiff.js";
100
+ export { WorkflowImportPreview, } from "./workflow/editor/WorkflowImportPreview.js";
101
+ export { WorkflowWorkspace, } from "./workflow/editor/WorkflowWorkspace.js";
package/dist/nav/Nav.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface NavProps {
16
16
  * is a heading that folds, over its groups and rows; a flat area (Home,
17
17
  * Overview) is just its rows. Collapsed, each area is an icon, and hovering,
18
18
  * focusing or pressing it opens its rows as a flyout. The current row is the
19
- * one lit row: an accent tint and a 2px accent edge.
19
+ * one lit row: a tint and a 2px edge in the sidebar's marker colour
20
+ * (`--sidebar-primary`, the accent unless a brand sets one).
20
21
  */
21
22
  export declare function Nav({ nav, active, collapsed, onNavigate, className }: NavProps): React.JSX.Element;
package/dist/nav/Nav.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDownIcon } from "lucide-react";
4
4
  import * as React from "react";
5
5
  import { cn } from "../lib/cn.js";
@@ -10,20 +10,33 @@ import { useUI } from "../provider/UIProvider.js";
10
10
  * is a heading that folds, over its groups and rows; a flat area (Home,
11
11
  * Overview) is just its rows. Collapsed, each area is an icon, and hovering,
12
12
  * focusing or pressing it opens its rows as a flyout. The current row is the
13
- * one lit row: an accent tint and a 2px accent edge.
13
+ * one lit row: a tint and a 2px edge in the sidebar's marker colour
14
+ * (`--sidebar-primary`, the accent unless a brand sets one).
14
15
  */
15
16
  export function Nav({ nav, active, collapsed = false, onNavigate, className }) {
16
17
  return (_jsx("div", { className: cn("flex flex-col", collapsed ? "items-center gap-1" : "gap-2", className), children: nav.map((area) => collapsed ? (_jsx(CollapsedArea, { area: area, active: active, onNavigate: onNavigate }, area.key)) : (_jsx(ExpandedArea, { area: area, active: active, onNavigate: onNavigate }, area.key))) }));
17
18
  }
18
- function FunctionLink({ fn, isActive, onNavigate, }) {
19
+ /** A count past 99 reads "99+". */
20
+ function badgeText(value) {
21
+ return typeof value === "number" && value > 99 ? "99+" : String(value);
22
+ }
23
+ /** The visible badge; hidden from the accessibility tree, which hears `label` instead. */
24
+ function Badge({ badge, className }) {
25
+ return (_jsx("span", { "aria-hidden": true, className: cn("inline-flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full px-1 font-mono text-2xs leading-none font-medium tabular-nums", badge.tone === "attention"
26
+ ? "bg-sidebar-primary/25 text-sidebar-foreground ring-1 ring-sidebar-primary/70"
27
+ : "bg-sidebar-foreground/10 text-sidebar-foreground/80", className), children: badgeText(badge.value) }));
28
+ }
29
+ function FunctionLink({ fn, isActive, flyout, onNavigate, }) {
19
30
  const { Link } = useUI();
20
31
  const Icon = fn.icon;
21
32
  return (_jsxs(Link, { href: fn.href, onClick: onNavigate, "aria-current": isActive ? "page" : undefined, ...fn.attributes, className: cn("flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", isActive
22
- ? "bg-primary/15 font-medium text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
23
- : "text-sidebar-foreground/80 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground"), children: [Icon && _jsx(Icon, { className: "size-4 shrink-0 opacity-80", "aria-hidden": true }), _jsx("span", { className: "truncate", children: fn.label })] }));
33
+ ? flyout
34
+ ? "bg-primary/15 font-medium text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
35
+ : "bg-sidebar-primary/15 font-medium text-sidebar-foreground shadow-[inset_2px_0_0_var(--sidebar-primary)]"
36
+ : "text-sidebar-foreground/80 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground"), children: [Icon && _jsx(Icon, { className: "size-4 shrink-0 opacity-80", "aria-hidden": true }), _jsx("span", { className: "truncate", children: fn.label }), (fn.badge || fn.trailing) && " ", (fn.badge || fn.trailing) && (_jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [fn.trailing, fn.badge && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: "sr-only", children: fn.badge.label }), _jsx(Badge, { badge: fn.badge })] }))] }))] }));
24
37
  }
25
- function Rows({ area, active, onNavigate, }) {
26
- return (_jsx(_Fragment, { children: area.groups.map((group, i) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [group.label && (_jsx("div", { className: "px-2 pt-2 pb-1 text-2xs text-muted-foreground", children: group.label })), group.functions.map((fn) => (_jsx(FunctionLink, { fn: fn, isActive: active?.area === area.key && active.fn === fn.key, onNavigate: onNavigate }, fn.key)))] }, group.key ?? group.label ?? i))) }));
38
+ function Rows({ area, active, flyout = false, onNavigate, }) {
39
+ return (_jsx(_Fragment, { children: area.groups.map((group, i) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [group.label && (_jsx("div", { className: cn("px-2 pt-2 pb-1 text-2xs", flyout ? "text-muted-foreground" : "text-sidebar-muted-foreground"), children: group.label })), group.functions.map((fn) => (_jsx(FunctionLink, { fn: fn, isActive: active?.area === area.key && active.fn === fn.key, flyout: flyout, onNavigate: onNavigate }, fn.key)))] }, group.key ?? group.label ?? i))) }));
27
40
  }
28
41
  function ExpandedArea({ area, active, onNavigate, }) {
29
42
  const [open, setOpen] = React.useState(true);
@@ -31,12 +44,12 @@ function ExpandedArea({ area, active, onNavigate, }) {
31
44
  if (area.flat) {
32
45
  return (_jsx("div", { className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) }));
33
46
  }
34
- return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, "aria-controls": regionId, className: "flex h-7 w-full items-center justify-between rounded-lg px-2 text-2xs font-semibold tracking-wider text-muted-foreground uppercase hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: [area.label, _jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", !open && "-rotate-90"), "aria-hidden": true })] }), _jsx("div", { id: regionId, hidden: !open, className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) })] }));
47
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), "aria-expanded": open, "aria-controls": regionId, className: "flex h-7 w-full items-center justify-between rounded-lg px-2 text-2xs font-semibold tracking-wider text-sidebar-muted-foreground uppercase hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: [area.label, _jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", !open && "-rotate-90"), "aria-hidden": true })] }), _jsx("div", { id: regionId, hidden: !open, className: "flex flex-col gap-0.5", children: _jsx(Rows, { area: area, active: active, onNavigate: onNavigate }) })] }));
35
48
  }
36
49
  /** How long the pointer may be away from a flyout before it closes. */
37
50
  const FLYOUT_GRACE_MS = 150;
38
51
  function CollapsedArea({ area, active, onNavigate, }) {
39
- const { Link } = useUI();
52
+ const { Link, messages } = useUI();
40
53
  const [open, setOpen] = React.useState(false);
41
54
  const pointerOpened = React.useRef(false);
42
55
  const closeTimer = React.useRef(null);
@@ -52,12 +65,18 @@ function CollapsedArea({ area, active, onNavigate, }) {
52
65
  clearTimeout(timer.current);
53
66
  };
54
67
  }, []);
55
- const tile = (_jsx("span", { className: cn("flex size-8 items-center justify-center rounded-lg", isActive
56
- ? "bg-primary/15 text-sidebar-foreground shadow-[inset_2px_0_0_var(--primary)]"
57
- : "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: _jsx(Icon, { className: "size-4", "aria-hidden": true }) }));
68
+ const badged = area.groups.some((group) => group.functions.some((fn) => fn.badge));
69
+ const onlyBadge = only?.badge;
70
+ const tile = (_jsxs("span", { className: cn("relative flex size-8 items-center justify-center rounded-lg", isActive
71
+ ? "bg-sidebar-primary/15 text-sidebar-foreground shadow-[inset_2px_0_0_var(--sidebar-primary)]"
72
+ : "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: [_jsx(Icon, { className: "size-4", "aria-hidden": true }), onlyBadge ? (_jsx(Badge, { badge: onlyBadge, className: "absolute -top-1 -right-1.5 h-3.5 min-w-3.5 px-0.5" })) : (badged && (_jsx("span", { "aria-hidden": true, className: "absolute top-1 right-1 size-1.5 rounded-full bg-sidebar-primary ring-2 ring-sidebar" })))] }));
58
73
  // One row: the icon is that row's link, no flyout needed.
59
74
  if (only) {
60
- return (_jsx(Link, { href: only.href, onClick: onNavigate, "aria-label": area.flat ? only.label : area.label, title: area.flat ? only.label : area.label, "aria-current": isActive ? "page" : undefined, ...only.attributes, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: tile }));
75
+ return (_jsx(Link, { href: only.href, onClick: onNavigate, "aria-label": onlyBadge
76
+ ? messages.nav.withBadge(area.flat ? only.label : area.label, onlyBadge.label)
77
+ : area.flat
78
+ ? only.label
79
+ : area.label, title: area.flat ? only.label : area.label, "aria-current": isActive ? "page" : undefined, ...only.attributes, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: tile }));
61
80
  }
62
81
  const cancelClose = () => {
63
82
  if (closeTimer.current)
@@ -72,7 +91,7 @@ function CollapsedArea({ area, active, onNavigate, }) {
72
91
  if (!next)
73
92
  pointerOpened.current = false;
74
93
  setOpen(next);
75
- }, children: [_jsx(PopoverTrigger, { "aria-label": area.label, title: area.label, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", onPointerEnter: (event) => {
94
+ }, children: [_jsx(PopoverTrigger, { "aria-label": badged ? messages.nav.areaWithBadges(area.label) : area.label, title: area.label, className: "flex rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", onPointerEnter: (event) => {
76
95
  if (event.pointerType !== "mouse")
77
96
  return;
78
97
  cancelClose();
@@ -90,7 +109,7 @@ function CollapsedArea({ area, active, onNavigate, }) {
90
109
  }, onPointerEnter: cancelClose, onPointerLeave: (event) => {
91
110
  if (event.pointerType === "mouse")
92
111
  scheduleClose();
93
- }, children: [_jsx("div", { className: "px-2 pb-1 text-2xs font-semibold tracking-wider text-muted-foreground uppercase", children: area.label }), _jsx(Rows, { area: area, active: active, onNavigate: () => {
112
+ }, children: [_jsx("div", { className: "px-2 pb-1 text-2xs font-semibold tracking-wider text-muted-foreground uppercase", children: area.label }), _jsx(Rows, { area: area, active: active, flyout: true, onNavigate: () => {
94
113
  setOpen(false);
95
114
  onNavigate?.();
96
115
  } })] })] }));
@@ -12,6 +12,19 @@ export type NavIcon = React.ComponentType<{
12
12
  className?: string;
13
13
  "aria-hidden"?: boolean;
14
14
  }>;
15
+ /**
16
+ * A count or a short word beside a row: unread items, things needing
17
+ * attention, "new". `label` is what a screen reader hears in place of the
18
+ * visible value ("3 unread"), so say what it counts.
19
+ */
20
+ export interface NavBadge {
21
+ /** A count (shown as "99+" past 99) or a short word. */
22
+ value: number | string;
23
+ /** The accessible text: "3 unread", "2 need attention". */
24
+ label: string;
25
+ /** `attention` draws it in the sidebar's marker colour; `neutral` (the default) is quiet. */
26
+ tone?: "neutral" | "attention";
27
+ }
15
28
  export interface NavFunction {
16
29
  key: string;
17
30
  label: string;
@@ -27,6 +40,14 @@ export interface NavFunction {
27
40
  also?: readonly string[];
28
41
  /** Extra attributes on the row's link, such as a tour anchor (`data-tour`). */
29
42
  attributes?: Readonly<Record<`data-${string}`, string>>;
43
+ /** A count or short word after the label, in expanded rows and the collapsed flyout; collapsed, the area's icon carries a mark. */
44
+ badge?: NavBadge;
45
+ /**
46
+ * Anything else after the label: presence avatars, a status dot. Inside
47
+ * the row's link, so nothing interactive; decorative unless it names
48
+ * itself. Not shown on the collapsed icon.
49
+ */
50
+ trailing?: React.ReactNode;
30
51
  }
31
52
  export interface NavGroup {
32
53
  key?: string;
@@ -3,6 +3,8 @@ import { type Crumb } from "./Breadcrumbs.js";
3
3
  import { type TabNavItem } from "./TabNav.js";
4
4
  export interface PageHeaderProps {
5
5
  crumbs?: readonly Crumb[];
6
+ /** A short line above the title: the kind of thing (a project, a run) or the section it sits in. */
7
+ eyebrow?: React.ReactNode;
6
8
  title: React.ReactNode;
7
9
  /** A status badge beside the title. */
8
10
  status?: React.ReactNode;
@@ -28,7 +30,7 @@ export interface PageHeaderProps {
28
30
  }
29
31
  /**
30
32
  * Every page's header, in three rows (Astrolift spec 44 §4.4): the
31
- * breadcrumb, the title row with its status, context and actions, and at
32
- * most one row of tabs. Pure.
33
+ * breadcrumb, the title row (an optional eyebrow over the title) with its
34
+ * status, context and actions, and at most one row of tabs. Pure.
33
35
  */
34
- export declare function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel, tabsClassName, className, }: PageHeaderProps): React.JSX.Element;
36
+ export declare function PageHeader({ crumbs, eyebrow, title, status, context, description, primaryAction, menu, tabs, tabsLabel, tabsClassName, className, }: PageHeaderProps): React.JSX.Element;
@@ -5,9 +5,10 @@ import { Breadcrumbs } from "./Breadcrumbs.js";
5
5
  import { TabNav } from "./TabNav.js";
6
6
  /**
7
7
  * Every page's header, in three rows (Astrolift spec 44 §4.4): the
8
- * breadcrumb, the title row with its status, context and actions, and at
9
- * most one row of tabs. Pure.
8
+ * breadcrumb, the title row (an optional eyebrow over the title) with its
9
+ * status, context and actions, and at most one row of tabs. Pure.
10
10
  */
11
- export function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel = "Page", tabsClassName, className, }) {
12
- return (_jsxs("header", { "data-slot": "page-header", className: cn("flex min-w-0 flex-col gap-3", className), children: [crumbs && crumbs.length > 0 && _jsx(Breadcrumbs, { crumbs: crumbs }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [_jsx("h1", { className: "min-w-0 truncate font-head text-xl font-semibold", children: title }), status, context && (_jsx("span", { className: "min-w-0 truncate text-sm text-muted-foreground", children: context })), (primaryAction || menu) && (_jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [primaryAction, menu] }))] }), description && (_jsx("div", { className: "max-w-2xl text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description })), tabs && tabs.length > 0 && (_jsx(TabNav, { tabs: tabs, label: tabsLabel, className: cn("-mx-3 md:-mx-6", tabsClassName) }))] }));
11
+ export function PageHeader({ crumbs, eyebrow, title, status, context, description, primaryAction, menu, tabs, tabsLabel = "Page", tabsClassName, className, }) {
12
+ const heading = _jsx("h1", { className: "min-w-0 truncate font-head text-xl font-semibold", children: title });
13
+ return (_jsxs("header", { "data-slot": "page-header", className: cn("flex min-w-0 flex-col gap-3", className), children: [crumbs && crumbs.length > 0 && _jsx(Breadcrumbs, { crumbs: crumbs }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [eyebrow ? (_jsxs("hgroup", { className: "flex min-w-0 flex-col", children: [_jsx("p", { className: "truncate font-mono text-2xs font-medium tracking-wider text-muted-foreground uppercase", children: eyebrow }), heading] })) : (heading), status, context && (_jsx("span", { className: "min-w-0 truncate text-sm text-muted-foreground", children: context })), (primaryAction || menu) && (_jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [primaryAction, menu] }))] }), description && (_jsx("div", { className: "max-w-2xl text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description })), tabs && tabs.length > 0 && (_jsx(TabNav, { tabs: tabs, label: tabsLabel, className: cn("-mx-3 md:-mx-6", tabsClassName) }))] }));
13
14
  }
@@ -0,0 +1,19 @@
1
+ import type * as React from "react";
2
+ export interface PanelProps extends Omit<React.ComponentProps<"div">, "title"> {
3
+ /** A short heading inside the box. */
4
+ title?: React.ReactNode;
5
+ /** A line over the title: what kind of thing the panel holds. */
6
+ eyebrow?: React.ReactNode;
7
+ /** Controls beside the title (a link, a small button). */
8
+ actions?: React.ReactNode;
9
+ /** For the document outline. Defaults to h3, under a page's h1 and a Section's h2. */
10
+ headingLevel?: "h2" | "h3" | "h4";
11
+ /** No padding around the body, for a table or a list that runs to the edges. */
12
+ flush?: boolean;
13
+ }
14
+ /**
15
+ * The bordered surface of `PageFrame > Section > Panel`: a card with an
16
+ * optional heading row, padded by the density (`--panel-pad`). The second
17
+ * container level, so it never holds a Section or another Panel.
18
+ */
19
+ export declare function Panel({ title, eyebrow, actions, headingLevel, flush, className, children, ...props }: PanelProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ /**
4
+ * The bordered surface of `PageFrame > Section > Panel`: a card with an
5
+ * optional heading row, padded by the density (`--panel-pad`). The second
6
+ * container level, so it never holds a Section or another Panel.
7
+ */
8
+ export function Panel({ title, eyebrow, actions, headingLevel = "h3", flush = false, className, children, ...props }) {
9
+ const Heading = headingLevel;
10
+ const hasHeader = Boolean(title || eyebrow || actions);
11
+ return (_jsxs("div", { "data-slot": "panel", className: cn("flex min-w-0 flex-col overflow-hidden rounded-lg border bg-card text-card-foreground", className), ...props, children: [hasHeader && (_jsxs("div", { className: cn("flex min-w-0 items-start justify-between gap-3 px-(--panel-pad) pt-(--panel-pad)", flush && "border-b pb-3"), children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [eyebrow && (_jsx("p", { className: "truncate font-mono text-2xs font-medium tracking-wider text-muted-foreground uppercase", children: eyebrow })), title && (_jsx(Heading, { className: "text-sm font-medium [overflow-wrap:anywhere]", children: title }))] }), actions && _jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions })] })), _jsx("div", { className: cn("flex min-w-0 flex-col gap-3", !flush && "p-(--panel-pad)", !flush && hasHeader && "pt-3"), children: children })] }));
12
+ }
@@ -0,0 +1,37 @@
1
+ /** Up to two initials from a name, upper case; "?" for a name with none. */
2
+ export declare function initials(name: string): string;
3
+ export type AvatarSize = "sm" | "md" | "lg";
4
+ /** What the avatar's ring separates it from, in a stack. */
5
+ export type AvatarSurface = "background" | "card" | "sidebar";
6
+ export interface AvatarProps {
7
+ /** The person's name: the initials, and the accessible name unless `decorative`. */
8
+ name: string;
9
+ /** A picture; the initials show until it loads, and if it fails. */
10
+ src?: string;
11
+ size?: AvatarSize;
12
+ /** The name is already visible beside it: hide the avatar from assistive tech. */
13
+ decorative?: boolean;
14
+ className?: string;
15
+ }
16
+ /** A person: their picture, or their initials on a neutral disc. */
17
+ export declare function Avatar({ name, src, size, decorative, className }: AvatarProps): import("react").JSX.Element;
18
+ export interface AvatarStackPerson {
19
+ name: string;
20
+ src?: string;
21
+ /** Stable identity when names repeat. */
22
+ id?: string;
23
+ }
24
+ export interface AvatarStackProps {
25
+ people: readonly AvatarStackPerson[];
26
+ /** How many avatars show before "+n". Defaults to 3. */
27
+ max?: number;
28
+ size?: AvatarSize;
29
+ /** The surface behind the stack, for the ring between avatars. Defaults to the page background. */
30
+ surface?: AvatarSurface;
31
+ className?: string;
32
+ }
33
+ /**
34
+ * Several people, overlapping: who is here, who owns it. Read as one phrase
35
+ * ("Leo Mata, Ana Ruiz and 2 more"), from `messages.avatar.people`.
36
+ */
37
+ export declare function AvatarStack({ people, max, size, surface, className, }: AvatarStackProps): import("react").JSX.Element | null;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Avatar as AvatarPrimitive } from "radix-ui";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useUI } from "../provider/UIProvider.js";
6
+ /** Up to two initials from a name, upper case; "?" for a name with none. */
7
+ export function initials(name) {
8
+ return (name
9
+ .trim()
10
+ .split(/\s+/)
11
+ .map((part) => part[0])
12
+ .filter(Boolean)
13
+ .slice(0, 2)
14
+ .join("")
15
+ .toUpperCase() || "?");
16
+ }
17
+ const SIZE = {
18
+ sm: "size-5 text-2xs",
19
+ md: "size-7 text-xs",
20
+ lg: "size-9 text-sm",
21
+ };
22
+ const RING = {
23
+ background: "ring-2 ring-background",
24
+ card: "ring-2 ring-card",
25
+ sidebar: "ring-2 ring-sidebar",
26
+ };
27
+ /** A person: their picture, or their initials on a neutral disc. */
28
+ export function Avatar({ name, src, size = "md", decorative = false, className }) {
29
+ return (_jsxs(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, role: decorative ? undefined : "img", "aria-label": decorative ? undefined : name, "aria-hidden": decorative || undefined, title: decorative ? undefined : name, className: cn("relative flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted font-mono font-medium text-foreground select-none", SIZE[size], className), children: [src && (_jsx(AvatarPrimitive.Image, { src: src, alt: "", className: "size-full rounded-full object-cover" })), _jsx(AvatarPrimitive.Fallback, { children: initials(name) })] }));
30
+ }
31
+ /**
32
+ * Several people, overlapping: who is here, who owns it. Read as one phrase
33
+ * ("Leo Mata, Ana Ruiz and 2 more"), from `messages.avatar.people`.
34
+ */
35
+ export function AvatarStack({ people, max = 3, size = "md", surface = "background", className, }) {
36
+ const { messages } = useUI();
37
+ if (people.length === 0)
38
+ return null;
39
+ // "+1" would take the place of the one avatar it hides: show that one instead.
40
+ const shown = people.length > max + 1 ? people.slice(0, max) : people;
41
+ const more = people.length - shown.length;
42
+ return (_jsxs("div", { "data-slot": "avatar-stack", role: "img", "aria-label": messages.avatar.people(shown.map((p) => p.name), more), title: people.map((p) => p.name).join(", "), className: cn("flex min-w-0 items-center -space-x-1.5", className), children: [shown.map((person, i) => (_jsx(Avatar, { name: person.name, src: person.src, size: size, decorative: true, className: RING[surface] }, person.id ?? `${person.name}-${i}`))), more > 0 && (_jsxs("span", { "aria-hidden": true, className: cn("relative flex shrink-0 items-center justify-center rounded-full bg-secondary font-mono font-medium text-muted-foreground tabular-nums", SIZE[size], RING[surface]), children: ["+", more] }))] }));
43
+ }