@maykonpaulo/maestro-admin 0.3.0 → 0.4.0-next.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 +40 -12
- package/dist/index.d.ts +281 -3
- package/dist/index.js +1285 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/styles.css +1716 -1
package/README.md
CHANGED
|
@@ -409,7 +409,7 @@ data, textarea/JSON…).
|
|
|
409
409
|
|
|
410
410
|
## 11. A UI de admin (`maestro-admin`)
|
|
411
411
|
|
|
412
|
-
A UI (React + Vite
|
|
412
|
+
A UI (React + Vite, CSS puro com temas) lê `GET /metadata` e monta sozinha: navegação por entidade, tabela
|
|
413
413
|
(com busca/ordenação/paginação/filtros vindos do metadata), detalhe, e formulários de criar/editar.
|
|
414
414
|
Ela respeita as **capacidades** (esconde New/Edit/Delete quando desabilitados) e o **RBAC** (403 vira
|
|
415
415
|
erro na tela). **Uma tela, todas as entidades.**
|
|
@@ -430,7 +430,7 @@ VITE_MAESTRO_API_URL=http://localhost:3000 \
|
|
|
430
430
|
|
|
431
431
|
```tsx
|
|
432
432
|
import { MaestroAdmin } from '@maykonpaulo/maestro-admin';
|
|
433
|
-
import '@maykonpaulo/maestro-admin/
|
|
433
|
+
import '@maykonpaulo/maestro-admin/styles.css';
|
|
434
434
|
|
|
435
435
|
export function AdminPage() {
|
|
436
436
|
return (
|
|
@@ -569,7 +569,7 @@ The **generic, metadata-driven admin UI** for Maestro, per [ADR 0008 — Camada
|
|
|
569
569
|
|
|
570
570
|
Point it at a running [`@maykonpaulo/maestro-server`](../server) and it reads `GET /metadata` and **auto-builds the whole admin**: a navigation sidebar, a list table, a detail view and create/update forms **for every entity/collection** — no per-entity code. It respects each entity's `capabilities` (a read-only collection shows no New/Edit/Delete) and the server's RBAC (a 403 surfaces as an error, never a broken screen).
|
|
571
571
|
|
|
572
|
-
Built with **React 19 + Vite
|
|
572
|
+
Built with **React 19 + Vite**, styled with plain CSS custom properties (three built-in themes, light and dark). It ships two ways:
|
|
573
573
|
|
|
574
574
|
- a **runnable static app** (`vite build`) you configure with an env var, and
|
|
575
575
|
- **exported React components** (`import { MaestroAdmin } from '@maykonpaulo/maestro-admin'`) to embed in your own app.
|
|
@@ -591,7 +591,7 @@ VITE_MAESTRO_API_URL=http://localhost:3000 pnpm --filter @maykonpaulo/maestro-ad
|
|
|
591
591
|
|
|
592
592
|
```tsx
|
|
593
593
|
import { MaestroAdmin } from '@maykonpaulo/maestro-admin';
|
|
594
|
-
import '@maykonpaulo/maestro-admin/
|
|
594
|
+
import '@maykonpaulo/maestro-admin/styles.css';
|
|
595
595
|
|
|
596
596
|
export function AdminPage() {
|
|
597
597
|
return (
|
|
@@ -606,24 +606,52 @@ export function AdminPage() {
|
|
|
606
606
|
|
|
607
607
|
`headers` may be a function so a fresh auth token is read on every request. Pass a pre-built `client` (a `MaestroClient`) instead of `apiUrl`/`headers` for full control (custom `fetch`, SSR).
|
|
608
608
|
|
|
609
|
-
|
|
609
|
+
The admin fills the box you give it — set a height on the container (`height: 100vh`, a grid cell, whatever). It never styles `html`/`body`.
|
|
610
610
|
|
|
611
|
-
|
|
611
|
+
### Styling and themes
|
|
612
612
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
613
|
+
One plain-CSS import, no build step and no Tailwind: `@maykonpaulo/maestro-admin/styles.css` (`@maykonpaulo/maestro-admin/src/styles.css` still resolves to the same file). Every colour is a CSS custom property, so nothing depends on your bundler discovering class names.
|
|
614
|
+
|
|
615
|
+
Three themes ship with the package — `cobalto` (default), `latao` and `teal` — each in light and dark:
|
|
616
|
+
|
|
617
|
+
```tsx
|
|
618
|
+
<MaestroAdmin apiUrl="…" theme="latao" />
|
|
617
619
|
```
|
|
618
620
|
|
|
621
|
+
The header carries a palette picker, a light/dark toggle (system → light → dark) and a density toggle, and remembers what the operator chose. Use the object form for more control:
|
|
622
|
+
|
|
623
|
+
```tsx
|
|
624
|
+
<MaestroAdmin
|
|
625
|
+
apiUrl="…"
|
|
626
|
+
theme={{
|
|
627
|
+
preset: 'teal',
|
|
628
|
+
scheme: 'dark', // 'system' (default) | 'light' | 'dark'
|
|
629
|
+
density: 'compact', // 'comfortable' (default) | 'compact'
|
|
630
|
+
picker: false, // hide the header controls
|
|
631
|
+
remember: false, // don't persist the operator's choices
|
|
632
|
+
tokens: { '--maestro-brand': '#167B61', '--maestro-shell': '#0B2119' },
|
|
633
|
+
}}
|
|
634
|
+
/>
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
`tokens` is applied inline on the admin root, so it wins over the preset in both light and dark. The full token list is at the top of `src/styles.css`; the ones you will reach for most are `--maestro-brand`, `--maestro-shell`, `--maestro-bg`, `--maestro-surface`, `--maestro-border`, `--maestro-fg` and `--maestro-radius`.
|
|
638
|
+
|
|
639
|
+
### The admin never scrolls sideways
|
|
640
|
+
|
|
641
|
+
Every screen scrolls vertically only. The hub uses fluid card tracks, the sidebar collapses to icons under 900px, and list tables have fixed column widths with truncation — so an entity with thirty fields does not become thirty columns. Which columns are on screen is the operator's call, from **Columns** in the list toolbar, remembered per entity; the default selection comes from `primaryListFields()` and **Reset columns** hands the decision back to it.
|
|
642
|
+
|
|
619
643
|
## Compose your own layout
|
|
620
644
|
|
|
621
645
|
Beyond the all-in-one `<MaestroAdmin>`, the building blocks are exported so you can assemble a custom shell:
|
|
622
646
|
|
|
623
|
-
-
|
|
624
|
-
-
|
|
647
|
+
- Shell: `Sidebar`, `Header`, `Hub`, `CommandPalette` (+ `entityCommands`), `MaestroThemeProvider`, `useTheme`
|
|
648
|
+
- Screens: `EntityList`, `EntityDetail`, `EntityForm`, `FieldValue`, `FieldInput`
|
|
649
|
+
- Primitives: `Button`, `IconButton`, `Badge`, `Spinner`, `EmptyState`, `ErrorBanner`, `Toast`, `Icon`, `entityIcon`
|
|
650
|
+
- Hooks: `useMetadata`, `useEntityList`, `useEntityRecord`, `useAsync`, `useColumnPrefs`, `useRecents`, and `listFields`/`detailFields`/`formFields`
|
|
625
651
|
- Client: `MaestroClient`, `MaestroApiError`, `AdminClientProvider`, `useClient`
|
|
626
652
|
|
|
653
|
+
Composing your own shell means owning the token root too: wrap it in `<MaestroThemeProvider className="mst-root">`, which is what paints `data-maestro-theme`/`-scheme`/`-density`.
|
|
654
|
+
|
|
627
655
|
All of them talk to the server through a single `MaestroClient` provided via `AdminClientProvider`.
|
|
628
656
|
|
|
629
657
|
## What it renders from metadata
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement, ButtonHTMLAttributes } from 'react';
|
|
2
3
|
|
|
3
4
|
type FieldType = 'string' | 'text' | 'integer' | 'float' | 'boolean' | 'date' | 'datetime' | 'enum' | 'json' | 'relation' | 'uuid' | 'email' | (string & {});
|
|
4
5
|
interface EnumOption {
|
|
@@ -186,6 +187,34 @@ interface AdminStrings {
|
|
|
186
187
|
recents: string;
|
|
187
188
|
menus: string;
|
|
188
189
|
noMenuResults: string;
|
|
190
|
+
hub: string;
|
|
191
|
+
breadcrumb: string;
|
|
192
|
+
search: string;
|
|
193
|
+
commandKey: string;
|
|
194
|
+
commandPalette: string;
|
|
195
|
+
palettePlaceholder: string;
|
|
196
|
+
goTo: string;
|
|
197
|
+
onThisScreen: string;
|
|
198
|
+
appearance: string;
|
|
199
|
+
palette: string;
|
|
200
|
+
density: string;
|
|
201
|
+
colorScheme: string;
|
|
202
|
+
schemeSystem: string;
|
|
203
|
+
schemeLight: string;
|
|
204
|
+
schemeDark: string;
|
|
205
|
+
entities: string;
|
|
206
|
+
hubMeta: (entities: number, sources: number) => string;
|
|
207
|
+
otherEntities: string;
|
|
208
|
+
noEntities: string;
|
|
209
|
+
noEntitiesHint: string;
|
|
210
|
+
fieldCount: (count: number) => string;
|
|
211
|
+
capRead: string;
|
|
212
|
+
capCreate: string;
|
|
213
|
+
capUpdate: string;
|
|
214
|
+
capDelete: string;
|
|
215
|
+
columns: string;
|
|
216
|
+
columnsCount: (visible: number, total: number) => string;
|
|
217
|
+
resetColumns: string;
|
|
189
218
|
}
|
|
190
219
|
declare function stringsFor(locale: AdminLocale): AdminStrings;
|
|
191
220
|
declare const I18nProvider: react.Provider<AdminStrings>;
|
|
@@ -217,6 +246,64 @@ interface AdminLabels {
|
|
|
217
246
|
/** Returns a copy of `metadata` with every label override applied. No-op when `labels` is empty. */
|
|
218
247
|
declare function applyLabels(metadata: MaestroMetadata, labels: AdminLabels | undefined): MaestroMetadata;
|
|
219
248
|
|
|
249
|
+
/** Built-in palettes. Each ships a light and a dark set of the same tokens. */
|
|
250
|
+
type MaestroThemeName = 'cobalto' | 'latao' | 'teal';
|
|
251
|
+
/** `system` follows the OS `prefers-color-scheme`; the other two force the decision. */
|
|
252
|
+
type MaestroScheme = 'system' | 'light' | 'dark';
|
|
253
|
+
/** Table row height. `compact` fits roughly 40% more rows on a screen and changes nothing else. */
|
|
254
|
+
type MaestroDensity = 'comfortable' | 'compact';
|
|
255
|
+
interface MaestroThemeOption {
|
|
256
|
+
id: MaestroThemeName;
|
|
257
|
+
label: string;
|
|
258
|
+
/** Light-mode brand colour — what the picker shows as the palette's dot. */
|
|
259
|
+
swatch: string;
|
|
260
|
+
}
|
|
261
|
+
declare const MAESTRO_THEMES: readonly MaestroThemeOption[];
|
|
262
|
+
interface MaestroThemeConfig {
|
|
263
|
+
/** Palette to start on. Defaults to `cobalto`. */
|
|
264
|
+
preset?: MaestroThemeName;
|
|
265
|
+
/** Light/dark decision to start on. Defaults to `system`. */
|
|
266
|
+
scheme?: MaestroScheme;
|
|
267
|
+
/** Row density to start on. Defaults to `comfortable`. */
|
|
268
|
+
density?: MaestroDensity;
|
|
269
|
+
/**
|
|
270
|
+
* Token overrides applied inline on the admin root, so they win over the preset in both schemes:
|
|
271
|
+
* `{ '--maestro-brand': '#167B61' }`. Use this to dress the admin in your own brand.
|
|
272
|
+
*/
|
|
273
|
+
tokens?: Record<string, string>;
|
|
274
|
+
/** Show the palette / light-dark / density controls in the header. Defaults to `true`. */
|
|
275
|
+
picker?: boolean;
|
|
276
|
+
/** Remember the operator's choices in `localStorage`. Defaults to `true`. */
|
|
277
|
+
remember?: boolean;
|
|
278
|
+
}
|
|
279
|
+
/** `theme="cobalto"` is shorthand for `theme={{ preset: 'cobalto' }}`. */
|
|
280
|
+
type MaestroThemeInput = MaestroThemeName | MaestroThemeConfig;
|
|
281
|
+
interface MaestroThemeState {
|
|
282
|
+
preset: MaestroThemeName;
|
|
283
|
+
scheme: MaestroScheme;
|
|
284
|
+
density: MaestroDensity;
|
|
285
|
+
picker: boolean;
|
|
286
|
+
setPreset: (preset: MaestroThemeName) => void;
|
|
287
|
+
setScheme: (scheme: MaestroScheme) => void;
|
|
288
|
+
setDensity: (density: MaestroDensity) => void;
|
|
289
|
+
}
|
|
290
|
+
/** The active appearance and its setters. Safe outside a provider — falls back to the defaults. */
|
|
291
|
+
declare function useTheme(): MaestroThemeState;
|
|
292
|
+
/**
|
|
293
|
+
* Owns the appearance state and paints the root element that carries the tokens. Everything below it
|
|
294
|
+
* reads colours from CSS custom properties, so a theme switch is one attribute change — no re-render
|
|
295
|
+
* of the tree's styles, no class swapping.
|
|
296
|
+
*
|
|
297
|
+
* The props are the *initial* values: once the operator picks a palette from the header it is
|
|
298
|
+
* remembered (unless `remember: false`), and the stored choice wins on the next mount.
|
|
299
|
+
*/
|
|
300
|
+
declare function MaestroThemeProvider({ theme, className, children, rootRef, }: {
|
|
301
|
+
theme?: MaestroThemeInput;
|
|
302
|
+
className?: string;
|
|
303
|
+
children: ReactNode;
|
|
304
|
+
rootRef?: (node: HTMLDivElement | null) => void;
|
|
305
|
+
}): react.JSX.Element;
|
|
306
|
+
|
|
220
307
|
interface MaestroAdminProps {
|
|
221
308
|
/** Base URL of the running maestro-server. Ignored when `client` is given. */
|
|
222
309
|
apiUrl?: string;
|
|
@@ -230,17 +317,29 @@ interface MaestroAdminProps {
|
|
|
230
317
|
locale?: AdminLocale;
|
|
231
318
|
/** Label rewrites (localization/humanization) applied over the served metadata. */
|
|
232
319
|
labels?: AdminLabels;
|
|
320
|
+
/**
|
|
321
|
+
* Appearance: `"cobalto" | "latao" | "teal"`, or an object to also set the light/dark scheme, the
|
|
322
|
+
* row density, whether the header shows the pickers, and per-token overrides for your own brand.
|
|
323
|
+
*/
|
|
324
|
+
theme?: MaestroThemeInput;
|
|
233
325
|
}
|
|
234
326
|
/**
|
|
235
327
|
* The whole metadata-driven admin in one component. Point it at a running maestro-server and it reads
|
|
236
328
|
* `GET /metadata` and auto-builds navigation, tables, detail views and create/update forms for every
|
|
237
329
|
* entity — respecting each entity's capabilities and the server's RBAC. No per-entity code.
|
|
330
|
+
*
|
|
331
|
+
* Styles ship separately so the host app controls bundling:
|
|
332
|
+
* `import '@maykonpaulo/maestro-admin/styles.css'`.
|
|
238
333
|
*/
|
|
239
334
|
declare function MaestroAdmin(props: MaestroAdminProps): react.JSX.Element;
|
|
240
335
|
|
|
241
336
|
declare const AdminClientProvider: react.Provider<MaestroClient | null>;
|
|
242
337
|
/** Returns the `MaestroClient` provided at the root of the admin. Throws if used outside the provider. */
|
|
243
338
|
declare function useClient(): MaestroClient;
|
|
339
|
+
/** All entities from the loaded metadata — lets deep components (relation chips) resolve a target entity. */
|
|
340
|
+
declare function useEntities(): EntityMetadata[];
|
|
341
|
+
declare function useEntity(entityId: string | undefined): EntityMetadata | undefined;
|
|
342
|
+
declare function useAdminRoot(): HTMLElement | null;
|
|
244
343
|
|
|
245
344
|
interface AsyncState<T> {
|
|
246
345
|
data: T | undefined;
|
|
@@ -302,6 +401,27 @@ declare function useRecents(scope?: string): {
|
|
|
302
401
|
push: (entityId: string) => void;
|
|
303
402
|
};
|
|
304
403
|
|
|
404
|
+
interface ColumnPrefs {
|
|
405
|
+
/** The fields to render, in metadata order. */
|
|
406
|
+
columns: FieldMetadata[];
|
|
407
|
+
/** Every field that *could* be a column, for the picker. */
|
|
408
|
+
available: FieldMetadata[];
|
|
409
|
+
visible: (name: string) => boolean;
|
|
410
|
+
toggle: (name: string) => void;
|
|
411
|
+
reset: () => void;
|
|
412
|
+
/** True when the operator has overridden the metadata's default selection. */
|
|
413
|
+
customized: boolean;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Which columns an entity's table shows, remembered per entity.
|
|
417
|
+
*
|
|
418
|
+
* The admin never scrolls sideways, so an entity with thirty fields cannot simply render thirty
|
|
419
|
+
* columns — something has to choose. The default choice is `primaryListFields`, which reads the
|
|
420
|
+
* metadata; this hook lets the operator override it for the entities they actually work in, and
|
|
421
|
+
* `reset` hands the decision back to the metadata.
|
|
422
|
+
*/
|
|
423
|
+
declare function useColumnPrefs(entity: EntityMetadata, max?: number): ColumnPrefs;
|
|
424
|
+
|
|
305
425
|
declare function EntityList({ entity, onOpen, onCreate, onEdit, onClone, onNavigate, initialFilters, }: {
|
|
306
426
|
entity: EntityMetadata;
|
|
307
427
|
onOpen: (id: string) => void;
|
|
@@ -332,7 +452,7 @@ declare function EntityForm({ entity, mode, id, cloneFromId, onCancel, onSaved,
|
|
|
332
452
|
onSaved: (record: Record_) => void;
|
|
333
453
|
}): react.JSX.Element;
|
|
334
454
|
|
|
335
|
-
declare function Sidebar({ entities, activeId, onSelect, title, labels, recents, }: {
|
|
455
|
+
declare function Sidebar({ entities, activeId, onSelect, title, labels, recents, onHome, homeActive, }: {
|
|
336
456
|
entities: EntityMetadata[];
|
|
337
457
|
activeId: string | undefined;
|
|
338
458
|
onSelect: (entityId: string) => void;
|
|
@@ -341,11 +461,169 @@ declare function Sidebar({ entities, activeId, onSelect, title, labels, recents,
|
|
|
341
461
|
labels?: AdminLabels;
|
|
342
462
|
/** Recently-visited entity ids, most recent first. */
|
|
343
463
|
recents?: string[];
|
|
464
|
+
/** Opens the hub. Omit to hide the hub entry (standalone/embedded use). */
|
|
465
|
+
onHome?: () => void;
|
|
466
|
+
homeActive?: boolean;
|
|
467
|
+
}): react.JSX.Element;
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* The admin's icon set, inline.
|
|
471
|
+
*
|
|
472
|
+
* Twenty-odd 24×24 stroke glyphs are not worth a runtime dependency: `@maykonpaulo/maestro-admin`
|
|
473
|
+
* ships with `dependencies: {}` and this file is why. Every glyph is drawn on the same grid with the
|
|
474
|
+
* same stroke weight, and takes its colour from `currentColor`.
|
|
475
|
+
*/
|
|
476
|
+
declare const PATHS: {
|
|
477
|
+
readonly hub: react.JSX.Element;
|
|
478
|
+
readonly baton: react.JSX.Element;
|
|
479
|
+
readonly search: react.JSX.Element;
|
|
480
|
+
readonly chevronRight: react.JSX.Element;
|
|
481
|
+
readonly chevronLeft: react.JSX.Element;
|
|
482
|
+
readonly chevronsLeft: react.JSX.Element;
|
|
483
|
+
readonly chevronsRight: react.JSX.Element;
|
|
484
|
+
readonly plus: react.JSX.Element;
|
|
485
|
+
readonly close: react.JSX.Element;
|
|
486
|
+
readonly check: react.JSX.Element;
|
|
487
|
+
readonly filter: react.JSX.Element;
|
|
488
|
+
readonly download: react.JSX.Element;
|
|
489
|
+
readonly columns: react.JSX.Element;
|
|
490
|
+
readonly rows: react.JSX.Element;
|
|
491
|
+
readonly sun: react.JSX.Element;
|
|
492
|
+
readonly moon: react.JSX.Element;
|
|
493
|
+
readonly swatch: react.JSX.Element;
|
|
494
|
+
readonly dots: react.JSX.Element;
|
|
495
|
+
readonly trash: react.JSX.Element;
|
|
496
|
+
readonly pencil: react.JSX.Element;
|
|
497
|
+
readonly eye: react.JSX.Element;
|
|
498
|
+
readonly copy: react.JSX.Element;
|
|
499
|
+
readonly clone: react.JSX.Element;
|
|
500
|
+
readonly arrowUpRight: react.JSX.Element;
|
|
501
|
+
readonly arrowLeft: react.JSX.Element;
|
|
502
|
+
readonly sort: react.JSX.Element;
|
|
503
|
+
readonly sortAsc: react.JSX.Element;
|
|
504
|
+
readonly sortDesc: react.JSX.Element;
|
|
505
|
+
readonly database: react.JSX.Element;
|
|
506
|
+
readonly table: react.JSX.Element;
|
|
507
|
+
readonly users: react.JSX.Element;
|
|
508
|
+
readonly box: react.JSX.Element;
|
|
509
|
+
readonly receipt: react.JSX.Element;
|
|
510
|
+
readonly key: react.JSX.Element;
|
|
511
|
+
readonly shield: react.JSX.Element;
|
|
512
|
+
readonly activity: react.JSX.Element;
|
|
513
|
+
readonly tag: react.JSX.Element;
|
|
514
|
+
readonly map: react.JSX.Element;
|
|
515
|
+
readonly calendar: react.JSX.Element;
|
|
516
|
+
readonly mail: react.JSX.Element;
|
|
517
|
+
readonly file: react.JSX.Element;
|
|
518
|
+
readonly alert: react.JSX.Element;
|
|
519
|
+
};
|
|
520
|
+
type IconName = keyof typeof PATHS;
|
|
521
|
+
declare function Icon({ name, size, className, title, }: {
|
|
522
|
+
name: IconName;
|
|
523
|
+
size?: number;
|
|
524
|
+
className?: string;
|
|
525
|
+
title?: string;
|
|
526
|
+
}): ReactElement;
|
|
527
|
+
/**
|
|
528
|
+
* A glyph for an entity, guessed from its names. A metadata-driven admin has no idea what a
|
|
529
|
+
* collection *means*, so this is deliberately a hint and not a promise: anything unrecognised gets
|
|
530
|
+
* the neutral table glyph, which is always a defensible answer.
|
|
531
|
+
*/
|
|
532
|
+
declare function entityIcon(entity: EntityMetadata): IconName;
|
|
533
|
+
|
|
534
|
+
interface Crumb {
|
|
535
|
+
label: string;
|
|
536
|
+
icon?: IconName;
|
|
537
|
+
/** Omit on the last crumb — the current place is not a link. */
|
|
538
|
+
onClick?: () => void;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The bar above the content: where you are (breadcrumbs), how to get anywhere (⌘K), and how the admin
|
|
542
|
+
* looks (palette, light/dark, density). The appearance controls live here rather than in a settings
|
|
543
|
+
* page because they are the operator's, changed mid-task and never worth a navigation.
|
|
544
|
+
*/
|
|
545
|
+
declare function Header({ crumbs, onOpenPalette }: {
|
|
546
|
+
crumbs: Crumb[];
|
|
547
|
+
onOpenPalette: () => void;
|
|
548
|
+
}): react.JSX.Element;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The landing screen: every entity the server serves, in its sidebar folder, with what you can do to
|
|
552
|
+
* it and where it actually lives. That last part is the whole point — Maestro speaks a dozen
|
|
553
|
+
* providers, so "which database is this collection in" is a real question with a real answer, and no
|
|
554
|
+
* other admin can show it.
|
|
555
|
+
*
|
|
556
|
+
* Record counts are deliberately absent: metadata does not carry them, and one `count` request per
|
|
557
|
+
* entity would turn opening the admin into thirty round-trips. The count you get is the real one, in
|
|
558
|
+
* the list header, once you open an entity.
|
|
559
|
+
*/
|
|
560
|
+
declare function Hub({ entities, labels, onOpen, }: {
|
|
561
|
+
entities: EntityMetadata[];
|
|
562
|
+
labels?: AdminLabels;
|
|
563
|
+
onOpen: (entityId: string) => void;
|
|
564
|
+
}): react.JSX.Element;
|
|
565
|
+
|
|
566
|
+
interface PaletteCommand {
|
|
567
|
+
id: string;
|
|
568
|
+
label: string;
|
|
569
|
+
icon?: IconName;
|
|
570
|
+
/** Right-aligned affordance: a shortcut, or the folder an entity belongs to. */
|
|
571
|
+
hint?: string;
|
|
572
|
+
/** Extra words matched by the search but never displayed (table name, singular label). */
|
|
573
|
+
keywords?: string;
|
|
574
|
+
run: () => void;
|
|
575
|
+
}
|
|
576
|
+
interface PaletteSection {
|
|
577
|
+
id: string;
|
|
578
|
+
label: string;
|
|
579
|
+
commands: PaletteCommand[];
|
|
580
|
+
}
|
|
581
|
+
/** Turns the entity list into palette commands — the navigation half of the palette. */
|
|
582
|
+
declare function entityCommands(entities: EntityMetadata[], groupOf: Map<string, string>, onSelect: (entityId: string) => void): PaletteCommand[];
|
|
583
|
+
/**
|
|
584
|
+
* ⌘K. Everything reachable in the admin — every entity, plus whatever the current screen can do —
|
|
585
|
+
* behind one accent-insensitive search, driven entirely from the keyboard.
|
|
586
|
+
*/
|
|
587
|
+
declare function CommandPalette({ sections, onClose }: {
|
|
588
|
+
sections: PaletteSection[];
|
|
589
|
+
onClose: () => void;
|
|
590
|
+
}): react.ReactPortal;
|
|
591
|
+
|
|
592
|
+
type Variant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
|
593
|
+
declare function Button({ variant, icon, className, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
594
|
+
variant?: Variant;
|
|
595
|
+
icon?: IconName;
|
|
596
|
+
}): react.JSX.Element;
|
|
597
|
+
/** A square, label-less button. `label` is required — it is the accessible name and the tooltip. */
|
|
598
|
+
declare function IconButton({ icon, label, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
599
|
+
icon: IconName;
|
|
600
|
+
label: string;
|
|
601
|
+
}): react.JSX.Element;
|
|
602
|
+
type BadgeTone = 'neutral' | 'brand' | 'ok' | 'warn' | 'danger';
|
|
603
|
+
declare function Badge({ children, tone, dot }: {
|
|
604
|
+
children: ReactNode;
|
|
605
|
+
tone?: BadgeTone;
|
|
606
|
+
dot?: boolean;
|
|
607
|
+
}): react.JSX.Element;
|
|
608
|
+
declare function Spinner({ label }: {
|
|
609
|
+
label?: string;
|
|
610
|
+
}): react.JSX.Element;
|
|
611
|
+
declare function EmptyState({ title, hint }: {
|
|
612
|
+
title: string;
|
|
613
|
+
hint?: string;
|
|
614
|
+
}): react.JSX.Element;
|
|
615
|
+
declare function ErrorBanner({ error }: {
|
|
616
|
+
error: Error;
|
|
617
|
+
}): react.JSX.Element;
|
|
618
|
+
/** The transient confirmation strip ("Copied!"). Callers own the timer; this only draws it. */
|
|
619
|
+
declare function Toast({ children }: {
|
|
620
|
+
children: ReactNode;
|
|
344
621
|
}): react.JSX.Element;
|
|
345
622
|
|
|
346
623
|
/**
|
|
347
624
|
* Renders a single record value read-only, shaped by the field's type (for tables and detail views).
|
|
348
|
-
* `compact` is the table-cell mode:
|
|
625
|
+
* `compact` is the table-cell mode: the cell itself truncates, so this only has to avoid rendering
|
|
626
|
+
* anything that would force the row taller.
|
|
349
627
|
*/
|
|
350
628
|
declare function FieldValue({ field, value, compact }: {
|
|
351
629
|
field: FieldMetadata;
|
|
@@ -386,4 +664,4 @@ declare function JsonInput({ field, value, onChange, disabled, }: {
|
|
|
386
664
|
disabled?: boolean;
|
|
387
665
|
}): react.JSX.Element;
|
|
388
666
|
|
|
389
|
-
export { AdminClientProvider, type AdminLabels, type AdminLocale, type AdminStrings, type AsyncState, type EntityCapabilities, EntityDetail, EntityForm, type EntityLabelOverride, EntityList, type EntityMetadata, FieldInput, type FieldMetadata, FieldValue, type FilterInput, I18nProvider, JsonInput, type ListQuery, type ListResult, MaestroAdmin, type MaestroAdminProps, MaestroApiError, MaestroClient, type MaestroClientConfig, type MaestroMetadata, type MenuGroup, ObjectValue, Sidebar, type SidebarMenu, applyLabels, buildMenu, detailFields, formFields, listFields, primaryListFields, stringsFor, useAsync, useClient, useEntityList, useEntityRecord, useMetadata, useRecents, useT };
|
|
667
|
+
export { AdminClientProvider, type AdminLabels, type AdminLocale, type AdminStrings, type AsyncState, Badge, Button, type ColumnPrefs, CommandPalette, type Crumb, EmptyState, type EntityCapabilities, EntityDetail, EntityForm, type EntityLabelOverride, EntityList, type EntityMetadata, ErrorBanner, FieldInput, type FieldMetadata, FieldValue, type FilterInput, Header, Hub, I18nProvider, Icon, IconButton, type IconName, JsonInput, type ListQuery, type ListResult, MAESTRO_THEMES, MaestroAdmin, type MaestroAdminProps, MaestroApiError, MaestroClient, type MaestroClientConfig, type MaestroDensity, type MaestroMetadata, type MaestroScheme, type MaestroThemeConfig, type MaestroThemeInput, type MaestroThemeName, type MaestroThemeOption, MaestroThemeProvider, type MaestroThemeState, type MenuGroup, ObjectValue, type PaletteCommand, type PaletteSection, Sidebar, type SidebarMenu, Spinner, Toast, applyLabels, buildMenu, detailFields, entityCommands, entityIcon, formFields, listFields, primaryListFields, stringsFor, useAdminRoot, useAsync, useClient, useColumnPrefs, useEntities, useEntity, useEntityList, useEntityRecord, useMetadata, useRecents, useT, useTheme };
|