@mdbase-dev/ui 0.1.0-beta.126 → 0.1.0-beta.148
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 +168 -0
- package/css/collections.css +978 -0
- package/css/controls.css +33 -1
- package/css/screens.css +44 -2
- package/dist/AppSwitcher.js +187 -33
- package/dist/Checkbox.d.ts +14 -0
- package/dist/Checkbox.js +32 -0
- package/dist/CollectionModeControl.d.ts +11 -0
- package/dist/CollectionModeControl.js +290 -0
- package/dist/CommandPalette.d.ts +4 -2
- package/dist/CommandPalette.js +275 -103
- package/dist/Dialog.js +72 -25
- package/dist/Feedback.js +763 -229
- package/dist/FeedbackMarkup.d.ts +1 -1
- package/dist/FeedbackMarkup.js +326 -125
- package/dist/FeedbackVerification.js +115 -68
- package/dist/SaveNotice.js +71 -28
- package/dist/Select.js +183 -135
- package/dist/SelectList.js +59 -14
- package/dist/ThemeSelect.js +55 -4
- package/dist/apps.d.ts +1 -0
- package/dist/apps.js +50 -29
- package/dist/brand.js +251 -68
- package/dist/codemirror.js +116 -87
- package/dist/collections/CollectionGate.d.ts +52 -0
- package/dist/collections/CollectionGate.js +456 -0
- package/dist/collections/CollectionSaveWarning.d.ts +10 -0
- package/dist/collections/CollectionSaveWarning.js +28 -0
- package/dist/collections/CollectionSwitcher.d.ts +51 -0
- package/dist/collections/CollectionSwitcher.js +598 -0
- package/dist/collections/SetupReview.d.ts +9 -0
- package/dist/collections/SetupReview.js +216 -0
- package/dist/collections/copy.d.ts +100 -0
- package/dist/collections/copy.js +937 -0
- package/dist/collections/dialogs.d.ts +27 -0
- package/dist/collections/dialogs.js +169 -0
- package/dist/collections/model.d.ts +146 -0
- package/dist/collections/model.js +337 -0
- package/dist/collections/mount.d.ts +19 -0
- package/dist/collections/mount.js +56 -0
- package/dist/collections/parts.d.ts +86 -0
- package/dist/collections/parts.js +439 -0
- package/dist/collections/testing.d.ts +19 -0
- package/dist/collections/testing.js +39 -0
- package/dist/collections/use-collection-ui.d.ts +140 -0
- package/dist/collections/use-collection-ui.js +236 -0
- package/dist/collections.d.ts +19 -0
- package/dist/collections.js +13 -0
- package/dist/feedback-data.d.ts +13 -12
- package/dist/feedback-data.js +365 -168
- package/dist/flock.d.ts +63 -0
- package/dist/flock.js +140 -0
- package/dist/i18n.d.ts +6 -0
- package/dist/i18n.js +71 -0
- package/dist/locales/de/messages.json +1 -0
- package/dist/locales/en/messages.json +1 -0
- package/dist/locales/es/messages.json +1 -0
- package/dist/locales/fr/messages.json +1 -0
- package/dist/locales/it/messages.json +1 -0
- package/dist/locales/ja/messages.json +1 -0
- package/dist/locales/ko/messages.json +1 -0
- package/dist/locales/pt/messages.json +1 -0
- package/dist/locales/ru/messages.json +1 -0
- package/dist/locales/zh/messages.json +1 -0
- package/dist/mark-activity.js +82 -75
- package/dist/permission-selection.js +27 -17
- package/dist/popover.js +138 -130
- package/dist/screens.d.ts +20 -3
- package/dist/screens.js +275 -6
- package/dist/select-model.js +34 -28
- package/dist/text-diff.d.ts +1 -0
- package/dist/text-diff.js +106 -74
- package/dist/theme.js +34 -40
- package/package.json +56 -7
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { describeError } from "@mdbase-dev/sdk/describe";
|
|
4
|
+
import { OpeningScreen } from "../screens.js";
|
|
5
|
+
import { CollectionMdbaseContext } from "./CollectionSaveWarning.js";
|
|
6
|
+
import { SwitcherContent } from "./CollectionSwitcher.js";
|
|
7
|
+
import { RowDialogs, SignOutDialog } from "./dialogs.js";
|
|
8
|
+
import { problemControl, actionNotice } from "./model.js";
|
|
9
|
+
import { ActionButton, CollectionMark, GateFooter, ItemCopy, ItemMore, NoticeBlock, RowCopy, RowMore } from "./parts.js";
|
|
10
|
+
import { SetupReview } from "./SetupReview.js";
|
|
11
|
+
import { useAutoStart, useCollectionUi } from "./use-collection-ui.js";
|
|
12
|
+
/**
|
|
13
|
+
* Everything before a collection is open: sign-in, choosing, approval, setup
|
|
14
|
+
* review and problems. In phase `ready` it renders the app, with a banner
|
|
15
|
+
* above it for a live problem (migrating, update required, access removed).
|
|
16
|
+
*/
|
|
17
|
+
export function CollectionGate({
|
|
18
|
+
children,
|
|
19
|
+
renderAppCollection,
|
|
20
|
+
extraActions,
|
|
21
|
+
layout = "page",
|
|
22
|
+
autoStart = true,
|
|
23
|
+
...options
|
|
24
|
+
}) {
|
|
25
|
+
const ui = useCollectionUi(options);
|
|
26
|
+
useAutoStart(options.mdbase, autoStart);
|
|
27
|
+
if (ui.appOpen && renderAppCollection) {
|
|
28
|
+
return _jsxs(CollectionMdbaseContext, {
|
|
29
|
+
value: options.mdbase,
|
|
30
|
+
children: [renderAppCollection(ui.appOpen.item, {
|
|
31
|
+
section: ui.appOpen.section,
|
|
32
|
+
chooseAnother: ui.actions.chooseAnother
|
|
33
|
+
}), _jsx(SignOutDialog, {
|
|
34
|
+
ui: ui
|
|
35
|
+
})]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (ui.view.kind === "ready") {
|
|
39
|
+
return _jsxs(_Fragment, {
|
|
40
|
+
children: [ui.view.notice && _jsx("div", {
|
|
41
|
+
className: "mdbase-collections",
|
|
42
|
+
children: _jsx(NoticeBlock, {
|
|
43
|
+
notice: ui.view.notice,
|
|
44
|
+
ui: ui,
|
|
45
|
+
variant: "banner"
|
|
46
|
+
})
|
|
47
|
+
}), _jsx(CollectionMdbaseContext, {
|
|
48
|
+
value: options.mdbase,
|
|
49
|
+
children: children(ui.view.phase.connection)
|
|
50
|
+
}), _jsx(SignOutDialog, {
|
|
51
|
+
ui: ui
|
|
52
|
+
})]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return _jsx(GateScreen, {
|
|
56
|
+
ui: ui,
|
|
57
|
+
layout: layout,
|
|
58
|
+
extraActions: extraActions
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* For write-first apps and settings dialogs: the gate's screens in a panel and,
|
|
63
|
+
* once a collection is open, the switcher's list inline.
|
|
64
|
+
*/
|
|
65
|
+
export function CollectionPanel({
|
|
66
|
+
extraActions,
|
|
67
|
+
autoStart = true,
|
|
68
|
+
...options
|
|
69
|
+
}) {
|
|
70
|
+
const ui = useCollectionUi(options);
|
|
71
|
+
useAutoStart(options.mdbase, autoStart);
|
|
72
|
+
const [dialog, setDialog] = useState(null);
|
|
73
|
+
if (ui.view.kind !== "ready") return _jsx(GateScreen, {
|
|
74
|
+
ui: ui,
|
|
75
|
+
layout: "panel",
|
|
76
|
+
extraActions: extraActions
|
|
77
|
+
});
|
|
78
|
+
return _jsxs("div", {
|
|
79
|
+
className: "mdbase-collections mdbase-collections-panel",
|
|
80
|
+
children: [ui.view.notice && _jsx(NoticeBlock, {
|
|
81
|
+
notice: ui.view.notice,
|
|
82
|
+
ui: ui,
|
|
83
|
+
variant: "inline"
|
|
84
|
+
}), _jsx(SwitcherContent, {
|
|
85
|
+
ui: ui,
|
|
86
|
+
onDone: () => {},
|
|
87
|
+
onDialog: setDialog
|
|
88
|
+
}), _jsx(RowDialogs, {
|
|
89
|
+
ui: ui,
|
|
90
|
+
dialog: dialog,
|
|
91
|
+
onClose: () => setDialog(null)
|
|
92
|
+
}), _jsx(SignOutDialog, {
|
|
93
|
+
ui: ui
|
|
94
|
+
})]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** One problem with its fix, for banners and toasts inside the app. Renders nothing when there is none. */
|
|
98
|
+
export function CollectionProblem({
|
|
99
|
+
problem,
|
|
100
|
+
variant = "banner",
|
|
101
|
+
...options
|
|
102
|
+
}) {
|
|
103
|
+
const ui = useCollectionUi(options);
|
|
104
|
+
const {
|
|
105
|
+
view
|
|
106
|
+
} = ui;
|
|
107
|
+
let notice = view.kind === "ready" || view.kind === "problem" || view.kind === "unavailable" ? view.notice : null;
|
|
108
|
+
if (problem !== undefined) {
|
|
109
|
+
const described = describeError(problem);
|
|
110
|
+
const quiet = actionNotice(described);
|
|
111
|
+
notice = described && quiet ? {
|
|
112
|
+
...quiet,
|
|
113
|
+
control: problemControl(described, {
|
|
114
|
+
collectionId: view.current?.id ?? null,
|
|
115
|
+
signedIn: view.signedIn,
|
|
116
|
+
open: view.kind === "ready"
|
|
117
|
+
})
|
|
118
|
+
} : null;
|
|
119
|
+
}
|
|
120
|
+
if (!notice) return null;
|
|
121
|
+
return _jsx("div", {
|
|
122
|
+
className: "mdbase-collections",
|
|
123
|
+
children: _jsx(NoticeBlock, {
|
|
124
|
+
notice: notice,
|
|
125
|
+
ui: ui,
|
|
126
|
+
variant: variant
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function GateScreen({
|
|
131
|
+
ui,
|
|
132
|
+
layout,
|
|
133
|
+
extraActions
|
|
134
|
+
}) {
|
|
135
|
+
const {
|
|
136
|
+
view,
|
|
137
|
+
options
|
|
138
|
+
} = ui;
|
|
139
|
+
const extra = typeof extraActions === "function" ? extraActions(view) : extraActions;
|
|
140
|
+
// An empty SDK list is not "no collections" when the app lists its own.
|
|
141
|
+
const appListed = view.kind === "choosing" && view.collections.length === 0 && (options.sections ?? []).some(section => section.items.length > 0);
|
|
142
|
+
const headingText = appListed ? ui.say("chooseHeading") : view.heading;
|
|
143
|
+
const lead = appListed ? null : view.lead;
|
|
144
|
+
const heading = useRef(null);
|
|
145
|
+
const root = useRef(null);
|
|
146
|
+
const [dialog, setDialog] = useState(null);
|
|
147
|
+
const previousKind = useRef(null);
|
|
148
|
+
// Move focus to the new heading when the phase changes, unless the person is busy elsewhere on the page.
|
|
149
|
+
useLayoutEffect(() => {
|
|
150
|
+
const before = previousKind.current;
|
|
151
|
+
previousKind.current = view.kind;
|
|
152
|
+
if (before === null || before === view.kind) return;
|
|
153
|
+
const active = document.activeElement;
|
|
154
|
+
if (!active || active === document.body || root.current?.contains(active)) heading.current?.focus({
|
|
155
|
+
preventScroll: true
|
|
156
|
+
});
|
|
157
|
+
}, [view.kind]);
|
|
158
|
+
const Heading = layout === "page" ? "h1" : "h2";
|
|
159
|
+
const opening = view.kind === "starting" || view.kind === "opening";
|
|
160
|
+
const announce = view.kind === "authorizing" ? view.heading : "";
|
|
161
|
+
return _jsxs("div", {
|
|
162
|
+
ref: root,
|
|
163
|
+
className: "mdbase-collections mdbase-collections-gate",
|
|
164
|
+
"data-layout": layout,
|
|
165
|
+
"data-phase": view.kind,
|
|
166
|
+
"aria-busy": ui.busy !== null || undefined,
|
|
167
|
+
children: [_jsxs("div", {
|
|
168
|
+
className: "mdbase-collections-gate-body",
|
|
169
|
+
children: [!opening && _jsxs("p", {
|
|
170
|
+
className: "mdbase-collections-brand",
|
|
171
|
+
children: [options.app.mark && _jsx("span", {
|
|
172
|
+
className: "mdbase-collections-brand-mark",
|
|
173
|
+
"aria-hidden": "true",
|
|
174
|
+
children: options.app.mark
|
|
175
|
+
}), _jsx("span", {
|
|
176
|
+
children: options.app.name
|
|
177
|
+
})]
|
|
178
|
+
}), _jsx(Heading, {
|
|
179
|
+
ref: heading,
|
|
180
|
+
tabIndex: -1,
|
|
181
|
+
className: opening ? "mdbase-collections-visually-hidden" : "mdbase-collections-heading",
|
|
182
|
+
children: headingText
|
|
183
|
+
}), opening ? _jsx(OpeningScreen, {
|
|
184
|
+
app: options.app,
|
|
185
|
+
layout: layout,
|
|
186
|
+
title: headingText,
|
|
187
|
+
detail: lead ?? undefined
|
|
188
|
+
}) : lead && _jsx("p", {
|
|
189
|
+
className: "mdbase-collections-lead",
|
|
190
|
+
children: lead
|
|
191
|
+
}), _jsx(GateBody, {
|
|
192
|
+
ui: ui,
|
|
193
|
+
extraActions: view.kind === "signed_out" ? extra : undefined,
|
|
194
|
+
onDialog: setDialog
|
|
195
|
+
}), ui.error && _jsx(NoticeBlock, {
|
|
196
|
+
notice: ui.error,
|
|
197
|
+
ui: ui,
|
|
198
|
+
variant: "inline"
|
|
199
|
+
}), _jsx(GateFooter, {
|
|
200
|
+
ui: ui,
|
|
201
|
+
chooseAnother: showsChooseAnother(view),
|
|
202
|
+
extra: view.kind === "signed_out" ? undefined : extra
|
|
203
|
+
})]
|
|
204
|
+
}), _jsx("p", {
|
|
205
|
+
className: "mdbase-collections-visually-hidden",
|
|
206
|
+
role: "status",
|
|
207
|
+
children: announce
|
|
208
|
+
}), _jsx(RowDialogs, {
|
|
209
|
+
ui: ui,
|
|
210
|
+
dialog: dialog,
|
|
211
|
+
onClose: () => setDialog(null)
|
|
212
|
+
}), _jsx(SignOutDialog, {
|
|
213
|
+
ui: ui
|
|
214
|
+
})]
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function showsChooseAnother(view) {
|
|
218
|
+
if (!view.canChooseAnother) return false;
|
|
219
|
+
switch (view.kind) {
|
|
220
|
+
case "opening":
|
|
221
|
+
return view.slow;
|
|
222
|
+
case "needs_approval":
|
|
223
|
+
case "needs_setup":
|
|
224
|
+
return true;
|
|
225
|
+
case "problem":
|
|
226
|
+
return view.notice.control?.kind !== "choose_another";
|
|
227
|
+
default:
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function GateBody({
|
|
232
|
+
ui,
|
|
233
|
+
extraActions,
|
|
234
|
+
onDialog
|
|
235
|
+
}) {
|
|
236
|
+
const {
|
|
237
|
+
view,
|
|
238
|
+
copy,
|
|
239
|
+
busy,
|
|
240
|
+
actions
|
|
241
|
+
} = ui;
|
|
242
|
+
const inert = busy !== null;
|
|
243
|
+
switch (view.kind) {
|
|
244
|
+
case "starting":
|
|
245
|
+
case "opening":
|
|
246
|
+
return null;
|
|
247
|
+
case "signed_out":
|
|
248
|
+
return _jsxs(_Fragment, {
|
|
249
|
+
children: [view.notice && _jsx(NoticeBlock, {
|
|
250
|
+
notice: view.notice,
|
|
251
|
+
ui: ui,
|
|
252
|
+
variant: "inline"
|
|
253
|
+
}), _jsxs("div", {
|
|
254
|
+
className: "mdbase-collections-actions",
|
|
255
|
+
children: [_jsx(ActionButton, {
|
|
256
|
+
tone: "primary",
|
|
257
|
+
inert: inert,
|
|
258
|
+
progress: busy === "sign_out" ? copy.signingOut : busy === "sign_in" ? copy.addingCollection : null,
|
|
259
|
+
onClick: actions.signIn,
|
|
260
|
+
children: copy.signIn
|
|
261
|
+
}), extraActions]
|
|
262
|
+
}), _jsx(AppSections, {
|
|
263
|
+
ui: ui,
|
|
264
|
+
onDialog: onDialog
|
|
265
|
+
})]
|
|
266
|
+
});
|
|
267
|
+
case "authorizing":
|
|
268
|
+
return _jsxs("div", {
|
|
269
|
+
className: "mdbase-collections-actions",
|
|
270
|
+
children: [_jsx(ActionButton, {
|
|
271
|
+
tone: "secondary",
|
|
272
|
+
onClick: actions.reopen,
|
|
273
|
+
children: copy.openWindowAgain
|
|
274
|
+
}), busy === "sign_in" || busy === "add" || busy === "approve" ? _jsx(ActionButton, {
|
|
275
|
+
tone: "quiet",
|
|
276
|
+
onClick: actions.cancel,
|
|
277
|
+
children: copy.cancel
|
|
278
|
+
}) : null]
|
|
279
|
+
});
|
|
280
|
+
case "choosing":
|
|
281
|
+
return _jsxs(_Fragment, {
|
|
282
|
+
children: [view.notice && _jsx(NoticeBlock, {
|
|
283
|
+
notice: view.notice,
|
|
284
|
+
ui: ui,
|
|
285
|
+
variant: "inline"
|
|
286
|
+
}), view.collections.length > 0 && _jsx(CollectionList, {
|
|
287
|
+
ui: ui,
|
|
288
|
+
onDialog: onDialog
|
|
289
|
+
}), _jsx(AppSections, {
|
|
290
|
+
ui: ui,
|
|
291
|
+
onDialog: onDialog
|
|
292
|
+
}), _jsx("div", {
|
|
293
|
+
className: "mdbase-collections-actions",
|
|
294
|
+
children: _jsx(ActionButton, {
|
|
295
|
+
tone: view.collections.length > 0 ? "secondary" : "primary",
|
|
296
|
+
inert: inert,
|
|
297
|
+
progress: busy === "add" ? copy.addingCollection : null,
|
|
298
|
+
onClick: actions.addCollection,
|
|
299
|
+
children: copy.addCollection
|
|
300
|
+
})
|
|
301
|
+
})]
|
|
302
|
+
});
|
|
303
|
+
case "needs_approval":
|
|
304
|
+
return _jsx("div", {
|
|
305
|
+
className: "mdbase-collections-actions",
|
|
306
|
+
children: _jsx(ActionButton, {
|
|
307
|
+
tone: "primary",
|
|
308
|
+
inert: inert,
|
|
309
|
+
progress: busy === "approve" ? copy.addingCollection : null,
|
|
310
|
+
onClick: () => actions.approve(view.collectionId),
|
|
311
|
+
children: copy.reviewAccess
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
case "needs_setup":
|
|
315
|
+
return _jsx(SetupReview, {
|
|
316
|
+
ui: ui
|
|
317
|
+
}, view.collectionId);
|
|
318
|
+
case "problem":
|
|
319
|
+
return _jsx(NoticeBlock, {
|
|
320
|
+
notice: view.notice,
|
|
321
|
+
ui: ui
|
|
322
|
+
});
|
|
323
|
+
case "unavailable":
|
|
324
|
+
case "closed":
|
|
325
|
+
return _jsxs(_Fragment, {
|
|
326
|
+
children: [view.kind === "unavailable" && _jsx(NoticeBlock, {
|
|
327
|
+
notice: view.notice,
|
|
328
|
+
ui: ui
|
|
329
|
+
}), _jsx("div", {
|
|
330
|
+
className: "mdbase-collections-actions",
|
|
331
|
+
children: _jsx(ActionButton, {
|
|
332
|
+
tone: "primary",
|
|
333
|
+
onClick: () => globalThis.location?.reload(),
|
|
334
|
+
children: copy.reload
|
|
335
|
+
})
|
|
336
|
+
})]
|
|
337
|
+
});
|
|
338
|
+
case "ready":
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** The approved collections as a labelled list: one row per collection, never mixed with actions. */
|
|
343
|
+
function CollectionList({
|
|
344
|
+
ui,
|
|
345
|
+
onDialog
|
|
346
|
+
}) {
|
|
347
|
+
const id = useId();
|
|
348
|
+
const {
|
|
349
|
+
view,
|
|
350
|
+
copy,
|
|
351
|
+
busy,
|
|
352
|
+
actions
|
|
353
|
+
} = ui;
|
|
354
|
+
return _jsxs("section", {
|
|
355
|
+
className: "mdbase-collections-section",
|
|
356
|
+
"aria-labelledby": id,
|
|
357
|
+
children: [_jsx("h2", {
|
|
358
|
+
id: id,
|
|
359
|
+
className: "mdbase-collections-section-title",
|
|
360
|
+
children: copy.yourCollections
|
|
361
|
+
}), _jsx("ul", {
|
|
362
|
+
className: "mdbase-collections-list",
|
|
363
|
+
children: view.collections.map(row => {
|
|
364
|
+
const detailId = row.statusDetail ? `${id}-${row.id}` : undefined;
|
|
365
|
+
return _jsxs("li", {
|
|
366
|
+
className: "mdbase-collections-item",
|
|
367
|
+
children: [_jsxs("button", {
|
|
368
|
+
type: "button",
|
|
369
|
+
className: "mdbase-collections-row",
|
|
370
|
+
"aria-disabled": !row.available || busy !== null || undefined,
|
|
371
|
+
"aria-describedby": detailId,
|
|
372
|
+
onClick: () => {
|
|
373
|
+
if (row.available && busy === null) actions.select(row.id);
|
|
374
|
+
},
|
|
375
|
+
children: [_jsx(CollectionMark, {
|
|
376
|
+
row: row
|
|
377
|
+
}), _jsx(RowCopy, {
|
|
378
|
+
row: row,
|
|
379
|
+
ui: ui,
|
|
380
|
+
...(detailId ? {
|
|
381
|
+
detailId
|
|
382
|
+
} : {})
|
|
383
|
+
})]
|
|
384
|
+
}), _jsx(RowMore, {
|
|
385
|
+
row: row,
|
|
386
|
+
ui: ui,
|
|
387
|
+
onDialog: onDialog
|
|
388
|
+
})]
|
|
389
|
+
}, row.id);
|
|
390
|
+
})
|
|
391
|
+
})]
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/** The app's own sections ("Private collections"), after the SDK's list. */
|
|
395
|
+
function AppSections({
|
|
396
|
+
ui,
|
|
397
|
+
onDialog
|
|
398
|
+
}) {
|
|
399
|
+
const sections = (ui.options.sections ?? []).filter(section => section.items.length > 0 || section.empty);
|
|
400
|
+
if (sections.length === 0) return null;
|
|
401
|
+
return _jsx(_Fragment, {
|
|
402
|
+
children: sections.map(section => _jsx(AppSection, {
|
|
403
|
+
ui: ui,
|
|
404
|
+
section: section,
|
|
405
|
+
onDialog: onDialog
|
|
406
|
+
}, section.id))
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function AppSection({
|
|
410
|
+
ui,
|
|
411
|
+
section,
|
|
412
|
+
onDialog
|
|
413
|
+
}) {
|
|
414
|
+
const id = useId();
|
|
415
|
+
const {
|
|
416
|
+
busy,
|
|
417
|
+
actions
|
|
418
|
+
} = ui;
|
|
419
|
+
return _jsxs("section", {
|
|
420
|
+
className: "mdbase-collections-section",
|
|
421
|
+
"aria-labelledby": id,
|
|
422
|
+
"data-section": section.id,
|
|
423
|
+
children: [_jsx("h2", {
|
|
424
|
+
id: id,
|
|
425
|
+
className: "mdbase-collections-section-title",
|
|
426
|
+
children: section.title
|
|
427
|
+
}), section.items.length === 0 ? _jsx("p", {
|
|
428
|
+
className: "mdbase-collections-empty",
|
|
429
|
+
children: section.empty
|
|
430
|
+
}) : _jsx("ul", {
|
|
431
|
+
className: "mdbase-collections-list",
|
|
432
|
+
children: section.items.map(item => _jsxs("li", {
|
|
433
|
+
className: "mdbase-collections-item",
|
|
434
|
+
children: [_jsxs("button", {
|
|
435
|
+
type: "button",
|
|
436
|
+
className: "mdbase-collections-row",
|
|
437
|
+
"aria-disabled": item.disabled || busy !== null || undefined,
|
|
438
|
+
"aria-current": item.current || undefined,
|
|
439
|
+
onClick: () => {
|
|
440
|
+
if (busy === null) actions.selectItem(section, item);
|
|
441
|
+
},
|
|
442
|
+
children: [_jsx(CollectionMark, {
|
|
443
|
+
row: item
|
|
444
|
+
}), _jsx(ItemCopy, {
|
|
445
|
+
item: item,
|
|
446
|
+
ui: ui
|
|
447
|
+
})]
|
|
448
|
+
}), _jsx(ItemMore, {
|
|
449
|
+
item: item,
|
|
450
|
+
ui: ui,
|
|
451
|
+
onDialog: onDialog
|
|
452
|
+
})]
|
|
453
|
+
}, item.id))
|
|
454
|
+
})]
|
|
455
|
+
});
|
|
456
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Mdbase } from "@mdbase-dev/sdk";
|
|
2
|
+
import { type JSX } from "react";
|
|
3
|
+
/** The gate supplies the entry to status controls inside the open workspace. */
|
|
4
|
+
export declare const CollectionMdbaseContext: import("react").Context<Mdbase | null>;
|
|
5
|
+
/** Quiet storage-risk copy beside an app's save status. Never shown without
|
|
6
|
+
* the SDK's warning: persistence/installation policy stays SDK-owned. */
|
|
7
|
+
export declare function useCollectionSaveWarning(supplied?: Mdbase): import("@mdbase-dev/sdk").MdbaseErrorDescription | null;
|
|
8
|
+
export declare function CollectionSaveWarning({ mdbase }: {
|
|
9
|
+
readonly mdbase?: Mdbase;
|
|
10
|
+
}): JSX.Element | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describeError } from "@mdbase-dev/sdk/describe";
|
|
3
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
4
|
+
/** The gate supplies the entry to status controls inside the open workspace. */
|
|
5
|
+
export const CollectionMdbaseContext = createContext(null);
|
|
6
|
+
const emptySubscribe = () => () => {};
|
|
7
|
+
const emptySnapshot = () => null;
|
|
8
|
+
/** Quiet storage-risk copy beside an app's save status. Never shown without
|
|
9
|
+
* the SDK's warning: persistence/installation policy stays SDK-owned. */
|
|
10
|
+
export function useCollectionSaveWarning(supplied) {
|
|
11
|
+
const inherited = useContext(CollectionMdbaseContext);
|
|
12
|
+
const mdbase = supplied ?? inherited;
|
|
13
|
+
const snapshot = useSyncExternalStore(mdbase?.subscribe ?? emptySubscribe, mdbase?.getSnapshot ?? emptySnapshot, emptySnapshot);
|
|
14
|
+
const warning = snapshot?.saveWarning;
|
|
15
|
+
return warning ? describeError(warning) : null;
|
|
16
|
+
}
|
|
17
|
+
export function CollectionSaveWarning({
|
|
18
|
+
mdbase
|
|
19
|
+
}) {
|
|
20
|
+
const copy = useCollectionSaveWarning(mdbase);
|
|
21
|
+
if (!copy) return null;
|
|
22
|
+
return _jsx("span", {
|
|
23
|
+
className: "mdbase-collections-save-warning",
|
|
24
|
+
role: "status",
|
|
25
|
+
title: copy.message,
|
|
26
|
+
children: copy.message
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type JSX, type ReactNode, type RefObject } from "react";
|
|
2
|
+
import { type RowDialog } from "./parts.js";
|
|
3
|
+
import { type CollectionUi, type CollectionUiOptions } from "./use-collection-ui.js";
|
|
4
|
+
export type CollectionSwitcherProps = CollectionUiOptions & {
|
|
5
|
+
readonly className?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Leading content in the trigger: the app's icon, or "mark" for the open
|
|
8
|
+
* collection's initials. In the "icon" variant it replaces the folder glyph.
|
|
9
|
+
*/
|
|
10
|
+
readonly icon?: ReactNode | "mark";
|
|
11
|
+
/**
|
|
12
|
+
* Whether the switcher is open. Leave it out and the switcher keeps its own
|
|
13
|
+
* state; pass it and the switcher only asks through `onOpenChange`.
|
|
14
|
+
*/
|
|
15
|
+
readonly open?: boolean;
|
|
16
|
+
/** Called whenever the switcher opens or closes itself, controlled or not. */
|
|
17
|
+
readonly onOpenChange?: (open: boolean) => void;
|
|
18
|
+
} & ({
|
|
19
|
+
/** "full": name and role; "compact": name only; "icon": a glyph for toolbars; "none": no trigger. */
|
|
20
|
+
readonly variant?: "full" | "compact" | "icon";
|
|
21
|
+
readonly anchorRef?: never;
|
|
22
|
+
} | {
|
|
23
|
+
/**
|
|
24
|
+
* No trigger of its own: the app opens the switcher with `open` and
|
|
25
|
+
* `onOpenChange`, for instance from an item in its own menu.
|
|
26
|
+
*/
|
|
27
|
+
readonly variant: "none";
|
|
28
|
+
/**
|
|
29
|
+
* The app's element the switcher belongs to, such as the button of the menu
|
|
30
|
+
* that offered it. The wide-screen popover is placed against it and focus
|
|
31
|
+
* returns to it on close. It must stay mounted while the switcher is open.
|
|
32
|
+
*/
|
|
33
|
+
readonly anchorRef: RefObject<HTMLElement | null>;
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* The in-app collection switcher: a trigger that opens the approved collections
|
|
37
|
+
* (one click switches, no Connect window), "Add a collection…", "Manage access"
|
|
38
|
+
* and the account. A popover on wide screens, a bottom sheet on narrow ones.
|
|
39
|
+
*
|
|
40
|
+
* Pass `open` and `onOpenChange` to control it. With `variant="none"` it has no
|
|
41
|
+
* trigger, so an app can open it from its own control and pass that control's
|
|
42
|
+
* element (or the menu button that held it) as `anchorRef`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function CollectionSwitcher({ variant, className, icon, open: openProp, onOpenChange, anchorRef, ...options }: CollectionSwitcherProps): JSX.Element;
|
|
45
|
+
/** The switcher's list and actions, also shown inline by `CollectionPanel`. */
|
|
46
|
+
export declare function SwitcherContent({ ui, onDone, onDialog }: {
|
|
47
|
+
readonly ui: CollectionUi;
|
|
48
|
+
readonly onDone: () => void;
|
|
49
|
+
/** Open a row's dialog (remove from list, delete), for an SDK collection or an app section item. */
|
|
50
|
+
readonly onDialog: (dialog: NonNullable<RowDialog>) => void;
|
|
51
|
+
}): JSX.Element;
|