@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.
@@ -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":"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"}
@@ -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';
@@ -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.5.3",
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.5.3"
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,