@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.
- package/README.md +11 -9
- package/dist/admin/user-roster.js +31 -12
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/verify-email.d.ts +4 -0
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/components/alert-banner.d.ts +9 -0
- package/dist/components/alert-banner.js +9 -5
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/button-look.d.ts +4 -19
- package/dist/components/button-look.js +3 -30
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-virtual.js +1 -0
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/field-base.d.ts +31 -1
- package/dist/components/field-base.js +28 -0
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/floating-panel.d.ts +6 -13
- package/dist/components/floating-panel.js +2 -9
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/icon-button.js +3 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/select.js +3 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/stat-tile.d.ts +8 -1
- package/dist/components/stat-tile.js +7 -3
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/i18n/german.js +2 -0
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +2 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +2 -0
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +2 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +2 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +2 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +2 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +2 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/page-seo.d.ts +19 -11
- package/dist/landing/page-seo.js +11 -7
- package/dist/landing/page-seo.js.map +1 -1
- package/dist/landing/public-page.d.ts +57 -0
- package/dist/landing/public-page.js +45 -0
- package/dist/landing/public-page.js.map +1 -0
- package/dist/lib/server-wake.d.ts +3 -2
- package/dist/lib/server-wake.js +19 -13
- package/dist/lib/server-wake.js.map +1 -1
- package/dist/shell/app-shell.d.ts +2 -15
- package/dist/shell/app-shell.js +7 -26
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +98 -0
- package/dist/shell/dev-menu.js +133 -0
- package/dist/shell/dev-menu.js.map +1 -0
- package/dist/shell/dev-switchers.d.ts +6 -53
- package/dist/shell/dev-switchers.js +5 -100
- package/dist/shell/dev-switchers.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +4 -1
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +18 -9
- package/dist/shell/top-bar-parts-labels.js +2 -0
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +17 -2
- package/dist/shell/topbar-action-menu.js +3 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +3 -1
- package/dist/shell.js +3 -1
- package/dist/shell.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +0 -3
- package/dist/theme/palette-presets.js +0 -2
- package/dist/theme/palette-presets.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/user-roster.tsx +30 -10
- package/src/auth/verify-email.tsx +4 -0
- package/src/components/alert-banner.tsx +37 -10
- package/src/components/button-look.ts +7 -57
- package/src/components/data-table-fit.ts +4 -1
- package/src/components/data-table-virtual.ts +14 -1
- package/src/components/field-base.tsx +53 -1
- package/src/components/floating-panel.tsx +6 -28
- package/src/components/icon-button.tsx +6 -8
- package/src/components/select.tsx +3 -6
- package/src/components/stat-tile.tsx +27 -9
- package/src/i18n/german.ts +2 -0
- package/src/i18n/kit-labels.tsx +2 -2
- package/src/i18n/locales/es.ts +2 -0
- package/src/i18n/locales/fr.ts +2 -0
- package/src/i18n/locales/hu.ts +2 -0
- package/src/i18n/locales/it.ts +2 -0
- package/src/i18n/locales/zh.ts +2 -0
- package/src/index.ts +4 -1
- package/src/landing/page-seo.ts +28 -16
- package/src/landing/public-page.tsx +106 -0
- package/src/lib/server-wake.ts +41 -17
- package/src/shell/app-shell.tsx +11 -48
- package/src/shell/dev-menu.tsx +259 -0
- package/src/shell/dev-switchers.tsx +13 -174
- package/src/shell/top-bar-actions.tsx +4 -1
- package/src/shell/top-bar-parts-labels.ts +20 -9
- package/src/shell/topbar-action-menu.tsx +28 -2
- package/src/shell.ts +5 -1
- package/src/theme/palette-presets.ts +0 -4
|
@@ -1,179 +1,18 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
/** `
|
|
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 `
|
|
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
|
|
8
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
85
|
+
/** @deprecated 0.51 — DevMenu's one flask is named by `menu`; removed in 0.52. */
|
|
77
86
|
switchPlan: string;
|
|
78
|
-
/** The 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
|
|
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
|
|
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
|
-
|
|
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
|