@apliteni/apliteni-ui 0.137.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.137.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:
@@ -976,6 +978,29 @@ instead of 32, with the same line drawn in the middle of it.
976
978
  ]} />
977
979
  ```
978
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
+
979
1004
  ## NavTabs and Breadcrumbs
980
1005
 
981
1006
  `NavTabs` renders route links with the kit’s underline or pill presentation. Pass
@@ -560,6 +560,8 @@ type DropdownProps = {
560
560
  label?: string;
561
561
  /** What the trigger shows. Given one, the trigger is yours to update. */
562
562
  value?: string;
563
+ /** When given, the caller owns the selected row, including after a pick. */
564
+ selectedValue?: string | number;
563
565
  placeholder?: string;
564
566
  /** Inferred from the items when it is left out, exactly as the factory infers it. */
565
567
  variant?: 'select' | 'menu';
@@ -598,7 +600,7 @@ type DropdownProps = {
598
600
  /** Draw the row yourself. `props` is everything the row needs; spread it. */
599
601
  row?: (item: DropdownItem, props: DropdownRowProps) => ReactNode;
600
602
  };
601
- 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;
602
604
 
603
605
  type BackLinkOwnProps = {
604
606
  /** The address. No address, or a `javascript:` one, and nothing is rendered. */
@@ -1201,6 +1203,69 @@ declare const AccentPicker: react.ForwardRefExoticComponent<Omit<HTMLAttributes<
1201
1203
  options?: readonly Accent[];
1202
1204
  } & react.RefAttributes<HTMLDivElement>>;
1203
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
+
1204
1269
  type NavTabBadge = string | number | {
1205
1270
  text: string | number;
1206
1271
  tone?: 'neutral' | 'accent' | 'live' | 'danger';
@@ -1248,4 +1313,4 @@ declare const Breadcrumbs: react.ForwardRefExoticComponent<Omit<Omit<react.Detai
1248
1313
  declare const PAGE_SIZES: readonly number[];
1249
1314
  declare const DEFAULT_PAGE_SIZE: number;
1250
1315
 
1251
- 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 })
@@ -1882,12 +1882,12 @@ function Denied({
1882
1882
  title = "You don\u2019t have access",
1883
1883
  sub,
1884
1884
  need,
1885
- icon: icon8 = "lock",
1885
+ icon: icon9 = "lock",
1886
1886
  className,
1887
1887
  children
1888
1888
  }) {
1889
1889
  return /* @__PURE__ */ jsxs16("div", { className: cx4("ui-denied", className), children: [
1890
- /* @__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 }) }),
1891
1891
  /* @__PURE__ */ jsx20("div", { className: "ui-denied__title", children: title }),
1892
1892
  sub && /* @__PURE__ */ jsx20("div", { className: "ui-denied__sub", children: sub }),
1893
1893
  need && /* @__PURE__ */ jsxs16("div", { className: "ui-denied__need", children: [
@@ -2440,6 +2440,7 @@ var SEARCH_DEFAULTS = {
2440
2440
  function Dropdown({
2441
2441
  label,
2442
2442
  value,
2443
+ selectedValue,
2443
2444
  placeholder = "Select\u2026",
2444
2445
  variant,
2445
2446
  items,
@@ -2584,15 +2585,16 @@ function Dropdown({
2584
2585
  const name = ariaLabel || (label ? String(label).replace(/:\s*$/, "") : "") || "Options";
2585
2586
  const given = rows.find((it) => it.selected);
2586
2587
  const givenKey = given ? keyOf(given) : null;
2587
- const pickedKey = pick && pick.against === givenKey ? pick.key : null;
2588
+ const pickedKey = selectedValue === void 0 && pick && pick.against === givenKey ? pick.key : null;
2588
2589
  useEffect8(() => {
2589
2590
  if (pick && pick.against !== givenKey) setPick(null);
2590
2591
  }, [givenKey, pick]);
2591
2592
  const picked = pickedKey != null ? rows.find((it) => keyOf(it) === pickedKey) : void 0;
2592
- 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;
2593
2595
  const marked2 = isSelect && rows.some((it) => it.avatar);
2594
- const chosen = marked2 ? value != null ? rows.find((it) => it.label === value) : picked ?? given : void 0;
2595
- 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;
2596
2598
  const hiddenBy = (it) => Boolean(sx) && !dropdownMatch(it.label, query);
2597
2599
  const filtering = Boolean(sx) && dropdownFiltering(query);
2598
2600
  const visible = rows.filter((it) => !hiddenBy(it));
@@ -2770,7 +2772,7 @@ function Dropdown({
2770
2772
  e.preventDefault();
2771
2773
  return;
2772
2774
  }
2773
- if (isSelect) setPick({ key: keyOf(item), against: givenKey });
2775
+ if (isSelect && selectedValue === void 0) setPick({ key: keyOf(item), against: givenKey });
2774
2776
  onSelect?.(item.value, item);
2775
2777
  setOpen(false);
2776
2778
  trigger.current?.focus();
@@ -3595,11 +3597,11 @@ var cases = {
3595
3597
  "not-found": { title: "We can't find that page", sub: "Check the address or return to the home page.", icon: "folder" },
3596
3598
  "not-yet-built": { title: "This ships later", sub: "This screen is not available yet. Check back later.", icon: "clock" }
3597
3599
  };
3598
- function EmptyState({ variant = "first-run", title, sub, icon: icon8, art, actions }) {
3600
+ function EmptyState({ variant = "first-run", title, sub, icon: icon9, art, actions }) {
3599
3601
  const preset = cases[variant];
3600
3602
  const heading = title ?? preset.title;
3601
3603
  const guidance = sub ?? preset.sub;
3602
- const glyph = icon8 ?? preset.icon;
3604
+ const glyph = icon9 ?? preset.icon;
3603
3605
  return /* @__PURE__ */ jsxs27("div", { className: "ui-empty", children: [
3604
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,
3605
3607
  heading && /* @__PURE__ */ jsx32("div", { className: "ui-empty__title", children: heading }),
@@ -4341,10 +4343,10 @@ function Footer({
4341
4343
  brand !== false && /* @__PURE__ */ jsxs34("div", { className: "ui-footer__brand", children: [
4342
4344
  lockup,
4343
4345
  tagline && /* @__PURE__ */ jsx39("p", { className: "ui-footer__tagline", children: tagline }),
4344
- social.length > 0 && /* @__PURE__ */ jsx39("div", { className: "ui-footer__socials", children: social.map(({ label, href: to, icon: icon8 = "github" }) => {
4345
- const markup = SOCIAL_GLYPHS[icon8];
4346
- const fill = icon8 === "mail" ? "none" : "currentColor";
4347
- 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";
4348
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);
4349
4351
  }) })
4350
4352
  ] }),
@@ -5201,10 +5203,145 @@ var AccentPicker = forwardRef10(function AccentPicker2({
5201
5203
  );
5202
5204
  });
5203
5205
 
5204
- // src/NavTabs.tsx
5205
- import { Fragment as Fragment17, forwardRef as forwardRef11, useEffect as useEffect18, useRef as useRef23, useImperativeHandle as useImperativeHandle2 } from "react";
5206
- import { safeUrl as safeUrl3, observeNavFit, revealCurrentNav, revealFocusedNav } from "@apliteni/apliteni-ui";
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";
5207
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
+
5341
+ // src/NavTabs.tsx
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";
5208
5345
  var NavTabs = forwardRef11(function NavTabs2({
5209
5346
  items,
5210
5347
  active,
@@ -5222,7 +5359,7 @@ var NavTabs = forwardRef11(function NavTabs2({
5222
5359
  useEffect18(() => {
5223
5360
  if (navRef.current) revealCurrentNav(navRef.current);
5224
5361
  }, [active, signature]);
5225
- return /* @__PURE__ */ jsx43(
5362
+ return /* @__PURE__ */ jsx44(
5226
5363
  "nav",
5227
5364
  {
5228
5365
  ...rest,
@@ -5235,20 +5372,20 @@ var NavTabs = forwardRef11(function NavTabs2({
5235
5372
  },
5236
5373
  children: items.map((item) => {
5237
5374
  const badge = item.badge != null && typeof item.badge === "object" ? item.badge : { text: item.badge, tone: void 0 };
5238
- const inner = /* @__PURE__ */ jsxs37(Fragment18, { children: [
5239
- /* @__PURE__ */ jsx43("span", { className: "ui-nav__tab-label", children: item.label }),
5240
- 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 })
5241
5378
  ] });
5242
- 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);
5243
5380
  const current = item.id === active;
5244
5381
  const linkProps = {
5245
5382
  className: ["ui-nav__tab", current && "is-active"].filter(Boolean).join(" "),
5246
- href: safeUrl3(item.href || `#${item.id}`),
5383
+ href: safeUrl4(item.href || `#${item.id}`),
5247
5384
  target: item.target,
