@eifi1/ui-kit 0.50.1 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +11 -9
  2. package/dist/admin/user-roster.js +31 -12
  3. package/dist/admin/user-roster.js.map +1 -1
  4. package/dist/auth/verify-email.d.ts +4 -0
  5. package/dist/auth/verify-email.js.map +1 -1
  6. package/dist/components/alert-banner.d.ts +9 -0
  7. package/dist/components/alert-banner.js +9 -5
  8. package/dist/components/alert-banner.js.map +1 -1
  9. package/dist/components/button-look.d.ts +4 -19
  10. package/dist/components/button-look.js +3 -30
  11. package/dist/components/button-look.js.map +1 -1
  12. package/dist/components/data-table-fit.js.map +1 -1
  13. package/dist/components/data-table-virtual.js +1 -0
  14. package/dist/components/data-table-virtual.js.map +1 -1
  15. package/dist/components/field-base.d.ts +31 -1
  16. package/dist/components/field-base.js +28 -0
  17. package/dist/components/field-base.js.map +1 -1
  18. package/dist/components/floating-panel.d.ts +6 -13
  19. package/dist/components/floating-panel.js +2 -9
  20. package/dist/components/floating-panel.js.map +1 -1
  21. package/dist/components/icon-button.js +3 -5
  22. package/dist/components/icon-button.js.map +1 -1
  23. package/dist/components/select.js +3 -2
  24. package/dist/components/select.js.map +1 -1
  25. package/dist/components/stat-tile.d.ts +8 -1
  26. package/dist/components/stat-tile.js +7 -3
  27. package/dist/components/stat-tile.js.map +1 -1
  28. package/dist/eslint/index.js +1 -1
  29. package/dist/i18n/german.js +2 -0
  30. package/dist/i18n/german.js.map +1 -1
  31. package/dist/i18n/kit-labels.d.ts +2 -2
  32. package/dist/i18n/kit-labels.js.map +1 -1
  33. package/dist/i18n/locales/en.js +2 -0
  34. package/dist/i18n/locales/en.js.map +2 -2
  35. package/dist/i18n/locales/es.js +2 -0
  36. package/dist/i18n/locales/es.js.map +1 -1
  37. package/dist/i18n/locales/fr.js +2 -0
  38. package/dist/i18n/locales/fr.js.map +1 -1
  39. package/dist/i18n/locales/hu.js +2 -0
  40. package/dist/i18n/locales/hu.js.map +1 -1
  41. package/dist/i18n/locales/it.js +2 -0
  42. package/dist/i18n/locales/it.js.map +1 -1
  43. package/dist/i18n/locales/zh.js +2 -0
  44. package/dist/i18n/locales/zh.js.map +1 -1
  45. package/dist/index.d.ts +3 -1
  46. package/dist/index.js +2 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/landing/page-seo.d.ts +19 -11
  49. package/dist/landing/page-seo.js +11 -7
  50. package/dist/landing/page-seo.js.map +1 -1
  51. package/dist/landing/public-page.d.ts +57 -0
  52. package/dist/landing/public-page.js +45 -0
  53. package/dist/landing/public-page.js.map +1 -0
  54. package/dist/lib/server-wake.d.ts +3 -2
  55. package/dist/lib/server-wake.js +19 -13
  56. package/dist/lib/server-wake.js.map +1 -1
  57. package/dist/shell/app-shell.d.ts +2 -15
  58. package/dist/shell/app-shell.js +7 -26
  59. package/dist/shell/app-shell.js.map +1 -1
  60. package/dist/shell/dev-menu.d.ts +98 -0
  61. package/dist/shell/dev-menu.js +133 -0
  62. package/dist/shell/dev-menu.js.map +1 -0
  63. package/dist/shell/dev-switchers.d.ts +6 -53
  64. package/dist/shell/dev-switchers.js +5 -100
  65. package/dist/shell/dev-switchers.js.map +1 -1
  66. package/dist/shell/top-bar-actions.d.ts +4 -1
  67. package/dist/shell/top-bar-actions.js.map +1 -1
  68. package/dist/shell/top-bar-parts-labels.d.ts +18 -9
  69. package/dist/shell/top-bar-parts-labels.js +2 -0
  70. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  71. package/dist/shell/topbar-action-menu.d.ts +17 -2
  72. package/dist/shell/topbar-action-menu.js +3 -1
  73. package/dist/shell/topbar-action-menu.js.map +1 -1
  74. package/dist/shell.d.ts +3 -1
  75. package/dist/shell.js +3 -1
  76. package/dist/shell.js.map +1 -1
  77. package/dist/theme/palette-presets.d.ts +0 -3
  78. package/dist/theme/palette-presets.js +0 -2
  79. package/dist/theme/palette-presets.js.map +1 -1
  80. package/package.json +1 -1
  81. package/src/admin/user-roster.tsx +30 -10
  82. package/src/auth/verify-email.tsx +4 -0
  83. package/src/components/alert-banner.tsx +37 -10
  84. package/src/components/button-look.ts +7 -57
  85. package/src/components/data-table-fit.ts +4 -1
  86. package/src/components/data-table-virtual.ts +14 -1
  87. package/src/components/field-base.tsx +53 -1
  88. package/src/components/floating-panel.tsx +6 -28
  89. package/src/components/icon-button.tsx +6 -8
  90. package/src/components/select.tsx +3 -6
  91. package/src/components/stat-tile.tsx +27 -9
  92. package/src/i18n/german.ts +2 -0
  93. package/src/i18n/kit-labels.tsx +2 -2
  94. package/src/i18n/locales/es.ts +2 -0
  95. package/src/i18n/locales/fr.ts +2 -0
  96. package/src/i18n/locales/hu.ts +2 -0
  97. package/src/i18n/locales/it.ts +2 -0
  98. package/src/i18n/locales/zh.ts +2 -0
  99. package/src/index.ts +4 -1
  100. package/src/landing/page-seo.ts +28 -16
  101. package/src/landing/public-page.tsx +106 -0
  102. package/src/lib/server-wake.ts +41 -17
  103. package/src/shell/app-shell.tsx +11 -48
  104. package/src/shell/dev-menu.tsx +259 -0
  105. package/src/shell/dev-switchers.tsx +13 -174
  106. package/src/shell/top-bar-actions.tsx +4 -1
  107. package/src/shell/top-bar-parts-labels.ts +20 -9
  108. package/src/shell/topbar-action-menu.tsx +28 -2
  109. package/src/shell.ts +5 -1
  110. package/src/theme/palette-presets.ts +0 -4
