@apliteni/apliteni-ui 0.136.0 → 0.138.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.136.0",
3
+ "version": "0.138.0",
4
4
 
5
5
  "workspaces": [
6
6
  "react"
package/react/README.md CHANGED
@@ -232,6 +232,8 @@ finds its rows by `data-dd-item`, which is in the props you spread.
232
232
  leave `open` out and the component keeps its own, starting from `defaultOpen`. `onSelect`
233
233
  reports the item's `value` and the item. Pass `ariaLabel` to a `select` dropdown: a listbox
234
234
  needs a name, and axe says so — the same reason every vanilla dropdown story passes one.
235
+ Pass `selectedValue` when the caller owns the selected row. Its tick stays on that value after
236
+ `onSelect` until the caller changes it.
235
237
 
236
238
  **`search` puts a field over the rows**, the same one the factory draws, and Guidelines /
237
239
  Component choice makes it a rule at ten options or any list fed by data:
@@ -307,6 +309,19 @@ once installed. In the repo there is no copy to resolve to, so `kit-alias.ts` po
307
309
  vitest and Storybook straight at `../src/` — which is why the class-name parity tests
308
310
  now compare against the working tree rather than the last published release.
309
311
 
312
+ ### Table appearance
313
+
314
+ `DataTable` takes the vanilla table hooks as props. `zebra`, `hover` (on unless `false`), `stack`,
315
+ `density`, `stickyHeader` and `pinnedIdentity` set the table modifiers. `rowState={(row) => 'target' | 'dead' | undefined}`
316
+ marks a row, `footer` takes totals rows, and `caption` names the table for assistive technology. A column takes
317
+ `rowHeader` and `cell` (`'title' | 'code' | 'long' | 'strong' | 'act'`). A `dead` row is muted, so put the word
318
+ that names its state in one of its cells.
319
+
320
+ ```tsx
321
+ <DataTable columns={columns} rows={rows} selectable={false} pager={false}
322
+ density="dense" zebra stack rowState={(r) => (r.id === landedOn ? 'target' : undefined)} />
323
+ ```
324
+
310
325
  ### Tables with another presentation of the same rows
311
326
 
312
327
  Pass `selectable={false}` to omit selection controls and their callbacks. Existing callers
@@ -963,6 +978,29 @@ instead of 32, with the same line drawn in the middle of it.
963
978
  ]} />