5248
5385
  "aria-current": current ? "page" : void 0,
5249
5386
  children: inner
5250
5387
  };
5251
- return /* @__PURE__ */ jsx43(Fragment17, { 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);
5252
5389
  })
5253
5390
  }
5254
5391
  );
@@ -5256,8 +5393,8 @@ var NavTabs = forwardRef11(function NavTabs2({
5256
5393
 
5257
5394
  // src/Breadcrumbs.tsx
5258
5395
  import { forwardRef as forwardRef12 } from "react";
5259
- import { icon as icon7, safeUrl as safeUrl4 } from "@apliteni/apliteni-ui";
5260
- import { Fragment as Fragment19, 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";
5261
5398
  var Breadcrumbs = forwardRef12(function Breadcrumbs2({
5262
5399
  items,
5263
5400
  renderLink,
@@ -5265,21 +5402,21 @@ var Breadcrumbs = forwardRef12(function Breadcrumbs2({
5265
5402
  "aria-label": ariaLabel = "Breadcrumb",
5266
5403
  ...rest
5267
5404
  }, ref) {
5268
- return /* @__PURE__ */ jsx44(
5405
+ return /* @__PURE__ */ jsx45(
5269
5406
  "nav",
5270
5407
  {
5271
5408
  ...rest,
5272
5409
  ref,
5273
5410
  className: ["ui-nav", "ui-nav--crumbs", className].filter(Boolean).join(" "),
5274
5411
  "aria-label": ariaLabel,
5275
- 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) => {
5276
5413
  const current = index === items.length - 1;
5277
- const inner = /* @__PURE__ */ jsxs38(Fragment19, { children: [
5278
- item.icon && /* @__PURE__ */ jsx44("span", { className: "ui-nav__ic", dangerouslySetInnerHTML: { __html: icon7(item.icon) } }),
5279
- /* @__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 })
5280
5417
  ] });
5281
- const linkProps = !current && item.href ? { className: "ui-nav__crumb", href: safeUrl4(item.href), target: item.target, children: inner } : null;
5282
- 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(
5283
5420
  "span",
5284
5421
  {
5285
5422
  className: ["ui-nav__crumb", current && "is-current"].filter(Boolean).join(" "),
@@ -5297,6 +5434,7 @@ var PAGE_SIZES = KIT_PAGE_SIZES;
5297
5434
  var DEFAULT_PAGE_SIZE3 = KIT_DEFAULT_PAGE_SIZE;
5298
5435
  export {
5299
5436
  AccentPicker,
5437
+ AccountMenu,
5300
5438
  AppShell,
5301
5439
  Avatar,
5302
5440
  BackLink,
@@ -5356,6 +5494,9 @@ export {
5356
5494
  Timeline,
5357
5495
  Toast,
5358
5496
  Tooltip,
5497
+ TopBar,
5498
+ VersionSwitcher,
5499
+ ViewSwitch,
5359
5500
  sortTableRows,
5360
5501
  useToast
5361
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
 
@@ -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); }