@@ -1,179 +1,18 @@
1
- import { useState } from "react";
2
- import type { ReactNode } from "react";
3
- import { FlaskConical, Undo2 } from "lucide-react";
4
- import { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from "../i18n/kit-labels";
5
- import { TopBarActionMenu, type TopBarMenuEntry } from "./topbar-action-menu";
6
- import { useTopBarPartsLabels, type DevSwitchersLabels } from "./top-bar-parts-labels";
1
+ import { DevMenu, type DevMenuProps } from "./dev-menu";
7
2
 
8
- /** Where the role the session started with is kept, unless the app names its own key. */
9
- export const DEV_ORIGINAL_ROLE_KEY = "ui-kit:dev-original-role";
3
+ // 0.42's local-dev switchers (docs/top-bar-harmonization.md §2.7, §6.2, §6.3), since 0.51
4
+ // a `DevMenu` with no app sections (§2.7.1). The props are the same and so is every row,
5
+ // "Back to {role}" and its `sessionStorage` included — but it draws ONE flask where it
6
+ // drew two: the role and the plan are groups of one menu, as in DevMenu.
10
7
 
11
- function readKey(key: string): string | null {
12
- try {
13
- return sessionStorage.getItem(key);
14
- } catch {
15
- return null;
16
- }
17
- }
18
-
19
- function writeKey(key: string, value: string | null): void {
20
- try {
21
- if (value === null) sessionStorage.removeItem(key);
22
- else sessionStorage.setItem(key, value);
23
- } catch {
24
- /* storage blocked: "Back to" lasts as long as this page */
25
- }
26
- }
27
-
28
- export interface DevSwitchersProps<R extends string, P extends string> {
29
- /** Draw anything at all: the app passes `import.meta.env.DEV`, so a production build
30
- * tree-shakes the switchers with the constant. */
31
- enabled: boolean;
32
- roles?: readonly R[];
33
- /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on
34
- * a JSX element is the ARIA role (as `RoleChip`'s `value`). */
35
- currentRole?: R;
36
- /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads
37
- * `/auth/me` (§2.7, §6.2). */
38
- onRole?: (role: R) => void;
39
- plans?: readonly P[];
40
- /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a
41
- * 404 while billing is off), the plan menu still renders, with no row checked; a
42
- * `GET /dev/switch` answers it. */
43
- currentPlan?: P;
44
- /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */
45
- onPlan?: (plan: P) => void;
46
- /**
47
- * The role to offer "Back to {role}" for. Left out, the kit keeps it itself: the role
48
- * before the first switch of this browser session, in `sessionStorage` under
49
- * `storageKey` — the server answers with the NEW role after a switch, so only the
50
- * browser still knows the old one.
51
- */
52
- originalRole?: R;
53
- /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};
54
- * an app with several accounts on one browser may scope it per user. */
55
- storageKey?: string;
56
- /** Billing is off locally: the plan menu says plans change nothing, and stays usable
57
- * (§6.3). */
58
- billingOff?: boolean;
59
- /** A role's or a plan's words in the menu. Default: the value itself. */
60
- roleLabel?: (role: R) => ReactNode;
61
- planLabel?: (plan: P) => ReactNode;
62
- /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */
63
- labels?: LabelOverride<DevSwitchersLabels>;
64
- }
65
-
66
- const flask = <FlaskConical className="size-5 text-warning" />;
67
-
68
- /** A label's text for a sentence, or the raw value when the label is not text. */
69
- const textOf = (label: ReactNode, value: string) =>
70
- typeof label === "string" || typeof label === "number" ? String(label) : value;
71
-
72
- /**
73
- * The top bar's local-dev switchers (docs/top-bar-harmonization.md §2.7, §6.2, §6.3): the
74
- * role, then the plan, each a menu behind `RoleSwitcher`'s amber flask. Nothing renders
75
- * unless `enabled`; either menu renders only with its list and handler.
76
- *
77
- * The role menu ends in "Back to {role}" once the role differs from the one the session
78
- * started with: the dev route needs only a signed-in user, so a switch to a lesser role
79
- * can always switch back. With `billingOff` the plan menu notes that plans change nothing
80
- * while billing is off.
81
- */
82
- export function DevSwitchers<R extends string, P extends string>({
83
- enabled,
84
- roles,
85
- currentRole: role,
86
- onRole,
87
- plans,
88
- currentPlan: plan,
89
- onPlan,
90
- originalRole,
91
- storageKey = DEV_ORIGINAL_ROLE_KEY,
92
- billingOff = false,
93
- roleLabel = (r) => r,
94
- planLabel = (p) => p,
95
- labels: labelsProp,
96
- }: DevSwitchersProps<R, P>) {
97
- const bar = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS);
98
- const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;
99
- const [kept, setKept] = useState<string | null>(() => (enabled ? readKey(storageKey) : null));
100
- // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the
101
- // dev route — so the kept role is read again then, or "Back to {role}" is lost after a
102
- // reload. Adjusted during render, React's pattern for state that follows a prop.
103
- const [wasEnabled, setWasEnabled] = useState(enabled);
104
- if (wasEnabled !== enabled) {
105
- setWasEnabled(enabled);
106
- if (enabled) setKept(readKey(storageKey));
107
- }
108
- if (!enabled) return null;
109
-
110
- const original = (originalRole ?? kept ?? undefined) as R | undefined;
111
-
112
- const pickRole = (next: R) => {
113
- if (originalRole === undefined) {
114
- if (next === original) {
115
- // Back where the session began: nothing left to go back to.
116
- writeKey(storageKey, null);
117
- setKept(null);
118
- } else if (kept === null && role !== undefined && next !== role) {
119
- writeKey(storageKey, role);
120
- setKept(role);
121
- }
122
- }
123
- onRole?.(next);
124
- };
125
-
126
- const roleMenu =
127
- roles && roles.length > 0 && role !== undefined && onRole ? (
128
- <TopBarActionMenu
129
- icon={flask}
130
- aria-label={`${bar.switchRole} · ${bar.role(role)}`}
131
- entries={[
132
- { kind: "heading", key: "head-role", label: words.roleHeading },
133
- ...roles.map(
134
- (r): TopBarMenuEntry => ({ key: `role-${r}`, label: roleLabel(r), checked: r === role, onSelect: () => pickRole(r) }),
135
- ),
136
- ...(original !== undefined && original !== role
137
- ? ([
138
- { kind: "divider", key: "sep-back" },
139
- {
140
- key: "back",
141
- icon: <Undo2 className="size-4" />,
142
- label: words.backTo(textOf(roleLabel(original), original)),
143
- onSelect: () => pickRole(original),
144
- },
145
- ] satisfies TopBarMenuEntry[])
146
- : []),
147
- ]}
148
- />
149
- ) : null;
8
+ // Where it has always been imported from, until the wrapper goes in 0.52; the key and the
9
+ // "Back to" logic it serves are DevMenu's now.
10
+ export { DEV_ORIGINAL_ROLE_KEY } from "./dev-menu";
150
11
 
