@tai42/studio-sdk 0.5.3 → 0.7.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/dist/components/components.css +65 -0
- package/dist/components/explorer-view.d.ts +104 -0
- package/dist/components/explorer-view.d.ts.map +1 -0
- package/dist/components/explorer-view.js +141 -0
- package/dist/components/explorer-view.js.map +1 -0
- package/dist/components/page-fill.d.ts +34 -0
- package/dist/components/page-fill.d.ts.map +1 -0
- package/dist/components/page-fill.js +58 -0
- package/dist/components/page-fill.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/components.css +65 -0
- package/src/components/explorer-view.tsx +419 -0
- package/src/components/page-fill.tsx +76 -0
- package/src/index.ts +16 -0
|
@@ -1764,6 +1764,19 @@
|
|
|
1764
1764
|
padding: var(--tai-space-6) var(--tai-space-8);
|
|
1765
1765
|
}
|
|
1766
1766
|
|
|
1767
|
+
/* Fill mode: a page that opted in via `useFillViewport`. The wrapper stops being
|
|
1768
|
+
content-sized and instead fills `.tai-shell-main--fill` (which carries the
|
|
1769
|
+
viewport height), so a canvas page's `height: 100%`/`flex: 1` resolves down
|
|
1770
|
+
the chain. `min-height: 0` lets the flex child shrink below its content and
|
|
1771
|
+
scroll internally. A canvas wants the whole pane, so the reading-column cap
|
|
1772
|
+
and page padding are dropped — no letterboxing. */
|
|
1773
|
+
.tai-page--fill {
|
|
1774
|
+
flex: 1;
|
|
1775
|
+
min-height: 0;
|
|
1776
|
+
max-width: none;
|
|
1777
|
+
padding: 0;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1767
1780
|
.tai-stack {
|
|
1768
1781
|
display: flex;
|
|
1769
1782
|
flex-direction: column;
|
|
@@ -1941,6 +1954,22 @@
|
|
|
1941
1954
|
background: var(--tai-color-bg);
|
|
1942
1955
|
}
|
|
1943
1956
|
|
|
1957
|
+
/* Fill mode: `<main>` becomes a flex column pinned to the viewport height so a
|
|
1958
|
+
fill page has a definite height to resolve against (a stretched grid item's
|
|
1959
|
+
height is not a reliable percentage base). The `100vh`/`100dvh` pair matches
|
|
1960
|
+
`.tai-shell-sidebar` — `dvh` is the live viewport, `vh` the pre-`dvh` fallback
|
|
1961
|
+
a browser below the floor keeps. `--shell-fill-offset` is the chrome above
|
|
1962
|
+
`<main>` in the grid column: 0 beside the full-height sidebar, the top-bar
|
|
1963
|
+
height on the phone band where the sticky bar takes the first row. */
|
|
1964
|
+
.tai-shell-main--fill {
|
|
1965
|
+
--shell-fill-offset: 0px;
|
|
1966
|
+
display: flex;
|
|
1967
|
+
flex-direction: column;
|
|
1968
|
+
min-height: 0;
|
|
1969
|
+
height: calc(100vh - var(--shell-fill-offset));
|
|
1970
|
+
height: calc(100dvh - var(--shell-fill-offset));
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1944
1973
|
.tai-brand {
|
|
1945
1974
|
display: flex;
|
|
1946
1975
|
align-items: center;
|
|
@@ -2002,6 +2031,36 @@
|
|
|
2002
2031
|
min-width: 0;
|
|
2003
2032
|
}
|
|
2004
2033
|
|
|
2034
|
+
/* -- Explorer ---------------------------------------------------------------------
|
|
2035
|
+
The shared current-directory explorer: controls row, breadcrumb, tag chips,
|
|
2036
|
+
then the folder/item body. Vertical rhythm only — the body carries its own
|
|
2037
|
+
table/grid. */
|
|
2038
|
+
.tai-explorer {
|
|
2039
|
+
display: flex;
|
|
2040
|
+
flex-direction: column;
|
|
2041
|
+
gap: var(--tai-space-4);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/* Search on the leading edge, the view toggle on the trailing edge; wraps under
|
|
2045
|
+
its own width on a narrow surface. */
|
|
2046
|
+
.tai-explorer-controls {
|
|
2047
|
+
display: flex;
|
|
2048
|
+
align-items: center;
|
|
2049
|
+
justify-content: space-between;
|
|
2050
|
+
gap: var(--tai-space-3);
|
|
2051
|
+
flex-wrap: wrap;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/* A first-class folder entry (row cell or card): the folder affordance beside
|
|
2055
|
+
its optional actions, the actions pushed to the trailing edge. */
|
|
2056
|
+
.tai-explorer-folder {
|
|
2057
|
+
display: flex;
|
|
2058
|
+
align-items: center;
|
|
2059
|
+
justify-content: space-between;
|
|
2060
|
+
gap: var(--tai-space-2);
|
|
2061
|
+
flex-wrap: wrap;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2005
2064
|
/* -- Card grids ------------------------------------------------------------------- */
|
|
2006
2065
|
.tai-grid-cards {
|
|
2007
2066
|
display: grid;
|
|
@@ -2098,6 +2157,12 @@
|
|
|
2098
2157
|
gap: var(--tai-space-5);
|
|
2099
2158
|
}
|
|
2100
2159
|
|
|
2160
|
+
/* The sticky top bar occupies the first grid row here, so a fill `<main>` gets
|
|
2161
|
+
the viewport LESS that bar rather than the whole viewport. */
|
|
2162
|
+
.tai-shell-main--fill {
|
|
2163
|
+
--shell-fill-offset: var(--shell-topbar-height);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2101
2166
|
.tai-grid-cards {
|
|
2102
2167
|
grid-template-columns: minmax(0, 1fr);
|
|
2103
2168
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerView<T>` — the shared current-directory explorer every entity screen
|
|
3
|
+
* composes: a breadcrumb over a flat {@link Folder} tree, optional search + tag
|
|
4
|
+
* chips, a list/card toggle, and a body in which FOLDERS are first-class entries
|
|
5
|
+
* sorted above the items (folder rows inside the same table, folder cards inside
|
|
6
|
+
* the same grid). Fully controlled and data-agnostic — the caller owns the item
|
|
7
|
+
* shape via `getItemKey`/`getFolderId`/`renderRow`/`renderCard`, and keeps folder
|
|
8
|
+
* dir / tags / search in the URL or in local state; only the view mode persists
|
|
9
|
+
* internally (per `viewSurface`).
|
|
10
|
+
*
|
|
11
|
+
* The tag glue lives here as exported helpers ({@link buildTagVocabulary},
|
|
12
|
+
* {@link matchesSelectedTags}, {@link UNTAGGED_TOKEN}) so every consuming screen
|
|
13
|
+
* shares one vocabulary/untagged-sentinel/OR-match rule rather than copying it.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
import { type Folder } from './folder-nav';
|
|
17
|
+
/** Reserved filter token for items carrying NO tag; namespaced so it never
|
|
18
|
+
* collides with a real tag. */
|
|
19
|
+
export declare const UNTAGGED_TOKEN = "__untagged__";
|
|
20
|
+
export interface TagVocabularyEntry {
|
|
21
|
+
readonly token: string;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly count: number;
|
|
24
|
+
}
|
|
25
|
+
/** The tag vocabulary over `items`, name-sorted, plus an untagged pseudo-tag
|
|
26
|
+
* (labelled `untaggedLabel`) when any item is untagged; a selected token whose
|
|
27
|
+
* items vanished still appears (count 0) so it can be cleared. */
|
|
28
|
+
export declare function buildTagVocabulary<T>(items: readonly T[], getTags: (item: T) => readonly string[], selectedTags: readonly string[], untaggedLabel: string): TagVocabularyEntry[];
|
|
29
|
+
/** OR semantics: an empty selection matches everything; otherwise an item
|
|
30
|
+
* matches when it carries any selected tag, or is untagged and UNTAGGED_TOKEN
|
|
31
|
+
* is selected. */
|
|
32
|
+
export declare function matchesSelectedTags(itemTags: readonly string[], selected: ReadonlySet<string>): boolean;
|
|
33
|
+
/** One header cell of the table view. `renderRow` must emit exactly this many
|
|
34
|
+
* `<TD>`s per item; folder rows span all of them. */
|
|
35
|
+
export interface ExplorerColumn {
|
|
36
|
+
readonly key: string;
|
|
37
|
+
readonly header: ReactNode;
|
|
38
|
+
readonly numeric?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Enables the tag vocabulary, filter chips, and OR filter. Fully controlled. */
|
|
41
|
+
export interface ExplorerTags<T> {
|
|
42
|
+
readonly getTags: (item: T) => readonly string[];
|
|
43
|
+
readonly selected: readonly string[];
|
|
44
|
+
readonly onChange: (selected: readonly string[]) => void;
|
|
45
|
+
/** The untagged pseudo-tag's visible label (e.g. "Untagged"). */
|
|
46
|
+
readonly untaggedLabel: string;
|
|
47
|
+
/** The chip group's accessible name (e.g. "Filter tools by tag"). */
|
|
48
|
+
readonly filterLabel: string;
|
|
49
|
+
/** Collapse unselected chips beyond this count into a static "+N more"; unset
|
|
50
|
+
* shows every chip. Selected chips always show. */
|
|
51
|
+
readonly maxVisibleTags?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Controlled free-text search. `matches` owns the item shape and casing; it is
|
|
54
|
+
* called only with a non-empty, already-trimmed query. */
|
|
55
|
+
export interface ExplorerSearch<T> {
|
|
56
|
+
readonly value: string;
|
|
57
|
+
readonly onChange: (value: string) => void;
|
|
58
|
+
readonly matches: (item: T, query: string) => boolean;
|
|
59
|
+
/** The input's accessible name. */
|
|
60
|
+
readonly label: string;
|
|
61
|
+
readonly placeholder?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ExplorerEmptyState {
|
|
64
|
+
readonly title: string;
|
|
65
|
+
readonly description?: string;
|
|
66
|
+
readonly action?: ReactNode;
|
|
67
|
+
}
|
|
68
|
+
/** The folder-aware empty-state ladder — every label caller-supplied. */
|
|
69
|
+
export interface ExplorerEmptyStates {
|
|
70
|
+
/** No items and no folders exist at all. */
|
|
71
|
+
readonly empty: ExplorerEmptyState;
|
|
72
|
+
/** The current folder holds nothing — no items, no subfolders — yet the
|
|
73
|
+
* entity set is not globally empty. */
|
|
74
|
+
readonly emptyFolder: ExplorerEmptyState;
|
|
75
|
+
/** The current folder has items but the active tag/search filter excludes
|
|
76
|
+
* every one, and it has no subfolders to show. */
|
|
77
|
+
readonly noMatch: ExplorerEmptyState;
|
|
78
|
+
}
|
|
79
|
+
export interface ExplorerViewProps<T> {
|
|
80
|
+
readonly items: readonly T[];
|
|
81
|
+
readonly getItemKey: (item: T) => string;
|
|
82
|
+
/** The folder an item is filed in (`null` = the root directory). */
|
|
83
|
+
readonly getFolderId: (item: T) => string | null;
|
|
84
|
+
readonly folders: readonly Folder[];
|
|
85
|
+
readonly currentFolderId: string | null;
|
|
86
|
+
readonly onNavigate: (folderId: string | null) => void;
|
|
87
|
+
readonly rootLabel: string;
|
|
88
|
+
/** Persist + drive the list/card toggle under this surface key. */
|
|
89
|
+
readonly viewSurface: string;
|
|
90
|
+
/** The card grid's accessible name; the toggle's is "<label> view". */
|
|
91
|
+
readonly label: string;
|
|
92
|
+
readonly columns: readonly ExplorerColumn[];
|
|
93
|
+
/** The item's table cells (`<TD>`s) — wrapped in a keyed `<TR>` by the explorer. */
|
|
94
|
+
readonly renderRow: (item: T) => ReactNode;
|
|
95
|
+
/** The item's `<Card>` — wrapped in a keyed `listitem` by the explorer. */
|
|
96
|
+
readonly renderCard: (item: T) => ReactNode;
|
|
97
|
+
readonly tags?: ExplorerTags<T>;
|
|
98
|
+
readonly search?: ExplorerSearch<T>;
|
|
99
|
+
/** Per-folder actions rendered on the folder's row/card (e.g. a kebab). */
|
|
100
|
+
readonly renderFolderActions?: (folder: Folder) => ReactNode;
|
|
101
|
+
readonly emptyStates: ExplorerEmptyStates;
|
|
102
|
+
}
|
|
103
|
+
export declare function ExplorerView<T>({ items, getItemKey, getFolderId, folders, currentFolderId, onNavigate, rootLabel, viewSurface, label, columns, renderRow, renderCard, tags, search, renderFolderActions, emptyStates, }: ExplorerViewProps<T>): ReactNode;
|
|
104
|
+
//# sourceMappingURL=explorer-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-view.d.ts","sourceRoot":"","sources":["../../src/components/explorer-view.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,EAA6C,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtF;gCACgC;AAChC,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,MAAM,EAAE,EACvC,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,aAAa,EAAE,MAAM,GACpB,kBAAkB,EAAE,CA2BtB;AAED;;mBAEmB;AACnB,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAC5B,OAAO,CAIT;AAED;sDACsD;AACtD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IACzD,iEAAiE;IACjE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;wDACoD;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;2DAC2D;AAC3D,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACtD,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC;4CACwC;IACxC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC;uDACmD;IACnD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACzC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAEjD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAE5C,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAEpC,2EAA2E;IAC3E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IAE7D,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C;AA8FD,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAC9B,KAAK,EACL,UAAU,EACV,WAAW,EACX,OAAO,EACP,eAAe,EACf,UAAU,EACV,SAAS,EACT,WAAW,EACX,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,IAAI,EACJ,MAAM,EACN,mBAAmB,EACnB,WAAW,GACZ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,CAgJlC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EntityCardGrid } from './entity-card-grid';
|
|
3
|
+
import { childFolders, FolderBreadcrumb, FolderRow } from './folder-nav';
|
|
4
|
+
import { SearchIcon } from './icons';
|
|
5
|
+
import { TextInput } from './inputs';
|
|
6
|
+
import { Card, EmptyState } from './primitives';
|
|
7
|
+
import { Table, TBody, TD, TH, THead, TR } from './table';
|
|
8
|
+
import { useViewMode, ViewToggle } from './view-toggle';
|
|
9
|
+
/** Reserved filter token for items carrying NO tag; namespaced so it never
|
|
10
|
+
* collides with a real tag. */
|
|
11
|
+
export const UNTAGGED_TOKEN = '__untagged__';
|
|
12
|
+
/** The tag vocabulary over `items`, name-sorted, plus an untagged pseudo-tag
|
|
13
|
+
* (labelled `untaggedLabel`) when any item is untagged; a selected token whose
|
|
14
|
+
* items vanished still appears (count 0) so it can be cleared. */
|
|
15
|
+
export function buildTagVocabulary(items, getTags, selectedTags, untaggedLabel) {
|
|
16
|
+
const counts = new Map();
|
|
17
|
+
let untagged = 0;
|
|
18
|
+
for (const item of items) {
|
|
19
|
+
const tags = getTags(item);
|
|
20
|
+
if (tags.length === 0) {
|
|
21
|
+
untagged += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
for (const tag of tags)
|
|
25
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
const vocabulary = [...counts.entries()]
|
|
28
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
29
|
+
.map(([label, count]) => ({ token: label, label, count }));
|
|
30
|
+
if (untagged > 0) {
|
|
31
|
+
vocabulary.push({ token: UNTAGGED_TOKEN, label: untaggedLabel, count: untagged });
|
|
32
|
+
}
|
|
33
|
+
for (const token of selectedTags) {
|
|
34
|
+
if (!vocabulary.some((entry) => entry.token === token)) {
|
|
35
|
+
vocabulary.push({
|
|
36
|
+
token,
|
|
37
|
+
label: token === UNTAGGED_TOKEN ? untaggedLabel : token,
|
|
38
|
+
count: 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return vocabulary;
|
|
43
|
+
}
|
|
44
|
+
/** OR semantics: an empty selection matches everything; otherwise an item
|
|
45
|
+
* matches when it carries any selected tag, or is untagged and UNTAGGED_TOKEN
|
|
46
|
+
* is selected. */
|
|
47
|
+
export function matchesSelectedTags(itemTags, selected) {
|
|
48
|
+
if (selected.size === 0)
|
|
49
|
+
return true;
|
|
50
|
+
if (itemTags.length === 0)
|
|
51
|
+
return selected.has(UNTAGGED_TOKEN);
|
|
52
|
+
return itemTags.some((tag) => selected.has(tag));
|
|
53
|
+
}
|
|
54
|
+
/** A togglable tag chip; `aria-pressed` reflects whether the tag filters the list. */
|
|
55
|
+
function TagChip({ entry, active, onToggle, }) {
|
|
56
|
+
return (_jsxs("button", { type: "button", className: "tai-chip", "aria-pressed": active, "aria-label": `${entry.label} (${String(entry.count)})`, onClick: onToggle, children: [_jsx("span", { children: entry.label }), _jsx("span", { children: entry.count })] }));
|
|
57
|
+
}
|
|
58
|
+
/** The filter row: every selected chip plus as many unselected chips as fit under
|
|
59
|
+
* the cap, with the remainder collapsed into a STATIC "+N more" count. An unset
|
|
60
|
+
* cap shows every chip. */
|
|
61
|
+
function TagFilterRow({ vocabulary, selectedSet, onToggle, filterLabel, maxVisibleTags, }) {
|
|
62
|
+
const visible = [];
|
|
63
|
+
let hidden = 0;
|
|
64
|
+
let unselectedShown = 0;
|
|
65
|
+
for (const entry of vocabulary) {
|
|
66
|
+
if (selectedSet.has(entry.token)) {
|
|
67
|
+
visible.push(entry);
|
|
68
|
+
}
|
|
69
|
+
else if (maxVisibleTags === undefined || unselectedShown < maxVisibleTags) {
|
|
70
|
+
visible.push(entry);
|
|
71
|
+
unselectedShown += 1;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
hidden += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return (_jsxs("div", { role: "group", "aria-label": filterLabel, className: "tai-row", children: [visible.map((entry) => (_jsx(TagChip, { entry: entry, active: selectedSet.has(entry.token), onToggle: () => {
|
|
78
|
+
onToggle(entry.token);
|
|
79
|
+
} }, entry.token))), hidden > 0 ? (_jsx("span", { className: "tai-chip tai-chip-static", children: `+${String(hidden)} more` })) : null] }));
|
|
80
|
+
}
|
|
81
|
+
/** A first-class folder entry — a `FolderRow` (icon + name, navigates inward)
|
|
82
|
+
* beside its optional actions, in the layout shared by the row cell and the card. */
|
|
83
|
+
function FolderEntry({ folder, onNavigate, renderFolderActions, }) {
|
|
84
|
+
return (_jsxs("div", { className: "tai-explorer-folder", children: [_jsx(FolderRow, { folder: folder, onOpen: onNavigate }), renderFolderActions ? renderFolderActions(folder) : null] }));
|
|
85
|
+
}
|
|
86
|
+
export function ExplorerView({ items, getItemKey, getFolderId, folders, currentFolderId, onNavigate, rootLabel, viewSurface, label, columns, renderRow, renderCard, tags, search, renderFolderActions, emptyStates, }) {
|
|
87
|
+
const [viewMode, setViewMode] = useViewMode(viewSurface);
|
|
88
|
+
if (viewSurface === '')
|
|
89
|
+
throw new Error('ExplorerView requires a non-empty viewSurface key.');
|
|
90
|
+
if (columns.length === 0)
|
|
91
|
+
throw new Error('ExplorerView requires at least one column.');
|
|
92
|
+
// Nothing anywhere: no items and no folders. A bare empty state, no controls.
|
|
93
|
+
if (items.length === 0 && folders.length === 0) {
|
|
94
|
+
return _jsx(EmptyState, { ...emptyStates.empty });
|
|
95
|
+
}
|
|
96
|
+
const subfolders = childFolders(folders, currentFolderId);
|
|
97
|
+
const inFolder = items.filter((item) => getFolderId(item) === currentFolderId);
|
|
98
|
+
const selectedSet = tags !== undefined ? new Set(tags.selected) : undefined;
|
|
99
|
+
const query = search ? search.value.trim() : '';
|
|
100
|
+
const filtered = inFolder.filter((item) => {
|
|
101
|
+
if (tags !== undefined && selectedSet !== undefined && selectedSet.size > 0) {
|
|
102
|
+
if (!matchesSelectedTags(tags.getTags(item), selectedSet))
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (search !== undefined && query !== '') {
|
|
106
|
+
if (!search.matches(item, query))
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
const vocabulary = tags !== undefined
|
|
112
|
+
? buildTagVocabulary(inFolder, tags.getTags, tags.selected, tags.untaggedLabel)
|
|
113
|
+
: [];
|
|
114
|
+
const toggleTag = (token) => {
|
|
115
|
+
if (tags === undefined || selectedSet === undefined)
|
|
116
|
+
return;
|
|
117
|
+
const next = selectedSet.has(token)
|
|
118
|
+
? tags.selected.filter((t) => t !== token)
|
|
119
|
+
: [...tags.selected, token];
|
|
120
|
+
tags.onChange(next);
|
|
121
|
+
};
|
|
122
|
+
const hasEntries = subfolders.length > 0 || filtered.length > 0;
|
|
123
|
+
let body;
|
|
124
|
+
if (!hasEntries) {
|
|
125
|
+
// The current folder renders nothing. A filter emptied it (`inFolder` non-empty)
|
|
126
|
+
// → "no match"; otherwise the folder is genuinely empty.
|
|
127
|
+
body = (_jsx(EmptyState, { ...(inFolder.length > 0 ? emptyStates.noMatch : emptyStates.emptyFolder) }));
|
|
128
|
+
}
|
|
129
|
+
else if (viewMode === 'cards') {
|
|
130
|
+
body = (_jsxs(EntityCardGrid, { "aria-label": label, children: [subfolders.map((folder) => (_jsx("div", { role: "listitem", children: _jsx(Card, { interactive: true, children: _jsx(FolderEntry, { folder: folder, onNavigate: onNavigate, renderFolderActions: renderFolderActions }) }) }, `folder:${folder.id}`))), filtered.map((item) => (_jsx("div", { role: "listitem", children: renderCard(item) }, `item:${getItemKey(item)}`)))] }));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Folder rows share the item table, spanning every column, sorted above items —
|
|
134
|
+
// so a folder-with-only-subfolders is folder ROWS, never a header-only table.
|
|
135
|
+
body = (_jsxs(Table, { children: [_jsx(THead, { children: _jsx(TR, { children: columns.map((column) => (_jsx(TH, { numeric: column.numeric, children: column.header }, column.key))) }) }), _jsxs(TBody, { children: [subfolders.map((folder) => (_jsx(TR, { children: _jsx(TD, { colSpan: columns.length, children: _jsx(FolderEntry, { folder: folder, onNavigate: onNavigate, renderFolderActions: renderFolderActions }) }) }, `folder:${folder.id}`))), filtered.map((item) => (_jsx(TR, { children: renderRow(item) }, `item:${getItemKey(item)}`)))] })] }));
|
|
136
|
+
}
|
|
137
|
+
return (_jsxs("div", { className: "tai-explorer", children: [_jsxs("div", { className: "tai-explorer-controls", children: [search !== undefined ? (_jsxs("span", { className: "tai-row", children: [_jsx(SearchIcon, {}), _jsx(TextInput, { value: search.value, "aria-label": search.label, placeholder: search.placeholder, onChange: (event) => {
|
|
138
|
+
search.onChange(event.target.value);
|
|
139
|
+
} })] })) : (_jsx("span", {})), _jsx(ViewToggle, { value: viewMode, onValueChange: setViewMode, "aria-label": `${label} view` })] }), _jsx(FolderBreadcrumb, { folders: folders, currentFolderId: currentFolderId, onNavigate: onNavigate, rootLabel: rootLabel }), tags !== undefined && selectedSet !== undefined && vocabulary.length > 0 ? (_jsx(TagFilterRow, { vocabulary: vocabulary, selectedSet: selectedSet, onToggle: toggleTag, filterLabel: tags.filterLabel, maxVisibleTags: tags.maxVisibleTags })) : null, body] }));
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=explorer-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-view.js","sourceRoot":"","sources":["../../src/components/explorer-view.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-height page mode: a page declares "I fill the viewport" and the shell
|
|
3
|
+
* gives its wrapper a viewport-height flex chain instead of the default
|
|
4
|
+
* content-sized, scrolling `.tai-page`.
|
|
5
|
+
*
|
|
6
|
+
* The signal travels page → shell through context, NOT a route handle: plugin
|
|
7
|
+
* pages all share ONE catch-all route, so a per-route handle cannot vary between
|
|
8
|
+
* them. A rendered page (feature route or plugin page alike) opts in with
|
|
9
|
+
* {@link useFillViewport}; the shell reads {@link usePageFillActive} to add the
|
|
10
|
+
* `.tai-page--fill` / `.tai-shell-main--fill` modifiers. Opt-in is ref-counted so
|
|
11
|
+
* the modifier survives a route transition (old page unmounts after the new one
|
|
12
|
+
* mounts) and clears exactly when the last fill page leaves.
|
|
13
|
+
*/
|
|
14
|
+
import { type ReactNode } from 'react';
|
|
15
|
+
/**
|
|
16
|
+
* Wraps the shell's routed region: holds the count of pages asking for fill mode
|
|
17
|
+
* and publishes both the registrar (for {@link useFillViewport}) and the active
|
|
18
|
+
* flag (for {@link usePageFillActive}). Renders no DOM of its own.
|
|
19
|
+
*/
|
|
20
|
+
export declare function PageFillProvider({ children }: {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Opt the current page into full-height fill mode for as long as it is mounted.
|
|
25
|
+
* The shell fills `<main>` to the viewport and lets the page's own root flex or
|
|
26
|
+
* `height: 100%` down the chain. Must be called under a {@link PageFillProvider}
|
|
27
|
+
* (the shell mounts one); outside it there is no wrapper to modify, so it throws
|
|
28
|
+
* rather than silently do nothing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function useFillViewport(): void;
|
|
31
|
+
/** Whether any mounted page currently asks for fill mode — the shell's cue to add
|
|
32
|
+
* the fill modifiers to `<main>` and the page wrapper. */
|
|
33
|
+
export declare function usePageFillActive(): boolean;
|
|
34
|
+
//# sourceMappingURL=page-fill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-fill.d.ts","sourceRoot":"","sources":["../../src/components/page-fill.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAWhG;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,GAAG,SAAS,CAuBjF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAQtC;AAED;0DAC0D;AAC1D,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Full-height page mode: a page declares "I fill the viewport" and the shell
|
|
4
|
+
* gives its wrapper a viewport-height flex chain instead of the default
|
|
5
|
+
* content-sized, scrolling `.tai-page`.
|
|
6
|
+
*
|
|
7
|
+
* The signal travels page → shell through context, NOT a route handle: plugin
|
|
8
|
+
* pages all share ONE catch-all route, so a per-route handle cannot vary between
|
|
9
|
+
* them. A rendered page (feature route or plugin page alike) opts in with
|
|
10
|
+
* {@link useFillViewport}; the shell reads {@link usePageFillActive} to add the
|
|
11
|
+
* `.tai-page--fill` / `.tai-shell-main--fill` modifiers. Opt-in is ref-counted so
|
|
12
|
+
* the modifier survives a route transition (old page unmounts after the new one
|
|
13
|
+
* mounts) and clears exactly when the last fill page leaves.
|
|
14
|
+
*/
|
|
15
|
+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
16
|
+
const PageFillRegistryContext = createContext(null);
|
|
17
|
+
const PageFillActiveContext = createContext(false);
|
|
18
|
+
/**
|
|
19
|
+
* Wraps the shell's routed region: holds the count of pages asking for fill mode
|
|
20
|
+
* and publishes both the registrar (for {@link useFillViewport}) and the active
|
|
21
|
+
* flag (for {@link usePageFillActive}). Renders no DOM of its own.
|
|
22
|
+
*/
|
|
23
|
+
export function PageFillProvider({ children }) {
|
|
24
|
+
const [fillCount, setFillCount] = useState(0);
|
|
25
|
+
const registry = useMemo(() => ({
|
|
26
|
+
acquire() {
|
|
27
|
+
setFillCount((count) => count + 1);
|
|
28
|
+
let released = false;
|
|
29
|
+
return () => {
|
|
30
|
+
if (released)
|
|
31
|
+
return;
|
|
32
|
+
released = true;
|
|
33
|
+
setFillCount((count) => count - 1);
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
}), []);
|
|
37
|
+
return (_jsx(PageFillRegistryContext.Provider, { value: registry, children: _jsx(PageFillActiveContext.Provider, { value: fillCount > 0, children: children }) }));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Opt the current page into full-height fill mode for as long as it is mounted.
|
|
41
|
+
* The shell fills `<main>` to the viewport and lets the page's own root flex or
|
|
42
|
+
* `height: 100%` down the chain. Must be called under a {@link PageFillProvider}
|
|
43
|
+
* (the shell mounts one); outside it there is no wrapper to modify, so it throws
|
|
44
|
+
* rather than silently do nothing.
|
|
45
|
+
*/
|
|
46
|
+
export function useFillViewport() {
|
|
47
|
+
const registry = useContext(PageFillRegistryContext);
|
|
48
|
+
if (registry === null) {
|
|
49
|
+
throw new Error('useFillViewport must be used within a <PageFillProvider> (the shell provides it)');
|
|
50
|
+
}
|
|
51
|
+
useEffect(() => registry.acquire(), [registry]);
|
|
52
|
+
}
|
|
53
|
+
/** Whether any mounted page currently asks for fill mode — the shell's cue to add
|
|
54
|
+
* the fill modifiers to `<main>` and the page wrapper. */
|
|
55
|
+
export function usePageFillActive() {
|
|
56
|
+
return useContext(PageFillActiveContext);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=page-fill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-fill.js","sourceRoot":"","sources":["../../src/components/page-fill.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAQhG,MAAM,uBAAuB,GAAG,aAAa,CAA0B,IAAI,CAAC,CAAC;AAC7E,MAAM,qBAAqB,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,QAAQ,EAA2B;IACpE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,CAAC;QACL,OAAO;YACL,YAAY,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACnC,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,OAAO,GAAG,EAAE;gBACV,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,YAAY,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACrC,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,EACF,EAAE,CACH,CAAC;IACF,OAAO,CACL,KAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAC/C,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,SAAS,GAAG,CAAC,YACjD,QAAQ,GACsB,GACA,CACpC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,QAAQ,GAAG,UAAU,CAAC,uBAAuB,CAAC,CAAC;IACrD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IACD,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;0DAC0D;AAC1D,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAC;AAC3C,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export { Button, Card, Skeleton, EmptyState, ErrorState, Spinner } from './compo
|
|
|
45
45
|
export type { ButtonProps, LinkButtonProps, ButtonVariant, CardProps, SkeletonProps, EmptyStateProps, ErrorStateProps, SpinnerProps, } from './components/primitives';
|
|
46
46
|
export { PageHeader, Page, Stack } from './components/page-header';
|
|
47
47
|
export type { PageHeaderProps, PageLayoutProps, StackProps } from './components/page-header';
|
|
48
|
+
export { PageFillProvider, useFillViewport, usePageFillActive } from './components/page-fill';
|
|
48
49
|
export { ErrorBoundary } from './components/error-boundary';
|
|
49
50
|
export type { ErrorBoundaryProps } from './components/error-boundary';
|
|
50
51
|
export { Field, useFieldControl } from './components/field';
|
|
@@ -85,6 +86,8 @@ export { ViewToggle, useViewMode } from './components/view-toggle';
|
|
|
85
86
|
export type { ViewMode, ViewToggleProps } from './components/view-toggle';
|
|
86
87
|
export { EntityCardGrid } from './components/entity-card-grid';
|
|
87
88
|
export type { EntityCardGridProps } from './components/entity-card-grid';
|
|
89
|
+
export { ExplorerView, buildTagVocabulary, matchesSelectedTags, UNTAGGED_TOKEN, } from './components/explorer-view';
|
|
90
|
+
export type { ExplorerViewProps, ExplorerColumn, ExplorerTags, ExplorerSearch, ExplorerEmptyState, ExplorerEmptyStates, TagVocabularyEntry, } from './components/explorer-view';
|
|
88
91
|
export { FolderBreadcrumb, FolderRow, FolderPicker, childFolders, folderPathTo, } from './components/folder-nav';
|
|
89
92
|
export type { Folder, FolderBreadcrumbProps, FolderRowProps, FolderPickerProps, } from './components/folder-nav';
|
|
90
93
|
export { CodeBlock } from './components/code-block';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,7 @@ export { effectiveHidden, hiddenToolNames } from './tool-visibility';
|
|
|
48
48
|
// -- Design system -----------------------------------------------------------
|
|
49
49
|
export { Button, Card, Skeleton, EmptyState, ErrorState, Spinner } from './components/primitives';
|
|
50
50
|
export { PageHeader, Page, Stack } from './components/page-header';
|
|
51
|
+
export { PageFillProvider, useFillViewport, usePageFillActive } from './components/page-fill';
|
|
51
52
|
export { ErrorBoundary } from './components/error-boundary';
|
|
52
53
|
export { Field, useFieldControl } from './components/field';
|
|
53
54
|
export { TextInput, Textarea, NumberInput } from './components/inputs';
|
|
@@ -68,6 +69,7 @@ export { TagChips, TagsInput } from './components/tags';
|
|
|
68
69
|
export { OverlayDetailsFields, overlayDetailsPatch } from './components/overlay-details-fields';
|
|
69
70
|
export { ViewToggle, useViewMode } from './components/view-toggle';
|
|
70
71
|
export { EntityCardGrid } from './components/entity-card-grid';
|
|
72
|
+
export { ExplorerView, buildTagVocabulary, matchesSelectedTags, UNTAGGED_TOKEN, } from './components/explorer-view';
|
|
71
73
|
export { FolderBreadcrumb, FolderRow, FolderPicker, childFolders, folderPathTo, } from './components/folder-nav';
|
|
72
74
|
export { CodeBlock } from './components/code-block';
|
|
73
75
|
export { FleetReport } from './components/fleet-report';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tai42/studio-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "tai42 <oss@tai42.ai>",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@radix-ui/react-select": "^2.3.2",
|
|
90
90
|
"@radix-ui/react-tabs": "^1.1.16",
|
|
91
91
|
"@radix-ui/react-tooltip": "^1.2.11",
|
|
92
|
-
"@tai42/api-client": "0.
|
|
92
|
+
"@tai42/api-client": "0.7.0"
|
|
93
93
|
},
|
|
94
94
|
"scripts": {
|
|
95
95
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -1764,6 +1764,19 @@
|
|
|
1764
1764
|
padding: var(--tai-space-6) var(--tai-space-8);
|
|
1765
1765
|
}
|
|
1766
1766
|
|
|
1767
|
+
/* Fill mode: a page that opted in via `useFillViewport`. The wrapper stops being
|
|
1768
|
+
content-sized and instead fills `.tai-shell-main--fill` (which carries the
|
|
1769
|
+
viewport height), so a canvas page's `height: 100%`/`flex: 1` resolves down
|
|
1770
|
+
the chain. `min-height: 0` lets the flex child shrink below its content and
|
|
1771
|
+
scroll internally. A canvas wants the whole pane, so the reading-column cap
|
|
1772
|
+
and page padding are dropped — no letterboxing. */
|
|
1773
|
+
.tai-page--fill {
|
|
1774
|
+
flex: 1;
|
|
1775
|
+
min-height: 0;
|
|
1776
|
+
max-width: none;
|
|
1777
|
+
padding: 0;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1767
1780
|
.tai-stack {
|
|
1768
1781
|
display: flex;
|
|
1769
1782
|
flex-direction: column;
|
|
@@ -1941,6 +1954,22 @@
|
|
|
1941
1954
|
background: var(--tai-color-bg);
|
|
1942
1955
|
}
|
|
1943
1956
|
|
|
1957
|
+
/* Fill mode: `<main>` becomes a flex column pinned to the viewport height so a
|
|
1958
|
+
fill page has a definite height to resolve against (a stretched grid item's
|
|
1959
|
+
height is not a reliable percentage base). The `100vh`/`100dvh` pair matches
|
|
1960
|
+
`.tai-shell-sidebar` — `dvh` is the live viewport, `vh` the pre-`dvh` fallback
|
|
1961
|
+
a browser below the floor keeps. `--shell-fill-offset` is the chrome above
|
|
1962
|
+
`<main>` in the grid column: 0 beside the full-height sidebar, the top-bar
|
|
1963
|
+
height on the phone band where the sticky bar takes the first row. */
|
|
1964
|
+
.tai-shell-main--fill {
|
|
1965
|
+
--shell-fill-offset: 0px;
|
|
1966
|
+
display: flex;
|
|
1967
|
+
flex-direction: column;
|
|
1968
|
+
min-height: 0;
|
|
1969
|
+
height: calc(100vh - var(--shell-fill-offset));
|
|
1970
|
+
height: calc(100dvh - var(--shell-fill-offset));
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1944
1973
|
.tai-brand {
|
|
1945
1974
|
display: flex;
|
|
1946
1975
|
align-items: center;
|
|
@@ -2002,6 +2031,36 @@
|
|
|
2002
2031
|
min-width: 0;
|
|
2003
2032
|
}
|
|
2004
2033
|
|
|
2034
|
+
/* -- Explorer ---------------------------------------------------------------------
|
|
2035
|
+
The shared current-directory explorer: controls row, breadcrumb, tag chips,
|
|
2036
|
+
then the folder/item body. Vertical rhythm only — the body carries its own
|
|
2037
|
+
table/grid. */
|
|
2038
|
+
.tai-explorer {
|
|
2039
|
+
display: flex;
|
|
2040
|
+
flex-direction: column;
|
|
2041
|
+
gap: var(--tai-space-4);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/* Search on the leading edge, the view toggle on the trailing edge; wraps under
|
|
2045
|
+
its own width on a narrow surface. */
|
|
2046
|
+
.tai-explorer-controls {
|
|
2047
|
+
display: flex;
|
|
2048
|
+
align-items: center;
|
|
2049
|
+
justify-content: space-between;
|
|
2050
|
+
gap: var(--tai-space-3);
|
|
2051
|
+
flex-wrap: wrap;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/* A first-class folder entry (row cell or card): the folder affordance beside
|
|
2055
|
+
its optional actions, the actions pushed to the trailing edge. */
|
|
2056
|
+
.tai-explorer-folder {
|
|
2057
|
+
display: flex;
|
|
2058
|
+
align-items: center;
|
|
2059
|
+
justify-content: space-between;
|
|
2060
|
+
gap: var(--tai-space-2);
|
|
2061
|
+
flex-wrap: wrap;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2005
2064
|
/* -- Card grids ------------------------------------------------------------------- */
|
|
2006
2065
|
.tai-grid-cards {
|
|
2007
2066
|
display: grid;
|
|
@@ -2098,6 +2157,12 @@
|
|
|
2098
2157
|
gap: var(--tai-space-5);
|
|
2099
2158
|
}
|
|
2100
2159
|
|
|
2160
|
+
/* The sticky top bar occupies the first grid row here, so a fill `<main>` gets
|
|
2161
|
+
the viewport LESS that bar rather than the whole viewport. */
|
|
2162
|
+
.tai-shell-main--fill {
|
|
2163
|
+
--shell-fill-offset: var(--shell-topbar-height);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2101
2166
|
.tai-grid-cards {
|
|
2102
2167
|
grid-template-columns: minmax(0, 1fr);
|
|
2103
2168
|
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerView<T>` — the shared current-directory explorer every entity screen
|
|
3
|
+
* composes: a breadcrumb over a flat {@link Folder} tree, optional search + tag
|
|
4
|
+
* chips, a list/card toggle, and a body in which FOLDERS are first-class entries
|
|
5
|
+
* sorted above the items (folder rows inside the same table, folder cards inside
|
|
6
|
+
* the same grid). Fully controlled and data-agnostic — the caller owns the item
|
|
7
|
+
* shape via `getItemKey`/`getFolderId`/`renderRow`/`renderCard`, and keeps folder
|
|
8
|
+
* dir / tags / search in the URL or in local state; only the view mode persists
|
|
9
|
+
* internally (per `viewSurface`).
|
|
10
|
+
*
|
|
11
|
+
* The tag glue lives here as exported helpers ({@link buildTagVocabulary},
|
|
12
|
+
* {@link matchesSelectedTags}, {@link UNTAGGED_TOKEN}) so every consuming screen
|
|
13
|
+
* shares one vocabulary/untagged-sentinel/OR-match rule rather than copying it.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
|
|
17
|
+
import { EntityCardGrid } from './entity-card-grid';
|
|
18
|
+
import { childFolders, FolderBreadcrumb, FolderRow, type Folder } from './folder-nav';
|
|
19
|
+
import { SearchIcon } from './icons';
|
|
20
|
+
import { TextInput } from './inputs';
|
|
21
|
+
import { Card, EmptyState } from './primitives';
|
|
22
|
+
import { Table, TBody, TD, TH, THead, TR } from './table';
|
|
23
|
+
import { useViewMode, ViewToggle } from './view-toggle';
|
|
24
|
+
|
|
25
|
+
/** Reserved filter token for items carrying NO tag; namespaced so it never
|
|
26
|
+
* collides with a real tag. */
|
|
27
|
+
export const UNTAGGED_TOKEN = '__untagged__';
|
|
28
|
+
|
|
29
|
+
export interface TagVocabularyEntry {
|
|
30
|
+
readonly token: string;
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly count: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The tag vocabulary over `items`, name-sorted, plus an untagged pseudo-tag
|
|
36
|
+
* (labelled `untaggedLabel`) when any item is untagged; a selected token whose
|
|
37
|
+
* items vanished still appears (count 0) so it can be cleared. */
|
|
38
|
+
export function buildTagVocabulary<T>(
|
|
39
|
+
items: readonly T[],
|
|
40
|
+
getTags: (item: T) => readonly string[],
|
|
41
|
+
selectedTags: readonly string[],
|
|
42
|
+
untaggedLabel: string,
|
|
43
|
+
): TagVocabularyEntry[] {
|
|
44
|
+
const counts = new Map<string, number>();
|
|
45
|
+
let untagged = 0;
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
const tags = getTags(item);
|
|
48
|
+
if (tags.length === 0) {
|
|
49
|
+
untagged += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
for (const tag of tags) counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
const vocabulary: TagVocabularyEntry[] = [...counts.entries()]
|
|
55
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
56
|
+
.map(([label, count]) => ({ token: label, label, count }));
|
|
57
|
+
if (untagged > 0) {
|
|
58
|
+
vocabulary.push({ token: UNTAGGED_TOKEN, label: untaggedLabel, count: untagged });
|
|
59
|
+
}
|
|
60
|
+
for (const token of selectedTags) {
|
|
61
|
+
if (!vocabulary.some((entry) => entry.token === token)) {
|
|
62
|
+
vocabulary.push({
|
|
63
|
+
token,
|
|
64
|
+
label: token === UNTAGGED_TOKEN ? untaggedLabel : token,
|
|
65
|
+
count: 0,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return vocabulary;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** OR semantics: an empty selection matches everything; otherwise an item
|
|
73
|
+
* matches when it carries any selected tag, or is untagged and UNTAGGED_TOKEN
|
|
74
|
+
* is selected. */
|
|
75
|
+
export function matchesSelectedTags(
|
|
76
|
+
itemTags: readonly string[],
|
|
77
|
+
selected: ReadonlySet<string>,
|
|
78
|
+
): boolean {
|
|
79
|
+
if (selected.size === 0) return true;
|
|
80
|
+
if (itemTags.length === 0) return selected.has(UNTAGGED_TOKEN);
|
|
81
|
+
return itemTags.some((tag) => selected.has(tag));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One header cell of the table view. `renderRow` must emit exactly this many
|
|
85
|
+
* `<TD>`s per item; folder rows span all of them. */
|
|
86
|
+
export interface ExplorerColumn {
|
|
87
|
+
readonly key: string;
|
|
88
|
+
readonly header: ReactNode;
|
|
89
|
+
readonly numeric?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Enables the tag vocabulary, filter chips, and OR filter. Fully controlled. */
|
|
93
|
+
export interface ExplorerTags<T> {
|
|
94
|
+
readonly getTags: (item: T) => readonly string[];
|
|
95
|
+
readonly selected: readonly string[];
|
|
96
|
+
readonly onChange: (selected: readonly string[]) => void;
|
|
97
|
+
/** The untagged pseudo-tag's visible label (e.g. "Untagged"). */
|
|
98
|
+
readonly untaggedLabel: string;
|
|
99
|
+
/** The chip group's accessible name (e.g. "Filter tools by tag"). */
|
|
100
|
+
readonly filterLabel: string;
|
|
101
|
+
/** Collapse unselected chips beyond this count into a static "+N more"; unset
|
|
102
|
+
* shows every chip. Selected chips always show. */
|
|
103
|
+
readonly maxVisibleTags?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Controlled free-text search. `matches` owns the item shape and casing; it is
|
|
107
|
+
* called only with a non-empty, already-trimmed query. */
|
|
108
|
+
export interface ExplorerSearch<T> {
|
|
109
|
+
readonly value: string;
|
|
110
|
+
readonly onChange: (value: string) => void;
|
|
111
|
+
readonly matches: (item: T, query: string) => boolean;
|
|
112
|
+
/** The input's accessible name. */
|
|
113
|
+
readonly label: string;
|
|
114
|
+
readonly placeholder?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ExplorerEmptyState {
|
|
118
|
+
readonly title: string;
|
|
119
|
+
readonly description?: string;
|
|
120
|
+
readonly action?: ReactNode;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The folder-aware empty-state ladder — every label caller-supplied. */
|
|
124
|
+
export interface ExplorerEmptyStates {
|
|
125
|
+
/** No items and no folders exist at all. */
|
|
126
|
+
readonly empty: ExplorerEmptyState;
|
|
127
|
+
/** The current folder holds nothing — no items, no subfolders — yet the
|
|
128
|
+
* entity set is not globally empty. */
|
|
129
|
+
readonly emptyFolder: ExplorerEmptyState;
|
|
130
|
+
/** The current folder has items but the active tag/search filter excludes
|
|
131
|
+
* every one, and it has no subfolders to show. */
|
|
132
|
+
readonly noMatch: ExplorerEmptyState;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ExplorerViewProps<T> {
|
|
136
|
+
readonly items: readonly T[];
|
|
137
|
+
readonly getItemKey: (item: T) => string;
|
|
138
|
+
/** The folder an item is filed in (`null` = the root directory). */
|
|
139
|
+
readonly getFolderId: (item: T) => string | null;
|
|
140
|
+
|
|
141
|
+
readonly folders: readonly Folder[];
|
|
142
|
+
readonly currentFolderId: string | null;
|
|
143
|
+
readonly onNavigate: (folderId: string | null) => void;
|
|
144
|
+
readonly rootLabel: string;
|
|
145
|
+
|
|
146
|
+
/** Persist + drive the list/card toggle under this surface key. */
|
|
147
|
+
readonly viewSurface: string;
|
|
148
|
+
/** The card grid's accessible name; the toggle's is "<label> view". */
|
|
149
|
+
readonly label: string;
|
|
150
|
+
|
|
151
|
+
readonly columns: readonly ExplorerColumn[];
|
|
152
|
+
/** The item's table cells (`<TD>`s) — wrapped in a keyed `<TR>` by the explorer. */
|
|
153
|
+
readonly renderRow: (item: T) => ReactNode;
|
|
154
|
+
/** The item's `<Card>` — wrapped in a keyed `listitem` by the explorer. */
|
|
155
|
+
readonly renderCard: (item: T) => ReactNode;
|
|
156
|
+
|
|
157
|
+
readonly tags?: ExplorerTags<T>;
|
|
158
|
+
readonly search?: ExplorerSearch<T>;
|
|
159
|
+
|
|
160
|
+
/** Per-folder actions rendered on the folder's row/card (e.g. a kebab). */
|
|
161
|
+
readonly renderFolderActions?: (folder: Folder) => ReactNode;
|
|
162
|
+
|
|
163
|
+
readonly emptyStates: ExplorerEmptyStates;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A togglable tag chip; `aria-pressed` reflects whether the tag filters the list. */
|
|
167
|
+
function TagChip({
|
|
168
|
+
entry,
|
|
169
|
+
active,
|
|
170
|
+
onToggle,
|
|
171
|
+
}: {
|
|
172
|
+
readonly entry: TagVocabularyEntry;
|
|
173
|
+
readonly active: boolean;
|
|
174
|
+
readonly onToggle: () => void;
|
|
175
|
+
}): ReactNode {
|
|
176
|
+
return (
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
className="tai-chip"
|
|
180
|
+
aria-pressed={active}
|
|
181
|
+
aria-label={`${entry.label} (${String(entry.count)})`}
|
|
182
|
+
onClick={onToggle}
|
|
183
|
+
>
|
|
184
|
+
<span>{entry.label}</span>
|
|
185
|
+
<span>{entry.count}</span>
|
|
186
|
+
</button>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The filter row: every selected chip plus as many unselected chips as fit under
|
|
191
|
+
* the cap, with the remainder collapsed into a STATIC "+N more" count. An unset
|
|
192
|
+
* cap shows every chip. */
|
|
193
|
+
function TagFilterRow({
|
|
194
|
+
vocabulary,
|
|
195
|
+
selectedSet,
|
|
196
|
+
onToggle,
|
|
197
|
+
filterLabel,
|
|
198
|
+
maxVisibleTags,
|
|
199
|
+
}: {
|
|
200
|
+
readonly vocabulary: readonly TagVocabularyEntry[];
|
|
201
|
+
readonly selectedSet: ReadonlySet<string>;
|
|
202
|
+
readonly onToggle: (token: string) => void;
|
|
203
|
+
readonly filterLabel: string;
|
|
204
|
+
readonly maxVisibleTags: number | undefined;
|
|
205
|
+
}): ReactNode {
|
|
206
|
+
const visible: TagVocabularyEntry[] = [];
|
|
207
|
+
let hidden = 0;
|
|
208
|
+
let unselectedShown = 0;
|
|
209
|
+
for (const entry of vocabulary) {
|
|
210
|
+
if (selectedSet.has(entry.token)) {
|
|
211
|
+
visible.push(entry);
|
|
212
|
+
} else if (maxVisibleTags === undefined || unselectedShown < maxVisibleTags) {
|
|
213
|
+
visible.push(entry);
|
|
214
|
+
unselectedShown += 1;
|
|
215
|
+
} else {
|
|
216
|
+
hidden += 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div role="group" aria-label={filterLabel} className="tai-row">
|
|
222
|
+
{visible.map((entry) => (
|
|
223
|
+
<TagChip
|
|
224
|
+
key={entry.token}
|
|
225
|
+
entry={entry}
|
|
226
|
+
active={selectedSet.has(entry.token)}
|
|
227
|
+
onToggle={() => {
|
|
228
|
+
onToggle(entry.token);
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
231
|
+
))}
|
|
232
|
+
{hidden > 0 ? (
|
|
233
|
+
<span className="tai-chip tai-chip-static">{`+${String(hidden)} more`}</span>
|
|
234
|
+
) : null}
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A first-class folder entry — a `FolderRow` (icon + name, navigates inward)
|
|
240
|
+
* beside its optional actions, in the layout shared by the row cell and the card. */
|
|
241
|
+
function FolderEntry({
|
|
242
|
+
folder,
|
|
243
|
+
onNavigate,
|
|
244
|
+
renderFolderActions,
|
|
245
|
+
}: {
|
|
246
|
+
readonly folder: Folder;
|
|
247
|
+
readonly onNavigate: (folderId: string | null) => void;
|
|
248
|
+
readonly renderFolderActions: ((folder: Folder) => ReactNode) | undefined;
|
|
249
|
+
}): ReactNode {
|
|
250
|
+
return (
|
|
251
|
+
<div className="tai-explorer-folder">
|
|
252
|
+
<FolderRow folder={folder} onOpen={onNavigate} />
|
|
253
|
+
{renderFolderActions ? renderFolderActions(folder) : null}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function ExplorerView<T>({
|
|
259
|
+
items,
|
|
260
|
+
getItemKey,
|
|
261
|
+
getFolderId,
|
|
262
|
+
folders,
|
|
263
|
+
currentFolderId,
|
|
264
|
+
onNavigate,
|
|
265
|
+
rootLabel,
|
|
266
|
+
viewSurface,
|
|
267
|
+
label,
|
|
268
|
+
columns,
|
|
269
|
+
renderRow,
|
|
270
|
+
renderCard,
|
|
271
|
+
tags,
|
|
272
|
+
search,
|
|
273
|
+
renderFolderActions,
|
|
274
|
+
emptyStates,
|
|
275
|
+
}: ExplorerViewProps<T>): ReactNode {
|
|
276
|
+
const [viewMode, setViewMode] = useViewMode(viewSurface);
|
|
277
|
+
|
|
278
|
+
if (viewSurface === '') throw new Error('ExplorerView requires a non-empty viewSurface key.');
|
|
279
|
+
if (columns.length === 0) throw new Error('ExplorerView requires at least one column.');
|
|
280
|
+
|
|
281
|
+
// Nothing anywhere: no items and no folders. A bare empty state, no controls.
|
|
282
|
+
if (items.length === 0 && folders.length === 0) {
|
|
283
|
+
return <EmptyState {...emptyStates.empty} />;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const subfolders = childFolders(folders, currentFolderId);
|
|
287
|
+
const inFolder = items.filter((item) => getFolderId(item) === currentFolderId);
|
|
288
|
+
|
|
289
|
+
const selectedSet = tags !== undefined ? new Set(tags.selected) : undefined;
|
|
290
|
+
const query = search ? search.value.trim() : '';
|
|
291
|
+
const filtered = inFolder.filter((item) => {
|
|
292
|
+
if (tags !== undefined && selectedSet !== undefined && selectedSet.size > 0) {
|
|
293
|
+
if (!matchesSelectedTags(tags.getTags(item), selectedSet)) return false;
|
|
294
|
+
}
|
|
295
|
+
if (search !== undefined && query !== '') {
|
|
296
|
+
if (!search.matches(item, query)) return false;
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const vocabulary =
|
|
302
|
+
tags !== undefined
|
|
303
|
+
? buildTagVocabulary(inFolder, tags.getTags, tags.selected, tags.untaggedLabel)
|
|
304
|
+
: [];
|
|
305
|
+
|
|
306
|
+
const toggleTag = (token: string): void => {
|
|
307
|
+
if (tags === undefined || selectedSet === undefined) return;
|
|
308
|
+
const next = selectedSet.has(token)
|
|
309
|
+
? tags.selected.filter((t) => t !== token)
|
|
310
|
+
: [...tags.selected, token];
|
|
311
|
+
tags.onChange(next);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const hasEntries = subfolders.length > 0 || filtered.length > 0;
|
|
315
|
+
|
|
316
|
+
let body: ReactNode;
|
|
317
|
+
if (!hasEntries) {
|
|
318
|
+
// The current folder renders nothing. A filter emptied it (`inFolder` non-empty)
|
|
319
|
+
// → "no match"; otherwise the folder is genuinely empty.
|
|
320
|
+
body = (
|
|
321
|
+
<EmptyState {...(inFolder.length > 0 ? emptyStates.noMatch : emptyStates.emptyFolder)} />
|
|
322
|
+
);
|
|
323
|
+
} else if (viewMode === 'cards') {
|
|
324
|
+
body = (
|
|
325
|
+
<EntityCardGrid aria-label={label}>
|
|
326
|
+
{subfolders.map((folder) => (
|
|
327
|
+
<div role="listitem" key={`folder:${folder.id}`}>
|
|
328
|
+
<Card interactive>
|
|
329
|
+
<FolderEntry
|
|
330
|
+
folder={folder}
|
|
331
|
+
onNavigate={onNavigate}
|
|
332
|
+
renderFolderActions={renderFolderActions}
|
|
333
|
+
/>
|
|
334
|
+
</Card>
|
|
335
|
+
</div>
|
|
336
|
+
))}
|
|
337
|
+
{filtered.map((item) => (
|
|
338
|
+
<div role="listitem" key={`item:${getItemKey(item)}`}>
|
|
339
|
+
{renderCard(item)}
|
|
340
|
+
</div>
|
|
341
|
+
))}
|
|
342
|
+
</EntityCardGrid>
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
// Folder rows share the item table, spanning every column, sorted above items —
|
|
346
|
+
// so a folder-with-only-subfolders is folder ROWS, never a header-only table.
|
|
347
|
+
body = (
|
|
348
|
+
<Table>
|
|
349
|
+
<THead>
|
|
350
|
+
<TR>
|
|
351
|
+
{columns.map((column) => (
|
|
352
|
+
<TH key={column.key} numeric={column.numeric}>
|
|
353
|
+
{column.header}
|
|
354
|
+
</TH>
|
|
355
|
+
))}
|
|
356
|
+
</TR>
|
|
357
|
+
</THead>
|
|
358
|
+
<TBody>
|
|
359
|
+
{subfolders.map((folder) => (
|
|
360
|
+
<TR key={`folder:${folder.id}`}>
|
|
361
|
+
<TD colSpan={columns.length}>
|
|
362
|
+
<FolderEntry
|
|
363
|
+
folder={folder}
|
|
364
|
+
onNavigate={onNavigate}
|
|
365
|
+
renderFolderActions={renderFolderActions}
|
|
366
|
+
/>
|
|
367
|
+
</TD>
|
|
368
|
+
</TR>
|
|
369
|
+
))}
|
|
370
|
+
{filtered.map((item) => (
|
|
371
|
+
<TR key={`item:${getItemKey(item)}`}>{renderRow(item)}</TR>
|
|
372
|
+
))}
|
|
373
|
+
</TBody>
|
|
374
|
+
</Table>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div className="tai-explorer">
|
|
380
|
+
<div className="tai-explorer-controls">
|
|
381
|
+
{search !== undefined ? (
|
|
382
|
+
<span className="tai-row">
|
|
383
|
+
<SearchIcon />
|
|
384
|
+
<TextInput
|
|
385
|
+
value={search.value}
|
|
386
|
+
aria-label={search.label}
|
|
387
|
+
placeholder={search.placeholder}
|
|
388
|
+
onChange={(event) => {
|
|
389
|
+
search.onChange(event.target.value);
|
|
390
|
+
}}
|
|
391
|
+
/>
|
|
392
|
+
</span>
|
|
393
|
+
) : (
|
|
394
|
+
<span />
|
|
395
|
+
)}
|
|
396
|
+
<ViewToggle value={viewMode} onValueChange={setViewMode} aria-label={`${label} view`} />
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<FolderBreadcrumb
|
|
400
|
+
folders={folders}
|
|
401
|
+
currentFolderId={currentFolderId}
|
|
402
|
+
onNavigate={onNavigate}
|
|
403
|
+
rootLabel={rootLabel}
|
|
404
|
+
/>
|
|
405
|
+
|
|
406
|
+
{tags !== undefined && selectedSet !== undefined && vocabulary.length > 0 ? (
|
|
407
|
+
<TagFilterRow
|
|
408
|
+
vocabulary={vocabulary}
|
|
409
|
+
selectedSet={selectedSet}
|
|
410
|
+
onToggle={toggleTag}
|
|
411
|
+
filterLabel={tags.filterLabel}
|
|
412
|
+
maxVisibleTags={tags.maxVisibleTags}
|
|
413
|
+
/>
|
|
414
|
+
) : null}
|
|
415
|
+
|
|
416
|
+
{body}
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-height page mode: a page declares "I fill the viewport" and the shell
|
|
3
|
+
* gives its wrapper a viewport-height flex chain instead of the default
|
|
4
|
+
* content-sized, scrolling `.tai-page`.
|
|
5
|
+
*
|
|
6
|
+
* The signal travels page → shell through context, NOT a route handle: plugin
|
|
7
|
+
* pages all share ONE catch-all route, so a per-route handle cannot vary between
|
|
8
|
+
* them. A rendered page (feature route or plugin page alike) opts in with
|
|
9
|
+
* {@link useFillViewport}; the shell reads {@link usePageFillActive} to add the
|
|
10
|
+
* `.tai-page--fill` / `.tai-shell-main--fill` modifiers. Opt-in is ref-counted so
|
|
11
|
+
* the modifier survives a route transition (old page unmounts after the new one
|
|
12
|
+
* mounts) and clears exactly when the last fill page leaves.
|
|
13
|
+
*/
|
|
14
|
+
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
15
|
+
|
|
16
|
+
/** The registrar a fill page acquires against; each acquire returns its own
|
|
17
|
+
* one-shot release, so double-release is inert. */
|
|
18
|
+
interface PageFillRegistry {
|
|
19
|
+
readonly acquire: () => () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PageFillRegistryContext = createContext<PageFillRegistry | null>(null);
|
|
23
|
+
const PageFillActiveContext = createContext(false);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wraps the shell's routed region: holds the count of pages asking for fill mode
|
|
27
|
+
* and publishes both the registrar (for {@link useFillViewport}) and the active
|
|
28
|
+
* flag (for {@link usePageFillActive}). Renders no DOM of its own.
|
|
29
|
+
*/
|
|
30
|
+
export function PageFillProvider({ children }: { children: ReactNode }): ReactNode {
|
|
31
|
+
const [fillCount, setFillCount] = useState(0);
|
|
32
|
+
const registry = useMemo<PageFillRegistry>(
|
|
33
|
+
() => ({
|
|
34
|
+
acquire() {
|
|
35
|
+
setFillCount((count) => count + 1);
|
|
36
|
+
let released = false;
|
|
37
|
+
return () => {
|
|
38
|
+
if (released) return;
|
|
39
|
+
released = true;
|
|
40
|
+
setFillCount((count) => count - 1);
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
[],
|
|
45
|
+
);
|
|
46
|
+
return (
|
|
47
|
+
<PageFillRegistryContext.Provider value={registry}>
|
|
48
|
+
<PageFillActiveContext.Provider value={fillCount > 0}>
|
|
49
|
+
{children}
|
|
50
|
+
</PageFillActiveContext.Provider>
|
|
51
|
+
</PageFillRegistryContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Opt the current page into full-height fill mode for as long as it is mounted.
|
|
57
|
+
* The shell fills `<main>` to the viewport and lets the page's own root flex or
|
|
58
|
+
* `height: 100%` down the chain. Must be called under a {@link PageFillProvider}
|
|
59
|
+
* (the shell mounts one); outside it there is no wrapper to modify, so it throws
|
|
60
|
+
* rather than silently do nothing.
|
|
61
|
+
*/
|
|
62
|
+
export function useFillViewport(): void {
|
|
63
|
+
const registry = useContext(PageFillRegistryContext);
|
|
64
|
+
if (registry === null) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'useFillViewport must be used within a <PageFillProvider> (the shell provides it)',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
useEffect(() => registry.acquire(), [registry]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Whether any mounted page currently asks for fill mode — the shell's cue to add
|
|
73
|
+
* the fill modifiers to `<main>` and the page wrapper. */
|
|
74
|
+
export function usePageFillActive(): boolean {
|
|
75
|
+
return useContext(PageFillActiveContext);
|
|
76
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,7 @@ export type {
|
|
|
116
116
|
} from './components/primitives';
|
|
117
117
|
export { PageHeader, Page, Stack } from './components/page-header';
|
|
118
118
|
export type { PageHeaderProps, PageLayoutProps, StackProps } from './components/page-header';
|
|
119
|
+
export { PageFillProvider, useFillViewport, usePageFillActive } from './components/page-fill';
|
|
119
120
|
export { ErrorBoundary } from './components/error-boundary';
|
|
120
121
|
export type { ErrorBoundaryProps } from './components/error-boundary';
|
|
121
122
|
export { Field, useFieldControl } from './components/field';
|
|
@@ -175,6 +176,21 @@ export { ViewToggle, useViewMode } from './components/view-toggle';
|
|
|
175
176
|
export type { ViewMode, ViewToggleProps } from './components/view-toggle';
|
|
176
177
|
export { EntityCardGrid } from './components/entity-card-grid';
|
|
177
178
|
export type { EntityCardGridProps } from './components/entity-card-grid';
|
|
179
|
+
export {
|
|
180
|
+
ExplorerView,
|
|
181
|
+
buildTagVocabulary,
|
|
182
|
+
matchesSelectedTags,
|
|
183
|
+
UNTAGGED_TOKEN,
|
|
184
|
+
} from './components/explorer-view';
|
|
185
|
+
export type {
|
|
186
|
+
ExplorerViewProps,
|
|
187
|
+
ExplorerColumn,
|
|
188
|
+
ExplorerTags,
|
|
189
|
+
ExplorerSearch,
|
|
190
|
+
ExplorerEmptyState,
|
|
191
|
+
ExplorerEmptyStates,
|
|
192
|
+
TagVocabularyEntry,
|
|
193
|
+
} from './components/explorer-view';
|
|
178
194
|
export {
|
|
179
195
|
FolderBreadcrumb,
|
|
180
196
|
FolderRow,
|