964
979
  ```
965
980
 
981
+ ## TopBar
982
+
983
+ `TopBar` is the band: the brand first, then your slots after the spacer. Fill `view`, `version`,
984
+ `theme` and `account` with `ViewSwitch`, `VersionSwitcher`, `ThemeToggle` and `AccountMenu`, or
985
+ leave one out. Leave out `account` for the signed-out bar. The brand is the Apliteni mark and
986
+ `word`, or pass your own `brand`. Put it above `AppShell` or on its own.
987
+
988
+ ```tsx
989
+ <TopBar
990
+ view={<ViewSwitch items={[{ id: 'deck', label: 'Deck' }, { id: 'text', label: 'Text' }]} active="deck" />}
991
+ version={<VersionSwitcher versions={versions} value={version} onChange={setVersion} />}
992
+ theme={<ThemeToggle />}
993
+ account={<AccountMenu name="Ada Lovelace" email="ada@apliteni.com" current="prefs" onSignOut={signOut} />} />
994
+ ```
995
+
996
+ `ViewSwitch` is a group of links, so it takes `renderLink` like `NavTabs`. The current view is
997
+ filled with the same accent fill as a `Segmented` choice and has `aria-current="page"`.
998
+ `VersionSwitcher` takes `{ value, label, badge }` versions. The button shows only the chosen
999
+ name. Each row shows a name, a badge and the selected tick. `AccountMenu` shows the
1000
+ avatar mark, the reader's name and address, the rows in `items` and Sign out. Both menus are the
1001
+ kit's `Dropdown`, so they share its keyboard and placement. `current` marks one account page in
1002
+ accent ink and defaults to `prefs`.
1003
+
966
1004
  ## NavTabs and Breadcrumbs
967
1005
 
968
1006
  `NavTabs` renders route links with the kit’s underline or pill presentation. Pass
@@ -281,6 +281,10 @@ type Column<T> = {
281
281
  * why: docs/components.md#dense-financial-tables
282
282
  */
283
283
  linked?: boolean;
284
+ /** Renders this column's body cells as row headers (`th scope="row"`), as the kit's compact table does. */
285
+ rowHeader?: boolean;
286
+ /** `title` gives the column the remaining width, `code` sets a handle or id in the table's small text, `long` is running text that gets its own line in a stacked row, `strong` bolds a figure, `act` right-aligns a row action. */
287
+ cell?: 'title' | 'code' | 'long' | 'strong' | 'act';
284
288
  };
285
289
  type TableSort<T> = {
286
290
  key: (keyof T & string) | undefined;
@@ -316,6 +320,16 @@ type PagerProps = {
316
320
  type DataTableProps<T> = {
317
321
  columns: Column<T>[];
318
322
  rows: T[];
323
+ /** The kit's `--zebra`, `--hover` (on unless `false`) and `--stack` hooks. A stacked table lays each row out as a block below the phone step, so no column scrolls out of reach. */
324
+ zebra?: boolean;
325
+ hover?: boolean;
326
+ stack?: boolean;
327
+ /** Marks a row the reader landed on (`target`) or a revoked one (`dead`). A dead row is muted, so put the word that names its state in one of its cells. */
328
+ rowState?: (row: T) => 'target' | 'dead' | undefined;
329
+ /** Table footer rows, such as totals: `<tr>` elements with a `th scope="row"` label. */
330
+ footer?: ReactNode;
331
+ /** A caption read by assistive technology only. */
332
+ caption?: string;
319
333
  dense?: boolean;
320
334
  density?: 'default' | 'dense' | 'compact';
321
335
  stickyHeader?: boolean;
@@ -345,7 +359,7 @@ type DataTableProps<T> = {
345
359
  declare function sortTableRows<T>(rows: T[], sort: TableSort<T>): T[];
346
360
  declare function DataTable<T extends {
347
361
  name: string;
348
- }>({ columns, rows, dense, density, stickyHeader, pinnedIdentity, scrollLabel, empty, pageSize, pageSizes, onPageSizeChange, pager, pagerLabel, loading, selectable, selected, onToggle, onTogglePage, sort: controlledSort, onSortChange, page: controlledPage, onPageChange, total, hasMore, }: DataTableProps<T>): react.JSX.Element;
362
+ }>({ columns, rows, zebra, hover, stack, rowState, footer, caption, dense, density, stickyHeader, pinnedIdentity, scrollLabel, empty, pageSize, pageSizes, onPageSizeChange, pager, pagerLabel, loading, selectable, selected, onToggle, onTogglePage, sort: controlledSort, onSortChange, page: controlledPage, onPageChange, total, hasMore, }: DataTableProps<T>): react.JSX.Element;
349
363
 
350
364
  type PaginationProps = {
351
365
  page?: number;
@@ -546,6 +560,8 @@ type DropdownProps = {
546
560
  label?: string;
547
561
  /** What the trigger shows. Given one, the trigger is yours to update. */
548
562
  value?: string;
563
+ /** When given, the caller owns the selected row, including after a pick. */
564
+ selectedValue?: string | number;
549
565
  placeholder?: string;
550
566
  /** Inferred from the items when it is left out, exactly as the factory infers it. */
551
567
  variant?: 'select' | 'menu';
@@ -584,7 +600,7 @@ type DropdownProps = {
584
600
  /** Draw the row yourself. `props` is everything the row needs; spread it. */
585
601
  row?: (item: DropdownItem, props: DropdownRowProps) => ReactNode;
586
602
  };
587
- declare function Dropdown({ label, value, placeholder, variant, items, sections, header, footer, triggerContent, triggerClass, disabled, chevron, align, direction, scroll, search, ariaLabel, id, panelClass, 'aria-describedby': describedBy, 'aria-invalid': invalid, open: openProp, defaultOpen, onOpenChange, onSelect, row, }: DropdownProps): react.JSX.Element;
603
+ declare function Dropdown({ label, value, selectedValue, placeholder, variant, items, sections, header, footer, triggerContent, triggerClass, disabled, chevron, align, direction, scroll, search, ariaLabel, id, panelClass, 'aria-describedby': describedBy, 'aria-invalid': invalid, open: openProp, defaultOpen, onOpenChange, onSelect, row, }: DropdownProps): react.JSX.Element;
588
604
 
589
605
  type BackLinkOwnProps = {
590
606
  /** The address. No address, or a `javascript:` one, and nothing is rendered. */
@@ -1187,6 +1203,69 @@ declare const AccentPicker: react.ForwardRefExoticComponent<Omit<HTMLAttributes<
1187
1203
  options?: readonly Accent[];
1188
1204
  } & react.RefAttributes<HTMLDivElement>>;
1189
1205
 
1206
+ type TopBarProps = Omit<ComponentPropsWithoutRef<'header'>, 'children'> & {
1207
+ /** The lockup. Left out, the bar draws the Apliteni mark and `word`. */
1208
+ brand?: ReactNode;
1209
+ /** The product word beside the mark, when `brand` is left out. */
1210
+ word?: string;
1211
+ brandHref?: string;
1212
+ /** A `ViewSwitch`, or nothing. */
1213
+ view?: ReactNode;
1214
+ /** A `VersionSwitcher`, or nothing. */
1215
+ version?: ReactNode;
1216
+ /** A `ThemeToggle`, or nothing. */
1217
+ theme?: ReactNode;
1218
+ /** An `AccountMenu`. Left out, the bar is the signed-out bar. */
1219
+ account?: ReactNode;
1220
+ };
1221
+ /** The band: brand first, the controls after the spacer. Each control is a slot. */
1222
+ declare function TopBar({ brand, word, brandHref, view, version, theme, account, className, ...rest }: TopBarProps): react.JSX.Element;
1223
+ type ViewSwitchItem = {
1224
+ id: string;
1225
+ label: string;
1226
+ href?: string;
1227
+ target?: string;
1228
+ };
1229
+ type ViewSwitchProps = {
1230
+ items: readonly ViewSwitchItem[];
1231
+ active: string;
1232
+ /** Names the group. */
1233
+ label?: string;
1234
+ /** Spread linkProps onto the router link, including children. */
1235
+ renderLink?: (item: ViewSwitchItem, linkProps: ComponentPropsWithoutRef<'a'>) => ReactNode;
1236
+ };
1237
+ /** The Deck and Text switch: links between views of one page, the current one filled. */
1238
+ declare function ViewSwitch({ items, active, label, renderLink }: ViewSwitchProps): react.JSX.Element;
1239
+ type Version = {
1240
+ value: string;
1241
+ label: string;
1242
+ badge?: 'live' | 'archive';
1243
+ };
1244
+ type VersionSwitcherProps = {
1245
+ versions: readonly Version[];
1246
+ value: string;
1247
+ onChange?: (value: string) => void;
1248
+ /** Names the list and the button. */
1249
+ label?: string;
1250
+ defaultOpen?: boolean;
1251
+ };
1252
+ /** The version menu: the button shows the chosen name, the open list ticks it. */
1253
+ declare function VersionSwitcher({ versions, value, onChange, label, defaultOpen }: VersionSwitcherProps): react.JSX.Element;
1254
+ type AccountMenuProps = {
1255
+ name: string;
1256
+ email: string;
1257
+ /** The rows above Sign out. Left out, the kit's account pages. */
1258
+ items?: DropdownEntry[];
1259
+ /** The current account page. One menu row takes the accent ink. */
1260
+ current?: string;
1261
+ signOutHref?: string;
1262
+ onSignOut?: () => void;
1263
+ onSelect?: (value: string | number | undefined) => void;
1264
+ defaultOpen?: boolean;
1265
+ };
1266
+ /** The avatar and its menu. */
1267
+ declare function AccountMenu({ name, email, items, current, signOutHref, onSignOut, onSelect, defaultOpen, }: AccountMenuProps): react.JSX.Element;
1268
+
1190
1269
  type NavTabBadge = string | number | {
1191
1270
  text: string | number;
1192
1271
  tone?: 'neutral' | 'accent' | 'live' | 'danger';
@@ -1234,4 +1313,4 @@ declare const Breadcrumbs: react.ForwardRefExoticComponent<Omit<Omit<react.Detai
1234
1313
  declare const PAGE_SIZES: readonly number[];
1235
1314
  declare const DEFAULT_PAGE_SIZE: number;
1236
1315
 
1237
- export { type Accent, AccentPicker, type AccentPickerProps, type AddFilter, AppShell, type AppShellProps, type AppShellSection, Avatar, type AvatarProps, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, BusyRegion, type BusyRegionProps, Button, type ButtonLinkProps, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, Chart, type ChartBridgeStep, type ChartBridgeTones, type ChartPeriod, type ChartProps, type ChartSeries, type ChartTone, CheckList, type CheckListProps, Checkbox, type CheckboxProps, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DatePicker, type DatePickerMode, type DatePickerPreset, type DatePickerProps, type DatePickerRange, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownAvatar, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, Feedback, type FeedbackPayload, type FeedbackProps, type FeedbackResult, type FeedbackSection, Field, type FieldControlProps, type FieldProps, FileDrop, type FileDropFile, type FileDropProps, type FileDropStatus, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Footer, type FooterColumn, type FooterLink, type FooterProps, type FooterSocial, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, type NativeButtonProps, type NavTabBadge, type NavTabItem, NavTabs, type NavTabsProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, Pill, type PillProps, RowIdentity, type RowIdentityProps, SearchField, type SearchFieldProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, SidebarNav, type SidebarNavBadge, type SidebarNavItem, type SidebarNavLeaf, type SidebarNavProps, type SidebarNavSection, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, StatusDot, type StatusDotProps, Success, SuccessCheck, type SuccessCheckProps, type SuccessCountdown, SuccessPanel, type SuccessPanelProps, type SuccessProps, Switch, type SwitchProps, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineEventKind, type TimelineProps, Toast, type ToastNotice, type ToastProps, Tooltip, type TooltipProps, sortTableRows, useToast };
1316
+ export { type Accent, AccentPicker, type AccentPickerProps, AccountMenu, type AccountMenuProps, type AddFilter, AppShell, type AppShellProps, type AppShellSection, Avatar, type AvatarProps, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, BusyRegion, type BusyRegionProps, Button, type ButtonLinkProps, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, Chart, type ChartBridgeStep, type ChartBridgeTones, type ChartPeriod, type ChartProps, type ChartSeries, type ChartTone, CheckList, type CheckListProps, Checkbox, type CheckboxProps, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DatePicker, type DatePickerMode, type DatePickerPreset, type DatePickerProps, type DatePickerRange, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownAvatar, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, Feedback, type FeedbackPayload, type FeedbackProps, type FeedbackResult, type FeedbackSection, Field, type FieldControlProps, type FieldProps, FileDrop, type FileDropFile, type FileDropProps, type FileDropStatus, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Footer, type FooterColumn, type FooterLink, type FooterProps, type FooterSocial, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, type NativeButtonProps, type NavTabBadge, type NavTabItem, NavTabs, type NavTabsProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, Pill, type PillProps, RowIdentity, type RowIdentityProps, SearchField, type SearchFieldProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, SidebarNav, type SidebarNavBadge, type SidebarNavItem, type SidebarNavLeaf, type SidebarNavProps, type SidebarNavSection, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, StatusDot, type StatusDotProps, Success, SuccessCheck, type SuccessCheckProps, type SuccessCountdown, SuccessPanel, type SuccessPanelProps, type SuccessProps, Switch, type SwitchProps, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineEventKind, type TimelineProps, Toast, type ToastNotice, type ToastProps, Tooltip, type TooltipProps, TopBar, type TopBarProps, type Version, VersionSwitcher, type VersionSwitcherProps, ViewSwitch, type ViewSwitchItem, type ViewSwitchProps, sortTableRows, useToast };
@@ -94,7 +94,7 @@ var cx = (...a) => a.filter(Boolean).join(" ");
94
94
  var Button = forwardRef3(function Button2({
95
95
  variant = "secondary",
96
96
  size = "md",
97
- icon: icon8,
97
+ icon: icon9,
98
98
  leading,
99
99
  iconRight,
100
100
  iconOnly,
@@ -158,7 +158,7 @@ var Button = forwardRef3(function Button2({
158
158
  className
159
159
  );
160
160
  const labelled = rest["aria-label"] != null || rest["aria-labelledby"] != null;
161
- const fallback = typeof children === "string" && children.trim() ? children.trim() : icon8;
161
+ const fallback = typeof children === "string" && children.trim() ? children.trim() : icon9;
162
162
  const named = iconOnly && !labelled && fallback ? { "aria-label": fallback, title: rest.title ?? fallback } : {};
163
163
  const Root = href === void 0 ? "button" : "a";
164
164
  return /* @__PURE__ */ jsxs3(
@@ -203,7 +203,7 @@ var Button = forwardRef3(function Button2({
203
203
  if (!blockKey(event)) onKeyUp?.(event);
204
204
  },
205
205
  children: [
206
- leading != null ? /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", style: { display: "inline-flex" }, children: leading }) : icon8 && /* @__PURE__ */ jsx4(Icon, { name: icon8 }),
206
+ leading != null ? /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", style: { display: "inline-flex" }, children: leading }) : icon9 && /* @__PURE__ */ jsx4(Icon, { name: icon9 }),
207
207
  !iconOnly && renderedChildren != null && /* @__PURE__ */ jsx4("span", { className: "ui-btn__label-slot", children: /* @__PURE__ */ jsx4("span", { className: "ui-btn__label", ref: labelRef, children: renderedChildren }) }),
208
208
  iconRight && /* @__PURE__ */ jsx4(Icon, { name: iconRight }),
209
209
  busy && /* @__PURE__ */ jsxs3("span", { className: "ui-btn__dots", "aria-hidden": "true", children: [
@@ -284,14 +284,14 @@ var StatusDot = forwardRef5(function StatusDot2({
284
284
  // src/primitives/Card.tsx
285
285
  import { icon as kitIcon, safeUrl } from "@apliteni/apliteni-ui";
286
286
  import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
287
- function Card({ title, sub, level = 2, variant, pad: pad2, icon: icon8, href, className, children }) {
287
+ function Card({ title, sub, level = 2, variant, pad: pad2, icon: icon9, href, className, children }) {
288
288
  const n = Number(level);
289
289
  const Heading = href != null ? "div" : `h${[2, 3, 4, 5, 6].includes(n) ? n : 2}`;
290
290
  const titled = title != null && title !== false && title !== "";
291
291
  const classes = ["ui-card", variant && `ui-card--${variant}`, pad2 && `ui-card--pad-${pad2}`, href != null && "ui-card--interactive", className].filter(Boolean).join(" ");
292
292
  const inner = /* @__PURE__ */ jsxs5(Fragment, { children: [
293
293
  titled && /* @__PURE__ */ jsxs5(Heading, { className: "ui-card__title", children: [
294
- icon8 && /* @__PURE__ */ jsx9("span", { className: "ui-card__icon", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: kitIcon(icon8) } }),
294
+ icon9 && /* @__PURE__ */ jsx9("span", { className: "ui-card__icon", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: kitIcon(icon9) } }),
295
295
  title
296
296
  ] }),
297
297
  sub != null && /* @__PURE__ */ jsx9("div", { className: "ui-card__sub", children: sub }),
@@ -303,9 +303,9 @@ function Card({ title, sub, level = 2, variant, pad: pad2, icon: icon8, href, cl
303
303
  // src/primitives/Callout.tsx
304
304
  import { calloutIcons, icon as kitIcon2 } from "@apliteni/apliteni-ui";
305
305
  import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
306
- function Callout({ variant, icon: icon8 = calloutIcons[variant ?? "neutral"], children, actions }) {
306
+ function Callout({ variant, icon: icon9 = calloutIcons[variant ?? "neutral"], children, actions }) {
307
307
  return /* @__PURE__ */ jsxs6("div", { className: `ui-callout${variant && variant !== "neutral" ? ` ui-callout--${variant}` : ""}`, role: variant === "danger" ? "alert" : void 0, children: [
308
- /* @__PURE__ */ jsx10("span", { className: "ui-callout__icon", dangerouslySetInnerHTML: { __html: kitIcon2(icon8) } }),
308
+ /* @__PURE__ */ jsx10("span", { className: "ui-callout__icon", dangerouslySetInnerHTML: { __html: kitIcon2(icon9) } }),
309
309
  /* @__PURE__ */ jsxs6("div", { className: "ui-callout__body", children: [
310
310
  children,
311
311
  actions != null && /* @__PURE__ */ jsx10("div", { className: "ui-toolbar ui-callout__actions", children: actions })
@@ -1351,7 +1351,7 @@ function rowId(uid, groups, item) {
1351
1351
  }
1352
1352
 
1353
1353
  // src/DataTable.tsx
1354
- import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef10, useState as useState7 } from "react";
1354
+ import { Fragment as Fragment6, useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef10, useState as useState7 } from "react";
1355
1355
  import { DEFAULT_PAGE_SIZE as DEFAULT_PAGE_SIZE2 } from "@apliteni/apliteni-ui";
1356
1356
 
1357
1357
  // src/Pagination.tsx
@@ -1579,7 +1579,8 @@ function Pagination({
1579
1579
  }
1580
1580
 
1581
1581
  // src/DataTable.tsx
1582
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1582
+ import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1583
+ var CELL_CLASS = { title: "ui-table__title", code: "ui-table__code", long: "ui-table__long", strong: "ui-table__num--strong", act: "ui-table__act" };
1583
1584
  var NO_SORT = { key: void 0, dir: -1 };
1584
1585
  function sortTableRows(rows, sort) {
1585
1586
  if (sort.key === void 0) return [...rows];
@@ -1589,6 +1590,12 @@ function sortTableRows(rows, sort) {
1589
1590
  function DataTable({
1590
1591
  columns,
1591
1592
  rows,
1593
+ zebra = false,
1594
+ hover = true,
1595
+ stack: stack2 = false,
1596
+ rowState,
1597
+ footer,
1598
+ caption,
1592
1599
  dense = false,
1593
1600
  density,
1594
1601
  stickyHeader = false,
@@ -1686,70 +1693,72 @@ function DataTable({
1686
1693
  strokeLinejoin: "round",
1687
1694
  "aria-hidden": "true",
1688
1695
  focusable: "false",
1689
- children: sort.key === k ? /* @__PURE__ */ jsx19("path", { d: "m8 10 4 4 4-4" }) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
1696
+ children: sort.key === k ? /* @__PURE__ */ jsx19("path", { d: "m8 10 4 4 4-4" }) : /* @__PURE__ */ jsxs15(Fragment7, { children: [
1690
1697
  /* @__PURE__ */ jsx19("path", { d: "m8 9 4-4 4 4" }),
1691
1698
  /* @__PURE__ */ jsx19("path", { d: "m8 15 4 4 4-4" })
1692
1699
  ] })
1693
1700
  }
1694
1701
  );
1702
+ const Wrap = scrollable ? "div" : Fragment6;
1695
1703
  const pageAllOn = selectable && slice.length > 0 && slice.every((r) => selected.has(r.name));
1696
1704
  const pageSomeOn = selectable && !pageAllOn && slice.some((r) => selected.has(r.name));
1697
- return /* @__PURE__ */ jsxs15(Fragment6, { children: [
1698
- /* @__PURE__ */ jsx19(
1699
- "div",
1705
+ return /* @__PURE__ */ jsxs15(Fragment7, { children: [
1706
+ /* @__PURE__ */ jsx19(Wrap, { ...scrollable ? { className: "ui-table-scroll", role: "region", "aria-label": scrollLabel, tabIndex: 0 } : {}, children: /* @__PURE__ */ jsxs15(
1707
+ "table",
1700
1708
  {
1701
- className: scrollable ? "ui-table-scroll" : void 0,
1702
- role: scrollable ? "region" : void 0,
1703
- "aria-label": scrollable ? scrollLabel : void 0,
1704
- tabIndex: scrollable ? 0 : void 0,
1705
- children: /* @__PURE__ */ jsxs15(
1706
- "table",
1707
- {
1708
- ref: table,
1709
- className: ["ui-table ui-table--hover", (density === "dense" || !density && dense) && "ui-table--dense", density === "compact" && "ui-table--compact", stickyHeader && "ui-table--sticky", pinnedIdentity && "ui-table--pinned"].filter(Boolean).join(" "),
1710
- "aria-busy": loading || void 0,
1711
- children: [
1712
- /* @__PURE__ */ jsx19("thead", { children: /* @__PURE__ */ jsxs15("tr", { children: [
1713
- selectable ? /* @__PURE__ */ jsx19("th", { scope: "col", className: "ui-table__selection", children: /* @__PURE__ */ jsx19(
1714
- "input",
1715
- {
1716
- type: "checkbox",
1717
- checked: pageAllOn,
1718
- "aria-label": "Select all rows on this page",
1719
- ref: (input) => {
1720
- if (input) input.indeterminate = pageSomeOn;
1721
- },
1722
- onChange: () => onTogglePage(slice.map((r) => r.name))
1723
- }
1724
- ) }) : null,
1725
- columns.map((c, columnIndex) => (
1726
- // The sort control is a real <button> inside the header cell. It used to be
1727
- // role="button" ON the <th>, which threw away the columnheader role and put
1728
- // aria-sort on a role that forbids it.
1729
- /* @__PURE__ */ jsx19(
1730
- "th",
1731
- {
1732
- scope: "col",
1733
- className: [pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.num && "ui-table__num", c.sortable && "rx-sortable"].filter(Boolean).join(" "),
1734
- "aria-sort": sort.key === c.key ? sort.dir === 1 ? "ascending" : "descending" : c.sortable && sortIsKnown ? "none" : void 0,
1735
- children: c.sortable ? (
1736
- // The label is its own box so it can truncate: in a pinned
1737
- // identity column on a phone the header is capped, and a bare
1738
- // text node would push the caret out of the cell instead.
1739
- /* @__PURE__ */ jsxs15("button", { type: "button", className: "rx-sort", onClick: () => onSort(c.key), children: [
1740
- /* @__PURE__ */ jsx19("span", { className: "rx-sort__label", children: c.label }),
1741
- caret(c.key)
1742
- ] })
1743
- ) : c.label
1744
- },
1745
- c.key
1746
- )
1747
- ))
1748
- ] }) }),
1749
- /* @__PURE__ */ jsxs15("tbody", { children: [
1750
- !slice.length && /* @__PURE__ */ jsx19("tr", { children: /* @__PURE__ */ jsx19("td", { colSpan: columns.length + (selectable ? 1 : 0), children: loading ? "Loading rows\u2026" : empty }) }),
1751
- slice.map((r) => /* @__PURE__ */ jsxs15("tr", { children: [
1752
- selectable ? /* @__PURE__ */ jsx19("td", { className: "ui-table__selection", children: /* @__PURE__ */ jsx19(
1709
+ ref: table,
1710
+ className: ["ui-table", hover && "ui-table--hover", zebra && "ui-table--zebra", stack2 && "ui-table--stack", (density === "dense" || !density && dense) && "ui-table--dense", density === "compact" && "ui-table--compact", stickyHeader && "ui-table--sticky", pinnedIdentity && "ui-table--pinned"].filter(Boolean).join(" "),
1711
+ "aria-busy": loading || void 0,
1712
+ role: stack2 ? "table" : void 0,
1713
+ children: [
1714
+ caption ? /* @__PURE__ */ jsx19("caption", { className: "ui-sr", children: caption }) : null,
1715
+ /* @__PURE__ */ jsx19("thead", { role: stack2 ? "rowgroup" : void 0, children: /* @__PURE__ */ jsxs15("tr", { role: stack2 ? "row" : void 0, children: [
1716
+ selectable ? /* @__PURE__ */ jsx19("th", { scope: "col", role: stack2 ? "columnheader" : void 0, className: "ui-table__selection", children: /* @__PURE__ */ jsx19(
1717
+ "input",
1718
+ {
1719
+ type: "checkbox",
1720
+ checked: pageAllOn,
1721
+ "aria-label": "Select all rows on this page",
1722
+ ref: (input) => {
1723
+ if (input) input.indeterminate = pageSomeOn;
1724
+ },
1725
+ onChange: () => onTogglePage(slice.map((r) => r.name))
1726
+ }
1727
+ ) }) : null,
1728
+ columns.map((c, columnIndex) => (
1729
+ // The sort control is a real <button> inside the header cell. It used to be
1730
+ // role="button" ON the <th>, which threw away the columnheader role and put
1731
+ // aria-sort on a role that forbids it.
1732
+ /* @__PURE__ */ jsx19(
1733
+ "th",
1734
+ {
1735
+ scope: "col",
1736
+ role: stack2 ? "columnheader" : void 0,
1737
+ className: [pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.num && "ui-table__num", c.sortable && "rx-sortable"].filter(Boolean).join(" "),
1738
+ "aria-sort": sort.key === c.key ? sort.dir === 1 ? "ascending" : "descending" : c.sortable && sortIsKnown ? "none" : void 0,
1739
+ children: c.sortable ? (
1740
+ // The label is its own box so it can truncate: in a pinned
1741
+ // identity column on a phone the header is capped, and a bare
1742
+ // text node would push the caret out of the cell instead.
1743
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "rx-sort", onClick: () => onSort(c.key), children: [
1744
+ /* @__PURE__ */ jsx19("span", { className: "rx-sort__label", children: c.label }),
1745
+ caret(c.key)
1746
+ ] })
1747
+ ) : c.label
1748
+ },
1749
+ c.key
1750
+ )
1751
+ ))
1752
+ ] }) }),
1753
+ /* @__PURE__ */ jsxs15("tbody", { role: stack2 ? "rowgroup" : void 0, children: [
1754
+ !slice.length && /* @__PURE__ */ jsx19("tr", { role: stack2 ? "row" : void 0, children: /* @__PURE__ */ jsx19("td", { role: stack2 ? "cell" : void 0, colSpan: columns.length + (selectable ? 1 : 0), children: loading ? "Loading rows\u2026" : empty }) }),
1755
+ slice.map((r) => /* @__PURE__ */ jsxs15(
1756
+ "tr",
1757
+ {
1758
+ role: stack2 ? "row" : void 0,
1759
+ className: rowState?.(r) ? `is-${rowState(r)}` : void 0,
1760
+ children: [
1761
+ selectable ? /* @__PURE__ */ jsx19("td", { role: stack2 ? "cell" : void 0, className: "ui-table__selection", children: /* @__PURE__ */ jsx19(
1753
1762
  "input",
1754
1763
  {
1755
1764
  type: "checkbox",
@@ -1758,14 +1767,29 @@ function DataTable({
1758
1767
  onChange: () => onToggle(r.name)
1759
1768
  }
1760
1769
  ) }) : null,
1761
- columns.map((c, columnIndex) => /* @__PURE__ */ jsx19("td", { className: [c.num && "ui-table__num", pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.linked && "ui-table__linked"].filter(Boolean).join(" "), children: c.render ? c.render(r) : String(r[c.key]) }, c.key))
1762
- ] }, r.name))
1763
- ] })
1764
- ]
1765
- }
1766
- )
1770
+ columns.map((c, columnIndex) => {
1771
+ const Cell = c.rowHeader ? "th" : "td";
1772
+ return /* @__PURE__ */ jsx19(
1773
+ Cell,
1774
+ {
1775
+ scope: c.rowHeader ? "row" : void 0,
1776
+ role: stack2 ? c.rowHeader ? "rowheader" : "cell" : void 0,
1777
+ "data-label": stack2 && c.num && typeof c.label === "string" ? c.label : void 0,
1778
+ className: [c.num && "ui-table__num", c.cell && CELL_CLASS[c.cell], pinnedIdentity && columnIndex === 0 && "ui-table__identity", c.linked && "ui-table__linked"].filter(Boolean).join(" ") || void 0,
1779
+ children: c.render ? c.render(r) : String(r[c.key])
1780
+ },
1781
+ c.key
1782
+ );
1783
+ })
1784
+ ]
1785
+ },
1786
+ r.name
1787
+ ))
1788
+ ] }),
1789
+ footer ? /* @__PURE__ */ jsx19("tfoot", { role: stack2 ? "rowgroup" : void 0, children: footer }) : null
1790
+ ]
1767
1791
  }
1768
- ),
1792
+ ) }),
1769
1793
  pager ? (
1770
1794
  // The strip that pages a table may not be wider than the rows it pages (#504).
1771
1795
  // The two are siblings, and no selector sizes one to the other, so the width is
@@ -1858,12 +1882,12 @@ function Denied({
1858
1882
  title = "You don\u2019t have access",
1859
1883
  sub,
1860
1884
  need,
1861
- icon: icon8 = "lock",
1885
+ icon: icon9 = "lock",
1862
1886
  className,
1863
1887
  children
1864
1888
  }) {
1865
1889
  return /* @__PURE__ */ jsxs16("div", { className: cx4("ui-denied", className), children: [
1866
- /* @__PURE__ */ jsx20("div", { className: "ui-denied__seal", children: /* @__PURE__ */ jsx20(Icon, { name: icon8 }) }),
1890
+ /* @__PURE__ */ jsx20("div", { className: "ui-denied__seal", children: /* @__PURE__ */ jsx20(Icon, { name: icon9 }) }),
1867
1891
  /* @__PURE__ */ jsx20("div", { className: "ui-denied__title", children: title }),
1868
1892
  sub && /* @__PURE__ */ jsx20("div", { className: "ui-denied__sub", children: sub }),
1869
1893
  need && /* @__PURE__ */ jsxs16("div", { className: "ui-denied__need", children: [
@@ -2347,7 +2371,7 @@ function DatePicker({
2347
2371
 
2348
2372
  // src/Dropdown.tsx
2349
2373
  import {
2350
- Fragment as Fragment7,
2374
+ Fragment as Fragment8,
2351
2375
  useCallback as useCallback5,
2352
2376
  useContext as useContext2,
2353
2377
  useEffect as useEffect8,
@@ -2375,7 +2399,7 @@ import { createContext as createContext2 } from "react";
2375
2399
  var FieldContext = createContext2(null);
2376
2400
 
2377
2401
  // src/Dropdown.tsx
2378
- import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2402
+ import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2379
2403
  var EXIT_SLACK_MS2 = 50;
2380
2404
  var SETTLE_MS = 450;
2381
2405
  var inPanel = (node) => {
@@ -2416,6 +2440,7 @@ var SEARCH_DEFAULTS = {
2416
2440
  function Dropdown({
2417
2441
  label,
2418
2442
  value,
2443
+ selectedValue,
2419
2444
  placeholder = "Select\u2026",
2420
2445
  variant,
2421
2446
  items,
@@ -2560,15 +2585,16 @@ function Dropdown({
2560
2585
  const name = ariaLabel || (label ? String(label).replace(/:\s*$/, "") : "") || "Options";
2561
2586
  const given = rows.find((it) => it.selected);
2562
2587
  const givenKey = given ? keyOf(given) : null;
2563
- const pickedKey = pick && pick.against === givenKey ? pick.key : null;
2588
+ const pickedKey = selectedValue === void 0 && pick && pick.against === givenKey ? pick.key : null;
2564
2589
  useEffect8(() => {
2565
2590
  if (pick && pick.against !== givenKey) setPick(null);
2566
2591
  }, [givenKey, pick]);
2567
2592
  const picked = pickedKey != null ? rows.find((it) => keyOf(it) === pickedKey) : void 0;
2568
- const shown = value != null ? value : isSelect ? picked?.label ?? given?.label : null;
2593
+ const controlled = selectedValue !== void 0 ? rows.find((it) => it.value === selectedValue) : void 0;
2594
+ const shown = value != null ? value : isSelect ? selectedValue !== void 0 ? controlled?.label : picked?.label ?? given?.label : null;
2569
2595
  const marked2 = isSelect && rows.some((it) => it.avatar);
2570
- const chosen = marked2 ? value != null ? rows.find((it) => it.label === value) : picked ?? given : void 0;
2571
- const selectedOf = (it) => pickedKey != null ? it.disabled ? !!it.selected : keyOf(it) === pickedKey : !!it.selected;
2596
+ const chosen = marked2 ? value != null ? rows.find((it) => it.label === value) : selectedValue !== void 0 ? controlled : picked ?? given : void 0;
2597
+ const selectedOf = (it) => selectedValue !== void 0 ? it.value === selectedValue : pickedKey != null ? it.disabled ? !!it.selected : keyOf(it) === pickedKey : !!it.selected;
2572
2598
  const hiddenBy = (it) => Boolean(sx) && !dropdownMatch(it.label, query);
2573
2599
  const filtering = Boolean(sx) && dropdownFiltering(query);
2574
2600
  const visible = rows.filter((it) => !hiddenBy(it));
@@ -2746,7 +2772,7 @@ function Dropdown({
2746
2772
  e.preventDefault();
2747
2773
  return;
2748
2774
  }
2749
- if (isSelect) setPick({ key: keyOf(item), against: givenKey });
2775
+ if (isSelect && selectedValue === void 0) setPick({ key: keyOf(item), against: givenKey });
2750
2776
  onSelect?.(item.value, item);
2751
2777
  setOpen(false);
2752
2778
  trigger.current?.focus();
@@ -2838,7 +2864,7 @@ function Dropdown({
2838
2864
  ...asLink ? { href: entry.href, ...entry.target ? { target: entry.target } : null } : null,
2839
2865
  ...sx ? { id: rowIds.get(entry), hidden: hiddenBy(entry) || void 0 } : null,
2840
2866
  onClick: (e) => choose(entry, e),
2841
- children: /* @__PURE__ */ jsxs18(Fragment8, { children: [
2867
+ children: /* @__PURE__ */ jsxs18(Fragment9, { children: [
2842
2868
  entry.avatar ? /* @__PURE__ */ jsx22(Avatar, { personKey: entry.avatar.key, name: entry.avatar.name, src: entry.avatar.src, empty: entry.avatar.empty }) : entry.icon && /* @__PURE__ */ jsx22(Glyph2, { name: entry.icon, className: "ui-dropdown__ic" }),
2843
2869
  /* @__PURE__ */ jsxs18("span", { className: "ui-dropdown__main", children: [
2844
2870
  /* @__PURE__ */ jsx22("span", { className: "ui-dropdown__label", children: entry.label }),
@@ -2848,7 +2874,7 @@ function Dropdown({
2848
2874
  asOption && /* @__PURE__ */ jsx22(Glyph2, { name: "check", className: "ui-dropdown__tick", hidden: true })
2849
2875
  ] })
2850
2876
  };
2851
- if (row) return /* @__PURE__ */ jsx22(Fragment7, { children: row(entry, props) }, key);
2877
+ if (row) return /* @__PURE__ */ jsx22(Fragment8, { children: row(entry, props) }, key);
2852
2878
  const Tag = asLink ? "a" : "div";
2853
2879
  return /* @__PURE__ */ jsx22(Tag, { ...props }, key);
2854
2880
  }
@@ -2870,7 +2896,7 @@ function Dropdown({
2870
2896
  );
2871
2897
  }) : (items || []).map((entry, i) => renderRow(entry, `i${i}`));
2872
2898
  const cap = scroll && scroll !== true ? { "--ui-dropdown-cap": typeof scroll === "number" ? `${scroll}px` : scroll } : void 0;
2873
- const none = sx && filtering && !visible.length ? /* @__PURE__ */ jsxs18(Fragment8, { children: [
2899
+ const none = sx && filtering && !visible.length ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
2874
2900
  /* @__PURE__ */ jsx22("span", { className: "ui-dropdown__none-title", children: sx.empty.replace("{q}", () => query.trim()) }),
2875
2901
  /* @__PURE__ */ jsx22("span", { className: "ui-dropdown__none-hint", children: sx.hint })
2876
2902
  ] }) : null;
@@ -2895,7 +2921,7 @@ function Dropdown({
2895
2921
  setOpen(!open);
2896
2922
  },
2897
2923
  children: [
2898
- triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs18(Fragment8, { children: [
2924
+ triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs18(Fragment9, { children: [
2899
2925
  marked2 && (chosen?.avatar ? /* @__PURE__ */ jsx22(Avatar, { personKey: chosen.avatar.key, name: chosen.avatar.name, src: chosen.avatar.src, empty: chosen.avatar.empty }) : /* @__PURE__ */ jsx22(Avatar, { empty: true })),
2900
2926
  label && /* @__PURE__ */ jsx22("span", { className: "ui-dropdown__pre", children: label }),
2901
2927
  /* @__PURE__ */ jsx22("span", { className: cx6("ui-dropdown__value", marked2 && !chosen && "is-placeholder"), children: shown != null ? shown : placeholder })
@@ -2925,7 +2951,7 @@ function Dropdown({
2925
2951
  onMouseMove,
2926
2952
  children: [
2927
2953
  header,
2928
- sx ? /* @__PURE__ */ jsxs18(Fragment8, { children: [
2954
+ sx ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
2929
2955
  /* @__PURE__ */ jsxs18("div", { className: "ui-dropdown__search", children: [
2930
2956
  /* @__PURE__ */ jsx22(Glyph2, { name: "search", className: "ui-dropdown__search-ic", hidden: true }),
2931
2957
  /* @__PURE__ */ jsx22(
@@ -3076,7 +3102,7 @@ function Segmented({ options, value, onChange, label, appearance = "pill", size,
3076
3102
  // src/FilterBar.tsx
3077
3103
  import { useRef as useRef13, useState as useState10 } from "react";
3078
3104
  import { filterChipText, filterChipName, filterChipUnset, filterChipItems, focusNextStop } from "@apliteni/apliteni-ui";
3079
- import { Fragment as Fragment9, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
3105
+ import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
3080
3106
  var SEARCH_AT = 10;
3081
3107
  var isRow2 = (entry) => entry !== "---" && !entry.separator;
3082
3108
  function FilterBar({ filters, label = "Filters", clearLabel = "Clear all", clearVariant = "ghost", add, addLabel = "Filter", disabled = false, busy = false, onRemove, onClear, onChange, onAdd }) {
@@ -3183,7 +3209,7 @@ function FilterBar({ filters, label = "Filters", clearLabel = "Clear all", clear
3183
3209
  variant: "menu",
3184
3210
  sections,
3185
3211
  ariaLabel: addLabel,
3186
- triggerContent: /* @__PURE__ */ jsxs21(Fragment9, { children: [
3212
+ triggerContent: /* @__PURE__ */ jsxs21(Fragment10, { children: [
3187
3213
  /* @__PURE__ */ jsx26(Icon, { name: "plus" }),
3188
3214
  addLabel
3189
3215
  ] }),
@@ -3234,7 +3260,7 @@ function DrawerSection({ title, headingLevel = 3, className, children, ...rest }
3234
3260
 
3235
3261
  // src/Snippet.tsx
3236
3262
  import { useEffect as useEffect9, useRef as useRef14, useState as useState11 } from "react";
3237
- import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
3263
+ import { Fragment as Fragment11, jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
3238
3264
  function Snippet({ label = "shell", code = "", children, copy = true, reveal = false, copyLabel = "Copy code" }) {
3239
3265
  const [status, setStatus] = useState11("idle");
3240
3266
  const request = useRef14(0);
@@ -3262,7 +3288,7 @@ function Snippet({ label = "shell", code = "", children, copy = true, reveal = f
3262
3288
  return /* @__PURE__ */ jsxs23("div", { className: reveal ? "ui-snippet ui-snippet--reveal" : "ui-snippet", children: [
3263
3289
  /* @__PURE__ */ jsxs23("div", { className: "ui-snippet__bar", children: [
3264
3290
  /* @__PURE__ */ jsx28("span", { children: label }),
3265
- copy && /* @__PURE__ */ jsxs23(Fragment10, { children: [
3291
+ copy && /* @__PURE__ */ jsxs23(Fragment11, { children: [
3266
3292
  /* @__PURE__ */ jsx28(
3267
3293
  "button",
3268
3294
  {
@@ -3284,7 +3310,7 @@ function Snippet({ label = "shell", code = "", children, copy = true, reveal = f
3284
3310
  // src/Tabs.tsx
3285
3311
  import { useEffect as useEffect10, useId as useId9, useRef as useRef15 } from "react";
3286
3312
  import { playEntrance } from "@apliteni/apliteni-ui/motion";
3287
- import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
3313
+ import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
3288
3314
  function Tabs({ items, value, onChange, label }) {
3289
3315
  const id = useId9();
3290
3316
  const previous = useRef15(value);
@@ -3333,7 +3359,7 @@ function Tabs({ items, value, onChange, label }) {
3333
3359
  },
3334
3360
  children: [
3335
3361
  item.label,
3336
- item.count != null && /* @__PURE__ */ jsxs24(Fragment11, { children: [
3362
+ item.count != null && /* @__PURE__ */ jsxs24(Fragment12, { children: [
3337
3363
  " ",
3338
3364
  item.count
3339
3365
  ] })
@@ -3571,11 +3597,11 @@ var cases = {
3571
3597
  "not-found": { title: "We can't find that page", sub: "Check the address or return to the home page.", icon: "folder" },
3572
3598
  "not-yet-built": { title: "This ships later", sub: "This screen is not available yet. Check back later.", icon: "clock" }
3573
3599
  };
3574
- function EmptyState({ variant = "first-run", title, sub, icon: icon8, art, actions }) {
3600
+ function EmptyState({ variant = "first-run", title, sub, icon: icon9, art, actions }) {
3575
3601
  const preset = cases[variant];
3576
3602
  const heading = title ?? preset.title;
3577
3603
  const guidance = sub ?? preset.sub;
3578
- const glyph = icon8 ?? preset.icon;
3604
+ const glyph = icon9 ?? preset.icon;
3579
3605
  return /* @__PURE__ */ jsxs27("div", { className: "ui-empty", children: [
3580
3606
  art ? typeof art === "string" ? /* @__PURE__ */ jsx32("div", { className: "ui-empty__art", dangerouslySetInnerHTML: { __html: /<svg/.test(art) ? art : illo(art) } }) : /* @__PURE__ */ jsx32("div", { className: "ui-empty__art", "aria-hidden": "true", children: art }) : glyph ? /* @__PURE__ */ jsx32("div", { className: "ui-empty__icon", dangerouslySetInnerHTML: { __html: kitIcon3(glyph) } }) : null,
3581
3607
  heading && /* @__PURE__ */ jsx32("div", { className: "ui-empty__title", children: heading }),
@@ -3585,7 +3611,7 @@ function EmptyState({ variant = "first-run", title, sub, icon: icon8, art, actio
3585
3611
  }
3586
3612
 
3587
3613
  // src/FileDrop.tsx
3588
- import { Fragment as Fragment12, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
3614
+ import { Fragment as Fragment13, jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
3589
3615
  var MARKS = {
3590
3616
  uploading: "clock",
3591
3617
  done: "circleCheck",
@@ -3753,7 +3779,7 @@ function FileDrop({
3753
3779
  children: /* @__PURE__ */ jsx33("span", { style: { width: `${file.progress}%` } })
3754
3780
  }
3755
3781
  )
3756
- ] }) : /* @__PURE__ */ jsxs28(Fragment12, { children: [
3782
+ ] }) : /* @__PURE__ */ jsxs28(Fragment13, { children: [
3757
3783
  /* @__PURE__ */ jsx33(
3758
3784
  Button,
3759
3785
  {
@@ -3936,7 +3962,7 @@ import { paletteHotkey, prism, railCollapsed } from "@apliteni/apliteni-ui";
3936
3962
  // src/SidebarNav.tsx
3937
3963
  import { forwardRef as forwardRef8, useEffect as useEffect14, useId as useId11, useState as useState15 } from "react";
3938
3964
  import { ENTRANCE_FALLBACK_MS } from "@apliteni/apliteni-ui/motion";
3939
- import { Fragment as Fragment13, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
3965
+ import { Fragment as Fragment14, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
3940
3966
  var badgeText = (badge) => typeof badge === "object" ? badge.text : badge;
3941
3967
  function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
3942
3968
  const uid = useId11();
@@ -3962,7 +3988,7 @@ function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
3962
3988
  item.disabled && "is-disabled",
3963
3989
  item.danger && "is-danger"
3964
3990
  ].filter(Boolean).join(" ");
3965
- const children = /* @__PURE__ */ jsxs31(Fragment13, { children: [
3991
+ const children = /* @__PURE__ */ jsxs31(Fragment14, { children: [
3966
3992
  (item.leading ?? item.icon) && /* @__PURE__ */ jsx36("span", { className: "ui-nav__ic", children: item.leading ?? /* @__PURE__ */ jsx36(Icon, { name: item.icon }) }),
3967
3993
  /* @__PURE__ */ jsx36("span", { className: "ui-nav__label", children: item.label }),
3968
3994
  count != null && count !== "" && /* @__PURE__ */ jsx36("span", { className: `ui-nav__badge is-${typeof item.badge === "object" ? item.badge.tone ?? "neutral" : "neutral"}`, children: count }),
@@ -4102,7 +4128,7 @@ function ThemeToggle({ labelled = false }) {
4102
4128
  }
4103
4129
 
4104
4130
  // src/AppShell.tsx
4105
- import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
4131
+ import { Fragment as Fragment15, jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
4106
4132
  var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
4107
4133
  function AppShell({
4108
4134
  sections,
@@ -4204,7 +4230,7 @@ function AppShell({
4204
4230
  ] }),
4205
4231
  /* @__PURE__ */ jsxs33("div", { className: "ui-app__well", children: [
4206
4232
  /* @__PURE__ */ jsxs33("header", { className: "ui-app__bar", children: [
4207
- navigable && /* @__PURE__ */ jsxs33(Fragment14, { children: [
4233
+ navigable && /* @__PURE__ */ jsxs33(Fragment15, { children: [
4208
4234
  /* @__PURE__ */ jsx38(
4209
4235
  Button,
4210
4236
  {
@@ -4317,10 +4343,10 @@ function Footer({
4317
4343
  brand !== false && /* @__PURE__ */ jsxs34("div", { className: "ui-footer__brand", children: [
4318
4344
  lockup,
4319
4345
  tagline && /* @__PURE__ */ jsx39("p", { className: "ui-footer__tagline", children: tagline }),
4320
- social.length > 0 && /* @__PURE__ */ jsx39("div", { className: "ui-footer__socials", children: social.map(({ label, href: to, icon: icon8 = "github" }) => {
4321
- const markup = SOCIAL_GLYPHS[icon8];
4322
- const fill = icon8 === "mail" ? "none" : "currentColor";
4323
- const viewBox = icon8 === "linkedin" ? "0 0 448 512" : "0 0 24 24";
4346
+ social.length > 0 && /* @__PURE__ */ jsx39("div", { className: "ui-footer__socials", children: social.map(({ label, href: to, icon: icon9 = "github" }) => {
4347
+ const markup = SOCIAL_GLYPHS[icon9];
4348
+ const fill = icon9 === "mail" ? "none" : "currentColor";
4349
+ const viewBox = icon9 === "linkedin" ? "0 0 448 512" : "0 0 24 24";
4324
4350
  return /* @__PURE__ */ jsx39("a", { className: "ui-footer__social", href: safeUrl2(to ?? "#"), "aria-label": label, children: /* @__PURE__ */ jsx39("svg", { viewBox, fill, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: markup } }) }, label);
4325
4351
  }) })
4326
4352
  ] }),
@@ -4467,7 +4493,7 @@ import {
4467
4493
  useState as useState18
4468
4494
  } from "react";
4469
4495
  import { CHART_FLOOR, bridgeWalk, chartScale, pointTarget } from "@apliteni/apliteni-ui";
4470
- import { Fragment as Fragment15, jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
4496
+ import { Fragment as Fragment16, jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
4471
4497
  var HEIGHT = { months: 216, bridge: 216, spark: 32 };
4472
4498
  var PAD = 2;
4473
4499
  var ZERO_INSET = 1.5;
@@ -5097,7 +5123,7 @@ function Chart(props) {
5097
5123
  `${column.label}-${i}`
5098
5124
  )) })
5099
5125
  ] }) }),
5100
- /* @__PURE__ */ jsx41("span", { ref: tipEl, className: cx8("ui-tip", openId && "is-open"), role: open ? "tooltip" : void 0, children: open && /* @__PURE__ */ jsxs36(Fragment15, { children: [
5126
+ /* @__PURE__ */ jsx41("span", { ref: tipEl, className: cx8("ui-tip", openId && "is-open"), role: open ? "tooltip" : void 0, children: open && /* @__PURE__ */ jsxs36(Fragment16, { children: [
5101
5127
  /* @__PURE__ */ jsx41("span", { className: "ui-tip__label", children: spark ? columns[open.column]?.label : `${open.name}, ${columns[open.column]?.label}` }),
5102
5128
  /* @__PURE__ */ jsx41("span", { className: "ui-tip__value", children: format(open.value) }),
5103
5129
  (stateOf(columns[open.column]) || open.detail) && /* @__PURE__ */ jsx41("span", { className: "ui-tip__detail", children: stateOf(columns[open.column]) || open.detail })
@@ -5120,7 +5146,7 @@ function Chart(props) {
5120
5146
  /* @__PURE__ */ jsx41("tbody", { children: tableRows.map((row, i) => /* @__PURE__ */ jsxs36("tr", { "aria-current": pick === i ? true : void 0, children: [
5121
5147
  /* @__PURE__ */ jsxs36("th", { scope: "row", children: [
5122
5148
  row.column.label,
5123
- row.column.estimated && /* @__PURE__ */ jsxs36(Fragment15, { children: [
5149
+ row.column.estimated && /* @__PURE__ */ jsxs36(Fragment16, { children: [
5124
5150
  " ",
5125
5151
  /* @__PURE__ */ jsx41("span", { className: "ui-chart__state", children: stateOf(row.column) })
5126
5152
  ] })
@@ -5177,10 +5203,145 @@ var AccentPicker = forwardRef10(function AccentPicker2({
5177
5203
  );
5178
5204
  });
5179
5205
 
5206
+ // src/TopBar.tsx
5207
+ import { Fragment as Fragment17 } from "react";
5208
+ import { prism as prism3, safeUrl as safeUrl3, icon as icon7, ACCOUNT_NAV } from "@apliteni/apliteni-ui";
5209
+ import { Fragment as Fragment18, jsx as jsx43, jsxs as jsxs37 } from "react/jsx-runtime";
5210
+ function TopBar({
5211
+ brand,
5212
+ word = "Strategy",
5213
+ brandHref = "/",
5214
+ view,
5215
+ version,
5216
+ theme,
5217
+ account,
5218
+ className,
5219
+ ...rest
5220
+ }) {
5221
+ return /* @__PURE__ */ jsx43("header", { ...rest, className: ["topbar", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsxs37("div", { className: "topbar__in", children: [
5222
+ brand ?? /* @__PURE__ */ jsxs37("a", { className: "brand", href: safeUrl3(brandHref), "aria-label": `Apliteni ${word}`, children: [
5223
+ /* @__PURE__ */ jsx43("span", { "aria-hidden": "true", dangerouslySetInnerHTML: { __html: prism3("tb-mark", 22) } }),
5224
+ /* @__PURE__ */ jsx43("span", { children: word })
5225
+ ] }),
5226
+ view,
5227
+ /* @__PURE__ */ jsx43("span", { className: "spacer" }),
5228
+ version,
5229
+ theme,
5230
+ account
5231
+ ] }) });
5232
+ }
5233
+ function ViewSwitch({ items, active, label = "View", renderLink }) {
5234
+ return /* @__PURE__ */ jsx43("div", { className: "dtsw on", role: "group", "aria-label": label, children: items.map((item) => {
5235
+ const current = item.id === active;
5236
+ const linkProps = {
5237
+ className: current ? "cur" : void 0,
5238
+ href: safeUrl3(item.href || `#${item.id}`),
5239
+ target: item.target,
5240
+ "aria-current": current ? "page" : void 0,
5241
+ children: item.label
5242
+ };
5243
+ return /* @__PURE__ */ jsx43(Fragment17, { children: renderLink ? renderLink(item, linkProps) : /* @__PURE__ */ jsx43("a", { ...linkProps }) }, item.id);
5244
+ }) });
5245
+ }
5246
+ var BADGE = { live: "Live", archive: "Archive" };
5247
+ function VersionSwitcher({ versions, value, onChange, label = "Version", defaultOpen }) {
5248
+ const current = versions.find((v) => v.value === value);
5249
+ return /* @__PURE__ */ jsx43("div", { className: "vsw", children: /* @__PURE__ */ jsx43(
5250
+ Dropdown,
5251
+ {
5252
+ variant: "select",
5253
+ align: "start",
5254
+ triggerClass: "vsw__btn",
5255
+ panelClass: "vsw__menu",
5256
+ selectedValue: value,
5257
+ chevron: false,
5258
+ defaultOpen,
5259
+ ariaLabel: `${label} \u2014 ${current?.label ?? ""}`,
5260
+ triggerContent: /* @__PURE__ */ jsxs37(Fragment18, { children: [
5261
+ /* @__PURE__ */ jsx43("span", { className: "cur", children: current?.label }),
5262
+ /* @__PURE__ */ jsx43("span", { className: "car", "aria-hidden": "true" })
5263
+ ] }),
5264
+ items: versions.map((v) => ({
5265
+ label: v.label,
5266
+ value: v.value,
5267
+ badge: BADGE[v.badge ?? "archive"],
5268
+ selected: v.value === value
5269
+ })),
5270
+ row: (item, props) => /* @__PURE__ */ jsxs37("div", { ...props, className: `${props.className} vopt`, children: [
5271
+ /* @__PURE__ */ jsx43("span", { className: "vname", children: item.label }),
5272
+ /* @__PURE__ */ jsx43("span", { className: `vbadge ${item.badge === "Live" ? "live" : "arch"}`, children: String(item.badge) }),
5273
+ /* @__PURE__ */ jsx43("span", { className: "vtick", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: icon7("check") } })
5274
+ ] }),
5275
+ onSelect: (picked) => {
5276
+ if (picked != null) onChange?.(String(picked));
5277
+ }
5278
+ }
5279
+ ) });
5280
+ }
5281
+ var initialsOf = (name, email) => {
5282
+ const from = name.trim() || email.split("@")[0] || "";
5283
+ return (from.split(/[\s._-]+/).filter(Boolean).slice(0, 2).map((w) => w[0]).join("") || "?").toUpperCase();
5284
+ };
5285
+ var ACCOUNT_ITEMS = ACCOUNT_NAV.map((item) => ({
5286
+ label: item.label,
5287
+ icon: item.icon,
5288
+ value: item.id,
5289
+ href: `#${item.id}`
5290
+ }));
5291
+ function AccountMenu({
5292
+ name,
5293
+ email,
5294
+ items = ACCOUNT_ITEMS,
5295
+ current = "prefs",
5296
+ signOutHref,
5297
+ onSignOut,
5298
+ onSelect,
5299
+ defaultOpen
5300
+ }) {
5301
+ const mark = initialsOf(name, email);
5302
+ return /* @__PURE__ */ jsx43("div", { className: "acct on", children: /* @__PURE__ */ jsx43(
5303
+ Dropdown,
5304
+ {
5305
+ variant: "menu",
5306
+ align: "end",
5307
+ chevron: false,
5308
+ triggerClass: "avatar",
5309
+ panelClass: "amenu",
5310
+ defaultOpen,
5311
+ ariaLabel: "Account",
5312
+ triggerContent: mark,
5313
+ header: /* @__PURE__ */ jsxs37("div", { className: "ahead", children: [
5314
+ /* @__PURE__ */ jsx43("span", { className: "avatar", children: mark }),
5315
+ /* @__PURE__ */ jsxs37("span", { className: "aw", children: [
5316
+ /* @__PURE__ */ jsx43("span", { className: "anm", children: name }),
5317
+ /* @__PURE__ */ jsx43("span", { className: "aem", title: email, children: email })
5318
+ ] })
5319
+ ] }),
5320
+ items: [...items, "---", { label: "Sign out", icon: "logout", value: "sign-out", href: signOutHref }],
5321
+ row: (item, props) => /* @__PURE__ */ jsxs37(
5322
+ "a",
5323
+ {
5324
+ ...props,
5325
+ href: item.disabled ? void 0 : item.href ? safeUrl3(item.href) : item.value === "sign-out" && onSignOut ? void 0 : safeUrl3(item.value === "sign-out" ? "#logout" : `#${item.value}`),
5326
+ className: [props.className.replace(/\bui-dropdown__item\b/, "").trim(), !item.disabled && item.value === current && "cur", item.value === "sign-out" && "aout"].filter(Boolean).join(" "),
5327
+ children: [
5328
+ item.icon && /* @__PURE__ */ jsx43("span", { "aria-hidden": "true", dangerouslySetInnerHTML: { __html: icon7(item.icon) } }),
5329
+ item.label
5330
+ ]
5331
+ }
5332
+ ),
5333
+ onSelect: (value, item) => {
5334
+ if (item.value === "sign-out") onSignOut?.();
5335
+ else onSelect?.(value);
5336
+ }
5337
+ }
5338
+ ) });
5339
+ }
5340
+
5180
5341
  // src/NavTabs.tsx