151
- const planMenu =
152
- plans && plans.length > 0 && onPlan ? (
153
- <TopBarActionMenu
154
- icon={flask}
155
- aria-label={plan !== undefined ? `${words.switchPlan} · ${words.plan(plan)}` : words.switchPlan}
156
- entries={[
157
- { kind: "heading", key: "head-plan", label: words.planHeading },
158
- ...plans.map(
159
- (p): TopBarMenuEntry => ({ key: `plan-${p}`, label: planLabel(p), checked: p === plan, onSelect: () => onPlan(p) }),
160
- ),
161
- ]}
162
- >
163
- {billingOff
164
- ? () => (
165
- <li data-dev-billing-off="" className="mt-1 max-w-64 border-t border-subtle px-3 py-2 text-xs text-muted">
166
- {words.billingOff}
167
- </li>
168
- )
169
- : undefined}
170
- </TopBarActionMenu>
171
- ) : null;
12
+ /** @deprecated 0.51 — use {@link DevMenuProps} (the same props); removed in 0.52. */
13
+ export type DevSwitchersProps<R extends string, P extends string> = Omit<DevMenuProps<R, P>, "sections" | "active">;
172
14
 
173
- return (
174
- <>
175
- {roleMenu}
176
- {planMenu}
177
- </>
178
- );
15
+ /** @deprecated 0.51 — use {@link DevMenu} (one flask; same props); removed in 0.52. */
16
+ export function DevSwitchers<R extends string, P extends string>(props: DevSwitchersProps<R, P>) {
17
+ return <DevMenu {...props} />;
179
18
  }
@@ -20,7 +20,10 @@ export interface TopBarActionsProps {
20
20
  assistant?: ReactNode;
21
21
  /** `SyncStatusIndicator`. */
22
22
  sync?: ReactNode;
23
- /** `DevSwitchers` (role, then plan). */
23
+ /** `DevMenu` (0.51, docs/top-bar-harmonization.md §2.7.1): the one flask — the role,
24
+ * the plan, then the app's own dev sections. Fill it only behind
25
+ * `import.meta.env.DEV`, so a production build drops the branch. (0.42's
26
+ * `DevSwitchers`, deprecated, is a DevMenu with no sections.) */
24
27
  dev?: ReactNode;
25
28
  /** kastlan's platform button. */
26
29
  platform?: ReactNode;
@@ -2,10 +2,11 @@ import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
2
2
 
3
3
  /**
4
4
  * The `topBarParts` namespace (0.42, docs/top-bar-harmonization.md §3.1): the words of the
5
- * top bar's shared parts — `TourMenu`, `SyncStatusIndicator` and `DevSwitchers`. A module
5
+ * top bar's shared parts — `TourMenu`, `SyncStatusIndicator` and `DevMenu` (0.42's
6
+ * `DevSwitchers`, whose words these still are, §2.7.1). A module
6
7
  * of words with no component code, like every `*-labels.ts` (0.34, KS U8). The language
7
- * sub-list (`AccountMenuLanguage`) names itself with `topBar.language`, and the dev role
8
- * switcher's name and tooltip are `topBar.switchRole` / `topBar.role`, as `RoleSwitcher`'s.
8
+ * sub-list (`AccountMenuLanguage`) names itself with `topBar.language`, and the role in the
9
+ * dev flask's name is `topBar.role`, as `RoleSwitcher`'s tooltip.
9
10
  */
10
11
 
11
12
  /** `TourMenu`'s words (§2.4). */
@@ -69,19 +70,27 @@ export interface SyncStatusLabels {
69
70
  saving: string;
70
71
  }
71
72
 
72
- /** `DevSwitchers`' words (§2.7, §6.2, §6.3). */
73
+ /**
74
+ * `DevMenu`'s words (§2.7, §2.7.1, §6.2, §6.3). Still named for 0.42's `DevSwitchers`: the
75
+ * namespace is the same `topBarParts.dev`, and an app's label tree keeps compiling.
76
+ */
73
77
  export interface DevSwitchersLabels {
74
- /** The heading of the role menu. */
78
+ /** 0.51: the flask's name, and the menu's — the role and the plan follow it. */
79
+ menu: string;
80
+ /** 0.51: what the dot on the flask means while `active`, joined to its name — an
81
+ * override the developer could forget is on (a "today", a slow server). */
82
+ overridesOn: string;
83
+ /** The heading of the role group. */
75
84
  roleHeading: string;
76
- /** The plan switcher's name. */
85
+ /** @deprecated 0.51 — DevMenu's one flask is named by `menu`; removed in 0.52. */
77
86
  switchPlan: string;
78
- /** The plan switcher's tooltip, given the active plan. */
87
+ /** The plan in the flask's name, given the active plan. */
79
88
  plan: (value: string) => string;
80
- /** The heading of the plan menu. */
89
+ /** The heading of the plan group. */
81
90
  planHeading: string;
82
91
  /** The row back to the role the session started with. */
83
92
  backTo: (role: string) => string;
84
- /** The plan menu's note while billing is off locally. */
93
+ /** The plan group's note while billing is off locally. */
85
94
  billingOff: string;
86
95
  }
87
96
 
@@ -127,6 +136,8 @@ export const DEFAULT_TOP_BAR_PARTS_LABELS: TopBarPartsLabels = {
127
136
  saving: "Saving…",
128
137
  },
129
138
  dev: {
139
+ menu: "Dev tools",
140
+ overridesOn: "Overrides on",
130
141
  roleHeading: "Dev · role",
131
142
  switchPlan: "Switch plan",
132
143
  plan: (value) => `Plan: ${value}`,
@@ -135,6 +135,16 @@ export interface TopBarMenuHeader {
135
135
  const SECTION_CONTROLS = "a[href]:not([role]),button:not([role])";
136
136
  const TABBABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]';
137
137
 
138
+ /**
139
+ * `scrollable`'s cap (0.51, DevMenu): the glass under the bar. Under `TopBar` the panel
140
+ * starts about 50px down — the `size-9` trigger centred in the `h-12` bar ends at 42px,
141
+ * and `HoverMenu` hangs the panel `pt-2` below it — so 4rem (64px) leaves some 14px clear
142
+ * under the last row. `dvh`, not `vh`: on a phone `100vh` is the viewport with the
143
+ * browser's toolbars put away, taller than the glass while they show.
144
+ * `overscroll-contain`, so a fling past the last row does not scroll the page behind.
145
+ */
146
+ const SCROLLABLE_PANEL = "max-h-[calc(100dvh-4rem)] overflow-y-auto overscroll-contain";
147
+
138
148
  /**
139
149
  * A header or footer inside the menu panel.
140
150
  *
@@ -195,7 +205,8 @@ function keepTabInsidePanel(e: ReactKeyboardEvent<HTMLUListElement>) {
195
205
  * A long menu is grouped by topic with `{ kind: "heading" }` entries (0.23) — see
196
206
  * {@link TopBarMenuEntry}. There is no separate phone layout: the panel is the same
197
207
  * hover panel at 390px, kept inside the viewport by `HoverMenu`, and a heading wraps
198
- * rather than widening it.
208
+ * rather than widening it. A menu that can grow taller than a phone's glass passes
209
+ * `scrollable` (0.51), since that clamp is sideways only.
199
210
  */
200
211
  export function TopBarActionMenu({
201
212
  icon,
@@ -213,6 +224,7 @@ export function TopBarActionMenu({
213
224
  footerLabel,
214
225
  align,
215
226
  panelClassName,
227
+ scrollable = false,
216
228
  triggerAttributes,
217
229
  }: {
218
230
  /** The trigger's face for the usual icon button. Ignored when `trigger` is given. */
@@ -282,6 +294,20 @@ export function TopBarActionMenu({
282
294
  * direction (see `HoverMenu`, whose type this is). Default `end`. */
283
295
  align?: HoverMenuProps["align"];
284
296
  panelClassName?: string;
297
+ /**
298
+ * Cap the panel at the glass under the top bar and scroll the rows inside it (0.51,
299
+ * `DevMenu`). `HoverMenu` keeps a panel inside the viewport sideways only, so a tall
300
+ * one ran off the bottom of the screen: DevMenu's ~25 rows on a 390×844 phone, with
301
+ * the last ones out of reach — the page cannot scroll them up, the panel hangs from
302
+ * the sticky bar.
303
+ *
304
+ * Off by default: a menu that fits keeps its natural height and has no scroll
305
+ * container (`TourMenu` keeps its own `max-h-[70vh]`). The keyboard needs nothing
306
+ * extra: `HoverMenu`'s roving focus is a plain `focus()`, which the browser follows by
307
+ * scrolling the panel to the row, so ↓, ↑, Home and End reach a row below the cap. The
308
+ * header and the footer are inside the list and scroll with the rows.
309
+ */
310
+ scrollable?: boolean;
285
311
  /** `data-*` attributes for the trigger itself — a tour's anchor (`data-tour`), a test
286
312
  * hook. The parts built on this menu (`TourMenu`) pass theirs through here. */
287
313
  triggerAttributes?: { [key: `data-${string}`]: string | number | boolean | undefined };
@@ -389,7 +415,7 @@ export function TopBarActionMenu({
389
415
  );
390
416
  return (
391
417
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- a Tab bubbling up from a row or footer link, see keepTabInsidePanel
392
- <ul className={cn("py-1", panelClassName)} onKeyDown={keepTabInsidePanel}>
418
+ <ul className={cn("py-1", scrollable && SCROLLABLE_PANEL, panelClassName)} onKeyDown={keepTabInsidePanel}>
393
419
  {header && (
394
420
  <MenuSection className="mb-1 border-b border-subtle px-3 py-2">
395
421
  <div className="truncate text-sm font-medium text-primary">{header.title}</div>
package/src/shell.ts CHANGED
@@ -89,7 +89,11 @@ export { TourMenu } from "./shell/tour-menu";
89
89
  export type { TourMenuProps, TourMenuTour } from "./shell/tour-menu";
90
90
  export { SyncStatusIndicator, syncState } from "./shell/sync-status-indicator";
91
91
  export type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from "./shell/sync-status-indicator";
92
- export { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from "./shell/dev-switchers";
92
+ // 0.51 (§2.7.1, keksdose): one flask, the kit's Account section and the app's own dev
93
+ // sections; `DevSwitchers` is a deprecated wrapper over it until 0.52.
94
+ export { DevMenu, DEV_ORIGINAL_ROLE_KEY } from "./shell/dev-menu";
95
+ export type { DevMenuProps, DevMenuSection } from "./shell/dev-menu";
96
+ export { DevSwitchers } from "./shell/dev-switchers";
93
97
  export type { DevSwitchersProps } from "./shell/dev-switchers";
94
98
  export { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from "./shell/top-bar-parts-labels";
95
99
  export type {
@@ -211,10 +211,6 @@ export const PAUL_TOL_PRESET: PalettePreset = {
211
211
  },
212
212
  };
213
213
 
214
- /** @deprecated 0.49 — use `DEFAULT_PRESET` (Imprint IS the default now); removed in 0.51.
215
- * A persisted `"imprint"` id resolves to it through `presetById`'s fallback. */
216
- export const IMPRINT_PRESET: PalettePreset = DEFAULT_PRESET;
217
-
218
214
  // The full-theme switcher from #307 stayed retired (feedback #328) — the default
219
215
  // is still the single source of truth for surfaces, brand and money (its five
220
216
  // alternatives are gone, see the header). What IS selectable is