5181
- import { Fragment as Fragment16, forwardRef as forwardRef11, useEffect as useEffect18, useRef as useRef23, useImperativeHandle as useImperativeHandle2 } from "react";
5182
- import { safeUrl as safeUrl3, observeNavFit, revealCurrentNav, revealFocusedNav } from "@apliteni/apliteni-ui";
5183
- import { Fragment as Fragment17, jsx as jsx43, jsxs as jsxs37 } from "react/jsx-runtime";
5342
+ import { Fragment as Fragment19, forwardRef as forwardRef11, useEffect as useEffect18, useRef as useRef23, useImperativeHandle as useImperativeHandle2 } from "react";
5343
+ import { safeUrl as safeUrl4, observeNavFit, revealCurrentNav, revealFocusedNav } from "@apliteni/apliteni-ui";
5344
+ import { Fragment as Fragment20, jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
5184
5345
  var NavTabs = forwardRef11(function NavTabs2({
5185
5346
  items,
5186
5347
  active,
@@ -5198,7 +5359,7 @@ var NavTabs = forwardRef11(function NavTabs2({
5198
5359
  useEffect18(() => {
5199
5360
  if (navRef.current) revealCurrentNav(navRef.current);
5200
5361
  }, [active, signature]);
5201
- return /* @__PURE__ */ jsx43(
5362
+ return /* @__PURE__ */ jsx44(
5202
5363
  "nav",
5203
5364
  {
5204
5365
  ...rest,
@@ -5211,20 +5372,20 @@ var NavTabs = forwardRef11(function NavTabs2({
5211
5372
  },
5212
5373
  children: items.map((item) => {
5213
5374
  const badge = item.badge != null && typeof item.badge === "object" ? item.badge : { text: item.badge, tone: void 0 };
5214
- const inner = /* @__PURE__ */ jsxs37(Fragment17, { children: [
5215
- /* @__PURE__ */ jsx43("span", { className: "ui-nav__tab-label", children: item.label }),
5216
- badge.text != null && badge.text !== "" && /* @__PURE__ */ jsx43("span", { className: `ui-nav__badge is-${badge.tone || "neutral"}`, children: badge.text })
5375
+ const inner = /* @__PURE__ */ jsxs38(Fragment20, { children: [
5376
+ /* @__PURE__ */ jsx44("span", { className: "ui-nav__tab-label", children: item.label }),
5377
+ badge.text != null && badge.text !== "" && /* @__PURE__ */ jsx44("span", { className: `ui-nav__badge is-${badge.tone || "neutral"}`, children: badge.text })
5217
5378
  ] });
5218
- if (item.disabled) return /* @__PURE__ */ jsx43("span", { className: "ui-nav__tab is-disabled", "aria-disabled": "true", children: inner }, item.id);
5379
+ if (item.disabled) return /* @__PURE__ */ jsx44("span", { className: "ui-nav__tab is-disabled", "aria-disabled": "true", children: inner }, item.id);
5219
5380
  const current = item.id === active;
5220
5381
  const linkProps = {
5221
5382
  className: ["ui-nav__tab", current && "is-active"].filter(Boolean).join(" "),
5222
- href: safeUrl3(item.href || `#${item.id}`),
5383
+ href: safeUrl4(item.href || `#${item.id}`),
5223
5384
  target: item.target,
5224
5385
  "aria-current": current ? "page" : void 0,
5225
5386
  children: inner
5226
5387
  };
5227
- return /* @__PURE__ */ jsx43(Fragment16, { children: renderLink ? renderLink(item, linkProps) : /* @__PURE__ */ jsx43("a", { ...linkProps }) }, item.id);
5388
+ return /* @__PURE__ */ jsx44(Fragment19, { children: renderLink ? renderLink(item, linkProps) : /* @__PURE__ */ jsx44("a", { ...linkProps }) }, item.id);
5228
5389
  })
5229
5390
  }
5230
5391
  );
@@ -5232,8 +5393,8 @@ var NavTabs = forwardRef11(function NavTabs2({
5232
5393
 
5233
5394
  // src/Breadcrumbs.tsx
5234
5395
  import { forwardRef as forwardRef12 } from "react";
5235
- import { icon as icon7, safeUrl as safeUrl4 } from "@apliteni/apliteni-ui";
5236
- import { Fragment as Fragment18, jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
5396
+ import { icon as icon8, safeUrl as safeUrl5 } from "@apliteni/apliteni-ui";
5397
+ import { Fragment as Fragment21, jsx as jsx45, jsxs as jsxs39 } from "react/jsx-runtime";
5237
5398
  var Breadcrumbs = forwardRef12(function Breadcrumbs2({
5238
5399
  items,
5239
5400
  renderLink,
@@ -5241,21 +5402,21 @@ var Breadcrumbs = forwardRef12(function Breadcrumbs2({
5241
5402
  "aria-label": ariaLabel = "Breadcrumb",
5242
5403
  ...rest
5243
5404
  }, ref) {
5244
- return /* @__PURE__ */ jsx44(
5405
+ return /* @__PURE__ */ jsx45(
5245
5406
  "nav",
5246
5407
  {
5247
5408
  ...rest,
5248
5409
  ref,
5249
5410
  className: ["ui-nav", "ui-nav--crumbs", className].filter(Boolean).join(" "),
5250
5411
  "aria-label": ariaLabel,
5251
- children: /* @__PURE__ */ jsx44("ol", { className: "ui-nav__crumbs", children: items.map((item, index) => {
5412
+ children: /* @__PURE__ */ jsx45("ol", { className: "ui-nav__crumbs", children: items.map((item, index) => {
5252
5413
  const current = index === items.length - 1;
5253
- const inner = /* @__PURE__ */ jsxs38(Fragment18, { children: [
5254
- item.icon && /* @__PURE__ */ jsx44("span", { className: "ui-nav__ic", dangerouslySetInnerHTML: { __html: icon7(item.icon) } }),
5255
- /* @__PURE__ */ jsx44("span", { className: "ui-nav__crumb-label", children: item.label })
5414
+ const inner = /* @__PURE__ */ jsxs39(Fragment21, { children: [
5415
+ item.icon && /* @__PURE__ */ jsx45("span", { className: "ui-nav__ic", dangerouslySetInnerHTML: { __html: icon8(item.icon) } }),
5416
+ /* @__PURE__ */ jsx45("span", { className: "ui-nav__crumb-label", children: item.label })
5256
5417
  ] });
5257
- const linkProps = !current && item.href ? { className: "ui-nav__crumb", href: safeUrl4(item.href), target: item.target, children: inner } : null;
5258
- return /* @__PURE__ */ jsx44("li", { className: "ui-nav__crumb-item", children: linkProps ? renderLink ? renderLink(item, linkProps) : /* @__PURE__ */ jsx44("a", { ...linkProps }) : /* @__PURE__ */ jsx44(
5418
+ const linkProps = !current && item.href ? { className: "ui-nav__crumb", href: safeUrl5(item.href), target: item.target, children: inner } : null;
5419
+ return /* @__PURE__ */ jsx45("li", { className: "ui-nav__crumb-item", children: linkProps ? renderLink ? renderLink(item, linkProps) : /* @__PURE__ */ jsx45("a", { ...linkProps }) : /* @__PURE__ */ jsx45(
5259
5420
  "span",
5260
5421
  {
5261
5422
  className: ["ui-nav__crumb", current && "is-current"].filter(Boolean).join(" "),
@@ -5273,6 +5434,7 @@ var PAGE_SIZES = KIT_PAGE_SIZES;
5273
5434
  var DEFAULT_PAGE_SIZE3 = KIT_DEFAULT_PAGE_SIZE;
5274
5435
  export {
5275
5436
  AccentPicker,
5437
+ AccountMenu,
5276
5438
  AppShell,
5277
5439
  Avatar,
5278
5440
  BackLink,
@@ -5332,6 +5494,9 @@ export {
5332
5494
  Timeline,
5333
5495
  Toast,
5334
5496
  Tooltip,
5497
+ TopBar,
5498
+ VersionSwitcher,
5499
+ ViewSwitch,
5335
5500
  sortTableRows,
5336
5501
  useToast
5337
5502
  };
@@ -38,11 +38,11 @@ export function versionSwitcher(versions = [], activeIdx = 0) {
38
38
  const opts = versions.map((v, i) => {
39
39
  const key = String(v.badge || 'archive').toLowerCase();
40
40
  return `<div class="vopt" role="option" data-dd-item tabindex="-1" aria-selected="${i === activeIdx}" data-active="${i === activeIdx ? '1' : '0'}">` +
41
- `<span><div class="vname">${v.label}</div><div class="vmeta">${v.meta || ''}</div></span>` +
42
- `<span class="vbadge ${key === 'live' ? 'live' : 'arch'}">${Object.hasOwn(VBADGE, key) ? VBADGE[key] : v.badge}</span></div>`;
41
+ `<span class="vname">${v.label}</span>` +
42
+ `<span class="vbadge ${key === 'live' ? 'live' : 'arch'}">${Object.hasOwn(VBADGE, key) ? VBADGE[key] : v.badge}</span><span class="vtick" aria-hidden="true">${icon('check')}</span></div>`;
43
43
  }).join('');
44
44
  return `<div class="vsw" data-dropdown><button type="button" class="vsw__btn" data-dropdown-trigger aria-haspopup="listbox" aria-expanded="false" aria-label="Version — ${esc(cur)}">` +
45
- `<span class="lbl">version:</span><span class="cur">${cur}</span><span class="car"></span></button>` +
45
+ `<span class="cur">${cur}</span><span class="car"></span></button>` +
46
46
  `<div class="vsw__menu" data-dropdown-panel role="listbox" aria-label="Version">${opts}</div></div>`;
47
47
  }
48
48
 
@@ -1,6 +1,6 @@
1
1
  /* ============================================================================
2
2
  * Table — .ui-table (hairline rows, opaque base surface)
3
- * Row modifiers: .is-dead (revoked/struck), .is-target (landed on), right-aligned .ui-table__act
3
+ * Row modifiers: .is-dead (revoked, shown by its muted ink and a word), .is-target (landed on), right-aligned .ui-table__act
4
4
  * ========================================================================== */
5
5
 
6
6
  .ui-table {
@@ -52,14 +52,6 @@
52
52
  .ui-table th:last-child { padding-right: 0; }
53
53
  .ui-table td.ui-table__act { padding-right: 0; text-align: right; }
54
54
  .ui-table tr.is-dead td { /* muted-ink: state — revoked row */ color: var(--muted); }
55
- .ui-table tr.is-dead td.ui-table__title { text-decoration: line-through; }
56
- /* The strike is painted by the cell across its in-flow inline descendants, and an atomic
57
- inline-level box is not one — so a linked name in a revoked row needs it struck itself.
58
- On hover it carries both lines: the cell-link hover rule below is a whole `text-decoration`,
59
- which would otherwise replace the strike with the underline.
60
- held by: src/styles/table-focus.test.js */
61
- .ui-table tr.is-dead td.ui-table__title a { text-decoration: line-through; }
62
- .ui-table tr.is-dead td.ui-table__title a:hover { text-decoration-line: underline line-through; }
63
55
  .ui-table tbody tr { transition: background var(--dur-fast) var(--ease); }
64
56
 
65
57
  /* Row hover: an inset, rounded fill so the highlight never sits flush against a
@@ -72,7 +64,6 @@
72
64
  border-spacing: 0;
73
65
  padding: 0 var(--space-2);
74
66
  }
75
- .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td { background: var(--surface-2); }
76
67
  .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:first-child {
77
68
  border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
78
69
  .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:last-child {
@@ -106,8 +97,6 @@
106
97
  .ui-table--zebra td:first-child { padding-left: var(--space-3); }
107
98
  .ui-table--zebra th:last-child,
108
99
  .ui-table--zebra td:last-child { padding-right: var(--space-3); }
109
- .ui-table--zebra.ui-table--hover tbody tr:hover td,
110
- .ui-table--zebra tbody tr:hover td { background: color-mix(in srgb, var(--accent) 8%, transparent); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
111
100
 
112
101
  /* A link in a cell is a value that happens to open something: the row's ink, underlined
113
102
  on hover — what .ui-identity below has always done. Without it, base.css's bare
@@ -213,7 +202,7 @@
213
202
  * bold only in the figure. Scoped to the table, the helper wins on either cell and the
214
203
  * row reads as one row. */
215
204
  .ui-table :is(th, td).ui-table__num--strong { color: var(--strong); font-weight: var(--weight-semibold); }
216
- .ui-table__code { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text); }
205
+ .ui-table__code { font-size: var(--text-sm); color: var(--text); }
217
206
  /* Two things in one cell — a row's own name and the state of it — side by side
218
207
  * while the column has room and stacked when it does not. The wrap comes from flex
219
208
  * because a dense cell is nowrap, and the column is then only as wide as the wider
@@ -234,13 +223,13 @@
234
223
 
235
224
  /* White in light, base canvas in dark; never a grey or tinted data surface. */
236
225
  .ui-table { background: var(--table-bg); }
237
- .ui-table--zebra tbody tr:nth-child(even) td,
238
- .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
239
- .ui-table--zebra tbody tr:hover td,
240
- .ui-table--zebra.ui-table--hover tbody tr:hover td {
241
- background: var(--table-bg);
226
+ .ui-table--zebra tbody tr:nth-child(even) td { background: var(--table-bg); }
227
+ /* A row hover is a quiet neutral step. The row paints under transparent cells in a
228
+ scroll region, while ordinary cells cover the row without breaking its fill. */
229
+ .ui-table--hover tbody tr:hover:not(.is-target),
230
+ .ui-table--hover tbody tr:hover:not(.is-target) > :is(td, th) {
231
+ background: var(--surface-2);
242
232
  }
243
- .ui-table tbody tr:hover { outline: 1px solid var(--border-strong); outline-offset: -1px; }
244
233
 
245
234
  /* The row a reference landed a reader on. The mark is a GROUND, not a line: an outline
246
235
  says the keyboard is here (Artur, r34), and this one persists — it answers which row the
@@ -332,6 +321,9 @@
332
321
  beneath them. */
333
322
  .ui-table-scroll > .ui-table,
334
323
  .ui-table-scroll > .ui-table tbody td:not(.ui-table__identity, .ui-table__selection) { background-color: transparent; }
324
+ .ui-table-scroll > .ui-table--hover tbody tr:hover:not(.is-target) > :is(td, th) {
325
+ background: var(--surface-2);
326
+ }
335
327
  /* The kit's first ringed scroller, and a scroll region inside a card like the rest, so
336
328
  it draws the band inward too rather than spending a halo across the card around it.
337
329
  It had no room of its own to draw in — the padding above is horizontal — which is the
@@ -387,7 +379,7 @@
387
379
  .ui-delta--success { color: var(--chip-success-ink); }
388
380
  .ui-delta--danger { color: var(--chip-danger-ink); }
389
381
  .ui-identity { min-height: 24px; display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text); text-decoration: none; }
390
- .ui-identity__symbol { font-family: var(--font-mono); font-weight: var(--weight-semibold); }
382
+ .ui-identity__symbol { font-weight: var(--weight-semibold); }
391
383
  .ui-identity__name { white-space: normal; max-width: var(--panel-sm); }
392
384
  .ui-identity__logo { width: 24px; height: 24px; flex: 0 0 24px; position: relative; display: grid; place-items: center; border: 1px solid var(--border); border-radius: var(--radius-xs); font-size: var(--text-xs); }
393
385
  .ui-identity__logo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
@@ -500,10 +492,8 @@ a.ui-identity:hover { text-decoration: underline; }
500
492
  .ui-table.ui-table--stack > tbody > tr {
501
493
  display: flex; flex-wrap: wrap; align-items: baseline;
502
494
  column-gap: var(--space-3); row-gap: var(--space-1);
503
- /* The inset is on the row, not the table: the hover outline draws at the row's own
504
- edge, so a pad on the table would put the outline back against the text (#71). It is
505
- the step a dense cell already pays sideways, so the outline and the row separator keep
506
- the clearance they had at the width above this one. */
495
+ /* The inset is on the row, not the table, so its hover fill and separator keep
496
+ their clearance at the width above this one. */
507
497
  padding: var(--space-3); border-bottom: 1px solid var(--border);
508
498
  }
509
499
  .ui-table.ui-table--stack > tbody > tr:last-child { border-bottom: 0; }
@@ -515,7 +505,21 @@ a.ui-identity:hover { text-decoration: underline; }
515
505
  display: block; width: auto; min-width: 0; max-width: 100%; height: auto;
516
506
  padding: 0; border-bottom: 0; white-space: normal; overflow-wrap: break-word;
517
507
  }
508
+ .ui-table.ui-table--stack.ui-table--hover tbody tr:hover:not(.is-target) > :is(td, th) {
509
+ background: transparent;
510
+ }
518
511
  .ui-table.ui-table--stack > tbody > tr > td.ui-table__long { flex: 0 0 100%; text-align: left; }
512
+ /* The head is gone, so an amount that names its column in `data-label` says it in front of
513
+ the figure, in the unit's size and body ink. */
514
+ .ui-table.ui-table--stack > tbody > tr > td[data-label]::before {
515
+ content: attr(data-label) / ""; margin-right: var(--space-1); font-size: var(--text-xs);
516
+ }
517
+ /* The cells of a stacked row are separate boxes with gaps between them, so a tint on each
518
+ would paint patches. The row paints one tint and the cells let it through. */
519
+ .ui-table.ui-table--stack tbody tr.is-target:is(*, :hover) {
520
+ background: color-mix(in srgb, var(--accent) 12%, transparent); /* code-bg: inherit — this translucent wash leaves the chip on its opaque ground. */
521
+ }
522
+ .ui-table.ui-table--stack tbody tr.is-target:is(*, :hover) > :is(td, th) { background: transparent; }
519
523
  /* A stacked row has no columns left to pin, stick or inset, and the identity's name is
520
524
  what the row is about — the fold hides it only because a pinned column is narrow. */
521
525
  .ui-table.ui-table--stack.ui-table--pinned > :where(thead, tbody) > tr > :where(.ui-table__identity, .ui-table__selection) {
@@ -69,7 +69,11 @@
69
69
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
70
70
  }
71
71
  .dtsw a:hover { color: var(--strong); }
72
- .dtsw a.cur { color: var(--strong); background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
72
+ .dtsw a.cur { color: var(--accent-contrast); background: var(--accent); }
73
+ :where(:root[data-theme="dark"]) .dtsw a.cur { color: var(--signal-contrast); }
74
+ @media (forced-colors: active) {
75
+ .dtsw a.cur { forced-color-adjust: none; background: Highlight; color: HighlightText; --ring: var(--ring-width) solid CanvasText; }
76
+ }
73
77
  .dtsw a:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
74
78
 
75
79
  /* Theme toggle — a single icon-only switch (no label). */
@@ -96,6 +100,7 @@
96
100
 
97
101
  /* Version switcher */
98
102
  .vsw { position: relative; }
103
+ .vsw > .ui-dropdown { max-width: 100%; min-width: 0; }
99
104
  .vsw__btn {
100
105
  display: inline-flex;
101
106
  align-items: center;
@@ -117,16 +122,16 @@
117
122
  what the kit's rows already chose. why: docs/foundations.md#the-focus-ring */
118
123
  .vsw__btn:hover:not(:focus-visible) { border-color: var(--accent); }
119
124
  .vsw__btn:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
120
- .vsw__btn .lbl { color: var(--text); }
121
125
  .vsw__btn .cur { color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
122
126
  .vsw__btn .car {
123
127
  width: 8px; height: 8px;
124
128
  border-right: 1.6px solid var(--muted);
125
129
  border-bottom: 1.6px solid var(--muted);
126
- transform: rotate(45deg) translateY(-1px);
130
+ transform: translateY(-2px) rotate(45deg);
127
131
  transition: transform var(--dur-fast) var(--ease);
128
132
  }
129
- .vsw.open .vsw__btn .car { transform: rotate(-135deg) translateY(2px); }
133
+ .vsw.open .vsw__btn .car,
134
+ .vsw .ui-dropdown.open .vsw__btn .car { transform: translateY(2px) rotate(-135deg); }
130
135
  .vsw__menu {
131
136
  position: absolute;
132
137
  top: calc(100% + 9px);
@@ -214,8 +219,12 @@
214
219
  the arrow keys, and this is one. It takes the ring on its own 10px radius, the
215
220
  same way `.amenu a` above does. #482 */
216
221
  .vopt:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
222
+ /* The selected version carries the kit's menu tick, as `.ui-dropdown__tick` does for
223
+ every other menu; the badge keeps its own place beside it. */
224
+ .vopt .vtick { margin-left: 8px; flex: none; display: none; color: var(--accent); margin-top: 1px; }
225
+ .vopt .vtick svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.4; }
226
+ .vopt[aria-selected="true"] .vtick { display: inline-flex; }
217
227
  .vopt .vname { font-family: var(--font-sans); font-size: calc(var(--text-sm) - 0.5px); color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
218
- .vopt .vmeta { font-size: var(--text-xs); color: var(--text); margin-top: 2px; }
219
228
  .vopt .vbadge {
220
229
  /* rank: chip */
221
230
  font-size: var(--text-xs);
@@ -231,10 +240,8 @@
231
240
  the same neutral chip fill rather than the card surface — which, inside a panel that
232
241
  is itself lighter than a card, was darker than the ground it sat on. #453 */
233
242
  .vbadge.arch { /* muted-ink: state — archived version */ color: var(--muted); background: var(--chip-neutral-fill); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
234
- /* The version label drops at the shell breakpoint.
235
- Anchor the menu to its right edge so a closed panel cannot widen the page. */
243
+ /* Anchor the menu to its right edge so a closed panel cannot widen the page. */
236
244
  @media (max-width: 720px) {
237
- .vsw__btn .lbl { display: none; }
238
245
  .vsw__menu { left: auto; right: 0; }
239
246
  }
240
247
 
@@ -273,7 +280,7 @@
273
280
  end inset. A long label gives way to an ellipsis, and 8px always stays between the
274
281
  two controls. */
275
282
  .topbar__in > .vsw { grid-area: 2 / 2 / 3 / -1; justify-self: end; min-width: 0; max-width: calc(100% - var(--space-2)); margin-left: var(--space-2); }
276
- .vsw__btn { max-width: 100%; }
283
+ .vsw__btn { max-width: 100%; min-width: 0; }
277
284
  .vsw__btn .cur { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
278
285
  .vsw__btn .car { flex: none; }
279
286
  /* Two 44px rows, an 8px gap, the 4px and 3px insets and the bottom border. */
@@ -304,6 +311,8 @@
304
311
  accent band is the second band #578 round r34 took off the focus rules, and a
305
312
  pointer resting on a focused control would put it back. One indicator, which is
306
313
  what the kit's rows already chose. why: docs/foundations.md#the-focus-ring */
314
+ /* The React menu puts the dropdown trigger's pill padding and flex row on the avatar. */
315
+ .acct .avatar { display: grid; padding: 0; }
307
316
  .acct .avatar:hover:not(:focus-visible) { border-color: var(--accent); }
308
317
  .avatar:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
309
318
  /* The account button holding an Avatar: the part paints the circle in the reader's hue and
@@ -376,6 +385,8 @@
376
385
  border-radius: 14px 14px 0 0;
377
386
  display: flex; align-items: center; gap: 11px;
378
387
  }
388
+ .amenu .ui-dropdown__sep { margin: 5px 0; }
389
+ .amenu .ahead .avatar { cursor: default; }
379
390
  .amenu .ahead .aw { min-width: 0; display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
380
391
  .amenu .ahead .anm { color: var(--strong); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
381
392
  .amenu .ahead .aem { color: var(--text); font-size: calc(var(--text-xs) + 0.5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -388,6 +399,10 @@
388
399
  .amenu a:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
389
400
  .amenu a:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
390
401
  .amenu a.cur { color: var(--accent); }
402
+ /* A custom account row keeps the Dropdown's disabled state without its row shape. */
403
+ .amenu a.is-disabled { /* muted-ink: state — unavailable account page */ color: var(--disabled-ink); cursor: not-allowed; pointer-events: none; }
404
+ /* The SVG takes the row's height; its React wrapper adds no line box. */
405
+ .amenu a > span[aria-hidden="true"] { display: contents; }
391
406
  .amenu a svg { width: 19px; height: 19px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; opacity: 0.85; }
392
407
  .amenu .asep { height: 1px; background: var(--border); margin: 5px 0; }
393
408
  .amenu a.aout { color: var(--text); }
@@ -574,10 +574,7 @@
574
574
  .ui-table-scroll,
575
575
  .ui-table--sticky thead th,
576
576
  .ui-table--pinned .ui-table__identity, .ui-table--pinned .ui-table__selection,
577
- .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
578
577
  .ui-table--zebra tbody tr:nth-child(even) td,
579
- .ui-table--zebra tbody tr:hover td,
580
- .ui-table--zebra.ui-table--hover tbody tr:hover td,
581
578
  .ui-toast,
582
579
  .ui-toast--soft,
583
580
  .ui-toast--outline,
@@ -598,10 +595,14 @@
598
595
  .ui-table-scroll,
599
596
  .ui-table--sticky thead th,
600
597
  .ui-table--pinned .ui-table__identity, .ui-table--pinned .ui-table__selection,
601
- .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
602
- .ui-table--zebra tbody tr:nth-child(even) td,
603
- .ui-table--zebra tbody tr:hover td,
604
- .ui-table--zebra.ui-table--hover tbody tr:hover td { --code-bg: var(--table-code-bg); }
598
+ .ui-table--zebra tbody tr:nth-child(even) td { --code-bg: var(--table-code-bg); }
599
+ /* A neutral hovered row gives an inline code chip the card surface. The step from
600
+ * --surface-2 clears 1.1:1 in both themes, even when the table follows a card. */
601
+ .ui-table--hover tbody tr:hover:not(.is-target),
602
+ .ui-table--hover tbody tr:hover:not(.is-target) > :is(td, th),
603
+ .ui-table-scroll > .ui-table--hover tbody tr:hover:not(.is-target) > :is(td, th) {
604
+ --code-bg: var(--surface);
605
+ }
605
606
  .ui-card--accent,
606
607
  .ui-card--live,
607
608
  .ui-snippet--reveal { --code-bg: var(--surface); }