@fgv/ts-app-shell 5.1.0-1

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.
Files changed (180) hide show
  1. package/README.md +26 -0
  2. package/dist/index.browser.js +3 -0
  3. package/dist/index.js +43 -0
  4. package/dist/packlets/ai-assist/index.js +6 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +219 -0
  6. package/dist/packlets/cascade/CascadeContainer.js +83 -0
  7. package/dist/packlets/cascade/ComparisonView.js +48 -0
  8. package/dist/packlets/cascade/EntityTabLayout.js +104 -0
  9. package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
  10. package/dist/packlets/cascade/index.js +37 -0
  11. package/dist/packlets/cascade/model.js +30 -0
  12. package/dist/packlets/cascade/useCascadeOps.js +206 -0
  13. package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
  14. package/dist/packlets/detail/DetailHelpers.js +103 -0
  15. package/dist/packlets/detail/index.js +6 -0
  16. package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
  17. package/dist/packlets/drop-zone/index.js +6 -0
  18. package/dist/packlets/editing/EditFieldHelpers.js +130 -0
  19. package/dist/packlets/editing/MultiActionButton.js +73 -0
  20. package/dist/packlets/editing/NumericInput.js +119 -0
  21. package/dist/packlets/editing/TypeaheadInput.js +207 -0
  22. package/dist/packlets/editing/index.js +10 -0
  23. package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
  24. package/dist/packlets/keyboard/index.js +7 -0
  25. package/dist/packlets/keyboard/registry.js +133 -0
  26. package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
  27. package/dist/packlets/messages/MessagesContext.js +76 -0
  28. package/dist/packlets/messages/MessagesLogger.js +103 -0
  29. package/dist/packlets/messages/StatusBar.js +154 -0
  30. package/dist/packlets/messages/Toast.js +68 -0
  31. package/dist/packlets/messages/index.js +11 -0
  32. package/dist/packlets/messages/model.js +56 -0
  33. package/dist/packlets/messages/useLogReporter.js +66 -0
  34. package/dist/packlets/modal/ConfirmDialog.js +78 -0
  35. package/dist/packlets/modal/Modal.js +55 -0
  36. package/dist/packlets/modal/index.js +7 -0
  37. package/dist/packlets/print/PrintEnclosure.js +60 -0
  38. package/dist/packlets/print/index.js +7 -0
  39. package/dist/packlets/print/openPrintWindow.js +112 -0
  40. package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
  41. package/dist/packlets/responsive/index.js +7 -0
  42. package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
  43. package/dist/packlets/selectors/EntityRow.js +276 -0
  44. package/dist/packlets/selectors/PreferredSelector.js +251 -0
  45. package/dist/packlets/selectors/index.js +24 -0
  46. package/dist/packlets/sidebar/CollectionSection.js +107 -0
  47. package/dist/packlets/sidebar/EntityList.js +164 -0
  48. package/dist/packlets/sidebar/FilterBar.js +42 -0
  49. package/dist/packlets/sidebar/FilterRow.js +182 -0
  50. package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
  51. package/dist/packlets/sidebar/SearchBar.js +34 -0
  52. package/dist/packlets/sidebar/SidebarLayout.js +62 -0
  53. package/dist/packlets/sidebar/index.js +12 -0
  54. package/dist/packlets/theme/ThemeProvider.js +141 -0
  55. package/dist/packlets/theme/index.js +6 -0
  56. package/dist/packlets/top-bar/ModeSelector.js +46 -0
  57. package/dist/packlets/top-bar/TabBar.js +37 -0
  58. package/dist/packlets/top-bar/index.js +7 -0
  59. package/dist/packlets/url-sync/index.js +6 -0
  60. package/dist/packlets/url-sync/useUrlSync.js +157 -0
  61. package/eslint.config.js +22 -0
  62. package/lib/index.browser.d.ts +2 -0
  63. package/lib/index.browser.js +19 -0
  64. package/lib/index.d.ts +28 -0
  65. package/lib/index.js +59 -0
  66. package/lib/packlets/ai-assist/index.d.ts +6 -0
  67. package/lib/packlets/ai-assist/index.js +11 -0
  68. package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
  69. package/lib/packlets/ai-assist/useAiAssist.js +223 -0
  70. package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
  71. package/lib/packlets/cascade/CascadeContainer.js +119 -0
  72. package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
  73. package/lib/packlets/cascade/ComparisonView.js +54 -0
  74. package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
  75. package/lib/packlets/cascade/EntityTabLayout.js +110 -0
  76. package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
  77. package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
  78. package/lib/packlets/cascade/index.d.ts +12 -0
  79. package/lib/packlets/cascade/index.js +48 -0
  80. package/lib/packlets/cascade/model.d.ts +57 -0
  81. package/lib/packlets/cascade/model.js +33 -0
  82. package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
  83. package/lib/packlets/cascade/useCascadeOps.js +209 -0
  84. package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
  85. package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
  86. package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
  87. package/lib/packlets/detail/DetailHelpers.js +113 -0
  88. package/lib/packlets/detail/index.d.ts +6 -0
  89. package/lib/packlets/detail/index.js +14 -0
  90. package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
  91. package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
  92. package/lib/packlets/drop-zone/index.d.ts +6 -0
  93. package/lib/packlets/drop-zone/index.js +10 -0
  94. package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
  95. package/lib/packlets/editing/EditFieldHelpers.js +144 -0
  96. package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
  97. package/lib/packlets/editing/MultiActionButton.js +109 -0
  98. package/lib/packlets/editing/NumericInput.d.ts +47 -0
  99. package/lib/packlets/editing/NumericInput.js +155 -0
  100. package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
  101. package/lib/packlets/editing/TypeaheadInput.js +243 -0
  102. package/lib/packlets/editing/index.d.ts +10 -0
  103. package/lib/packlets/editing/index.js +26 -0
  104. package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
  105. package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
  106. package/lib/packlets/keyboard/index.d.ts +7 -0
  107. package/lib/packlets/keyboard/index.js +15 -0
  108. package/lib/packlets/keyboard/registry.d.ts +92 -0
  109. package/lib/packlets/keyboard/registry.js +138 -0
  110. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
  111. package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
  112. package/lib/packlets/messages/MessagesContext.d.ts +40 -0
  113. package/lib/packlets/messages/MessagesContext.js +113 -0
  114. package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
  115. package/lib/packlets/messages/MessagesLogger.js +107 -0
  116. package/lib/packlets/messages/StatusBar.d.ts +22 -0
  117. package/lib/packlets/messages/StatusBar.js +190 -0
  118. package/lib/packlets/messages/Toast.d.ts +31 -0
  119. package/lib/packlets/messages/Toast.js +105 -0
  120. package/lib/packlets/messages/index.d.ts +11 -0
  121. package/lib/packlets/messages/index.js +24 -0
  122. package/lib/packlets/messages/model.d.ts +59 -0
  123. package/lib/packlets/messages/model.js +61 -0
  124. package/lib/packlets/messages/useLogReporter.d.ts +22 -0
  125. package/lib/packlets/messages/useLogReporter.js +69 -0
  126. package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
  127. package/lib/packlets/modal/ConfirmDialog.js +114 -0
  128. package/lib/packlets/modal/Modal.d.ts +22 -0
  129. package/lib/packlets/modal/Modal.js +91 -0
  130. package/lib/packlets/modal/index.d.ts +7 -0
  131. package/lib/packlets/modal/index.js +12 -0
  132. package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
  133. package/lib/packlets/print/PrintEnclosure.js +96 -0
  134. package/lib/packlets/print/index.d.ts +7 -0
  135. package/lib/packlets/print/index.js +12 -0
  136. package/lib/packlets/print/openPrintWindow.d.ts +35 -0
  137. package/lib/packlets/print/openPrintWindow.js +118 -0
  138. package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
  139. package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
  140. package/lib/packlets/responsive/index.d.ts +7 -0
  141. package/lib/packlets/responsive/index.js +13 -0
  142. package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
  143. package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
  144. package/lib/packlets/selectors/EntityRow.d.ts +45 -0
  145. package/lib/packlets/selectors/EntityRow.js +315 -0
  146. package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
  147. package/lib/packlets/selectors/PreferredSelector.js +287 -0
  148. package/lib/packlets/selectors/index.d.ts +5 -0
  149. package/lib/packlets/selectors/index.js +29 -0
  150. package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
  151. package/lib/packlets/sidebar/CollectionSection.js +143 -0
  152. package/lib/packlets/sidebar/EntityList.d.ts +105 -0
  153. package/lib/packlets/sidebar/EntityList.js +200 -0
  154. package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
  155. package/lib/packlets/sidebar/FilterBar.js +48 -0
  156. package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
  157. package/lib/packlets/sidebar/FilterRow.js +218 -0
  158. package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
  159. package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
  160. package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
  161. package/lib/packlets/sidebar/SearchBar.js +40 -0
  162. package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
  163. package/lib/packlets/sidebar/SidebarLayout.js +98 -0
  164. package/lib/packlets/sidebar/index.d.ts +12 -0
  165. package/lib/packlets/sidebar/index.js +22 -0
  166. package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
  167. package/lib/packlets/theme/ThemeProvider.js +178 -0
  168. package/lib/packlets/theme/index.d.ts +6 -0
  169. package/lib/packlets/theme/index.js +11 -0
  170. package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
  171. package/lib/packlets/top-bar/ModeSelector.js +52 -0
  172. package/lib/packlets/top-bar/TabBar.d.ts +31 -0
  173. package/lib/packlets/top-bar/TabBar.js +43 -0
  174. package/lib/packlets/top-bar/index.d.ts +7 -0
  175. package/lib/packlets/top-bar/index.js +12 -0
  176. package/lib/packlets/url-sync/index.d.ts +6 -0
  177. package/lib/packlets/url-sync/index.js +12 -0
  178. package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
  179. package/lib/packlets/url-sync/useUrlSync.js +162 -0
  180. package/package.json +82 -0
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ /**
3
+ * A single selectable filter option.
4
+ * Generic over the value type so apps can use string IDs, enums, etc.
5
+ * @public
6
+ */
7
+ export interface IFilterOption<TValue> {
8
+ /** Unique value for this option */
9
+ readonly value: TValue;
10
+ /** Display label */
11
+ readonly label: string;
12
+ /** Optional count badge */
13
+ readonly count?: number;
14
+ }
15
+ /**
16
+ * Props for the FilterRow component.
17
+ * @public
18
+ */
19
+ export interface IFilterRowProps<TValue> {
20
+ /** Filter label (e.g., 'Categories', 'Tags') */
21
+ readonly label: string;
22
+ /** Available options */
23
+ readonly options: ReadonlyArray<IFilterOption<TValue>>;
24
+ /** Currently selected values */
25
+ readonly selected: ReadonlyArray<TValue>;
26
+ /** Callback when selection changes */
27
+ readonly onSelectionChange: (selected: ReadonlyArray<TValue>) => void;
28
+ /** Whether multiple selections are allowed (default: true) */
29
+ readonly multiple?: boolean;
30
+ /** Custom equality check for values (default: ===) */
31
+ readonly isEqual?: (a: TValue, b: TValue) => boolean;
32
+ }
33
+ /**
34
+ * A compact filter row with a right-side flyout overlay for selecting filter values.
35
+ *
36
+ * Collapsed: shows the label, status summary text, and a `\u203A` chevron.
37
+ * Expanded: a flyout panel slides out to the right of the sidebar, overlaying the main pane.
38
+ *
39
+ * @public
40
+ */
41
+ export declare function FilterRow<TValue>(props: IFilterRowProps<TValue>): React.ReactElement;
42
+ //# sourceMappingURL=FilterRow.d.ts.map
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.FilterRow = FilterRow;
58
+ const react_1 = __importStar(require("react"));
59
+ const responsive_1 = require("../responsive");
60
+ // ============================================================================
61
+ // FilterRow Component
62
+ // ============================================================================
63
+ /** Minimum number of options before showing the search-within-filter input */
64
+ const SEARCH_THRESHOLD = 8;
65
+ /**
66
+ * A compact filter row with a right-side flyout overlay for selecting filter values.
67
+ *
68
+ * Collapsed: shows the label, status summary text, and a `\u203A` chevron.
69
+ * Expanded: a flyout panel slides out to the right of the sidebar, overlaying the main pane.
70
+ *
71
+ * @public
72
+ */
73
+ function FilterRow(props) {
74
+ const { label, options, selected, onSelectionChange, multiple = true, isEqual = defaultIsEqual } = props;
75
+ const { layoutMode } = (0, responsive_1.useResponsive)();
76
+ const isMobile = layoutMode === 'mobile';
77
+ const [open, setOpen] = (0, react_1.useState)(false);
78
+ const [flyoutSearch, setFlyoutSearch] = (0, react_1.useState)('');
79
+ const rowRef = (0, react_1.useRef)(null);
80
+ const flyoutRef = (0, react_1.useRef)(null);
81
+ // Close flyout on outside click (check both row and flyout)
82
+ (0, react_1.useEffect)(() => {
83
+ if (!open) {
84
+ return undefined;
85
+ }
86
+ const handleClick = (e) => {
87
+ const target = e.target;
88
+ if (rowRef.current &&
89
+ !rowRef.current.contains(target) &&
90
+ flyoutRef.current &&
91
+ !flyoutRef.current.contains(target)) {
92
+ setOpen(false);
93
+ setFlyoutSearch('');
94
+ }
95
+ };
96
+ document.addEventListener('mousedown', handleClick);
97
+ return () => document.removeEventListener('mousedown', handleClick);
98
+ }, [open]);
99
+ // Close flyout on Escape
100
+ (0, react_1.useEffect)(() => {
101
+ if (!open) {
102
+ return undefined;
103
+ }
104
+ const handleKey = (e) => {
105
+ if (e.key === 'Escape') {
106
+ setOpen(false);
107
+ setFlyoutSearch('');
108
+ }
109
+ };
110
+ document.addEventListener('keydown', handleKey);
111
+ return () => document.removeEventListener('keydown', handleKey);
112
+ }, [open]);
113
+ const isSelected = (0, react_1.useCallback)((value) => selected.some((s) => isEqual(s, value)), [selected, isEqual]);
114
+ const toggleValue = (0, react_1.useCallback)((value) => {
115
+ if (multiple) {
116
+ if (isSelected(value)) {
117
+ onSelectionChange(selected.filter((s) => !isEqual(s, value)));
118
+ }
119
+ else {
120
+ onSelectionChange([...selected, value]);
121
+ }
122
+ }
123
+ else {
124
+ // Single-select: toggle off if already selected, otherwise select
125
+ if (isSelected(value)) {
126
+ onSelectionChange([]);
127
+ }
128
+ else {
129
+ onSelectionChange([value]);
130
+ }
131
+ }
132
+ }, [multiple, selected, onSelectionChange, isSelected, isEqual]);
133
+ const clearSelection = (0, react_1.useCallback)((e) => {
134
+ e.stopPropagation();
135
+ onSelectionChange([]);
136
+ }, [onSelectionChange]);
137
+ const activeCount = selected.length;
138
+ // Status summary text for the collapsed row
139
+ const statusText = (0, react_1.useMemo)(() => {
140
+ if (activeCount === 0) {
141
+ return 'All';
142
+ }
143
+ if (activeCount === 1) {
144
+ // Show the label of the single selected option
145
+ const sel = selected[0];
146
+ const match = options.find((o) => isEqual(o.value, sel));
147
+ return match ? match.label : '1 selected';
148
+ }
149
+ return `${activeCount} selected`;
150
+ }, [activeCount, selected, options, isEqual]);
151
+ // Filter options by flyout search
152
+ const filteredOptions = (0, react_1.useMemo)(() => {
153
+ const q = flyoutSearch.trim().toLowerCase();
154
+ if (q.length === 0) {
155
+ return options;
156
+ }
157
+ return options.filter((o) => o.label.toLowerCase().includes(q));
158
+ }, [options, flyoutSearch]);
159
+ // Compute flyout position: anchored to the right edge of the sidebar row, aligned vertically
160
+ const [flyoutStyle, setFlyoutStyle] = (0, react_1.useState)({});
161
+ (0, react_1.useEffect)(() => {
162
+ if (!open || !rowRef.current) {
163
+ return;
164
+ }
165
+ const rect = rowRef.current.getBoundingClientRect();
166
+ // Find the sidebar container (the <aside> ancestor)
167
+ let sidebar = rowRef.current;
168
+ while (sidebar && sidebar.tagName !== 'ASIDE') {
169
+ sidebar = sidebar.parentElement;
170
+ }
171
+ const rightEdge = sidebar ? sidebar.getBoundingClientRect().right : rect.right;
172
+ // Flyout appears at the right edge of the sidebar, vertically aligned with the row
173
+ const maxHeight = Math.min(400, window.innerHeight - rect.top - 8);
174
+ setFlyoutStyle({
175
+ position: 'fixed',
176
+ top: rect.top,
177
+ left: rightEdge,
178
+ width: 280,
179
+ maxHeight,
180
+ zIndex: 50
181
+ });
182
+ }, [open]);
183
+ const handleToggle = (0, react_1.useCallback)(() => {
184
+ setOpen((prev) => {
185
+ if (prev) {
186
+ setFlyoutSearch('');
187
+ }
188
+ return !prev;
189
+ });
190
+ }, []);
191
+ return (react_1.default.createElement("div", { ref: rowRef },
192
+ react_1.default.createElement("button", { onClick: handleToggle, className: `flex items-center justify-between w-full px-3 text-sm hover:bg-hover transition-colors ${isMobile ? 'py-3' : 'py-1.5'} ${activeCount > 0 ? 'text-brand-primary font-medium' : 'text-secondary'}` },
193
+ react_1.default.createElement("span", { className: "truncate" }, label),
194
+ react_1.default.createElement("span", { className: "flex items-center gap-1.5 shrink-0 ml-2" },
195
+ activeCount > 0 && (react_1.default.createElement("span", { onClick: clearSelection, className: "text-muted hover:text-secondary cursor-pointer text-xs", role: "button", "aria-label": `Clear ${label} filter` }, '\u00D7')),
196
+ react_1.default.createElement("span", { className: `text-xs ${activeCount > 0 ? 'text-brand-accent' : 'text-muted'}` }, statusText),
197
+ react_1.default.createElement("span", { className: "text-muted text-xs" }, '\u203A'))),
198
+ open && (react_1.default.createElement("div", { ref: flyoutRef, className: "bg-surface border border-border rounded-r-lg shadow-xl overflow-hidden flex flex-col", style: flyoutStyle },
199
+ react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-2 border-b border-border-subtle bg-surface-alt" },
200
+ react_1.default.createElement("span", { className: "text-xs font-medium text-secondary" }, label),
201
+ activeCount > 0 && (react_1.default.createElement("button", { onClick: () => onSelectionChange([]), className: "text-xs text-brand-accent hover:text-brand-primary" }, "Clear"))),
202
+ options.length >= SEARCH_THRESHOLD && (react_1.default.createElement("div", { className: "px-3 py-1.5 border-b border-border-subtle" },
203
+ react_1.default.createElement("input", { type: "text", value: flyoutSearch, onChange: (e) => setFlyoutSearch(e.target.value), placeholder: `Search ${label.toLowerCase()}...`, className: "w-full px-2 py-1 text-xs border border-border rounded bg-surface text-primary focus:outline-none focus:border-focus-ring", autoFocus: true }))),
204
+ react_1.default.createElement("div", { className: "flex-1 overflow-y-auto" }, filteredOptions.length === 0 ? (react_1.default.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, options.length === 0 ? 'No options available' : 'No matches')) : (filteredOptions.map((option, idx) => {
205
+ const checked = isSelected(option.value);
206
+ return (react_1.default.createElement("button", { key: idx, onClick: () => toggleValue(option.value), className: `flex items-center gap-2 w-full px-3 text-sm text-left hover:bg-hover transition-colors ${isMobile ? 'py-2.5' : 'py-1.5'} ${checked ? 'text-brand-primary font-medium' : 'text-secondary'}` },
207
+ react_1.default.createElement("span", { className: `inline-flex items-center justify-center w-4 h-4 border rounded ${multiple ? 'rounded' : 'rounded-full'} ${checked ? 'bg-brand-accent border-brand-accent text-white' : 'border-border'}` }, checked && react_1.default.createElement("span", { className: "text-[10px]" }, '\u2713')),
208
+ react_1.default.createElement("span", { className: "flex-1 truncate" }, option.label),
209
+ option.count !== undefined && react_1.default.createElement("span", { className: "text-xs text-muted" }, option.count)));
210
+ })))))));
211
+ }
212
+ // ============================================================================
213
+ // Helpers
214
+ // ============================================================================
215
+ function defaultIsEqual(a, b) {
216
+ return a === b;
217
+ }
218
+ //# sourceMappingURL=FilterRow.js.map
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import type { IEntityDescriptor, IEmptyStateConfig } from './EntityList';
3
+ /**
4
+ * Extends {@link IEntityDescriptor} with grouping support.
5
+ * @public
6
+ */
7
+ export interface IEntityGroupDescriptor<TEntity, TId extends string = string> extends IEntityDescriptor<TEntity, TId> {
8
+ /** Extract a group key from an entity. Entities with the same key are grouped together. */
9
+ readonly getGroupKey: (entity: TEntity) => string;
10
+ /** Extract a display label for a group. Called on the first entity in each group. */
11
+ readonly getGroupLabel: (entity: TEntity) => string;
12
+ /** Optional sort comparator for group ordering. Receives group keys. */
13
+ readonly compareGroups?: (a: string, b: string) => number;
14
+ }
15
+ /**
16
+ * Props for the {@link GroupedEntityList} component.
17
+ * @public
18
+ */
19
+ export interface IGroupedEntityListProps<TEntity, TId extends string = string> {
20
+ /** The entities to display (pre-sorted within groups by the caller). */
21
+ readonly entities: ReadonlyArray<TEntity>;
22
+ /** Descriptor for extracting display and grouping properties. */
23
+ readonly descriptor: IEntityGroupDescriptor<TEntity, TId>;
24
+ /** Currently selected entity ID (if any). */
25
+ readonly selectedId?: TId;
26
+ /** Callback when an entity is selected (browse — list stays open). */
27
+ readonly onSelect: (id: TId) => void;
28
+ /** Callback when the user drills into the selected entity (Enter/Arrow Right — collapses list). */
29
+ readonly onDrill?: () => void;
30
+ /** Whether compare mode is active. */
31
+ readonly compareMode?: boolean;
32
+ /** Entity IDs currently checked for comparison. */
33
+ readonly checkedIds?: ReadonlySet<string>;
34
+ /** Callback to toggle an entity ID in/out of compare selection. */
35
+ readonly onCheckedChange?: (id: TId) => void;
36
+ /** Empty state configuration. */
37
+ readonly emptyState?: IEmptyStateConfig;
38
+ /** Callback to toggle compare mode. */
39
+ readonly onToggleCompare?: () => void;
40
+ /** Number of items selected for comparison. */
41
+ readonly compareCount?: number;
42
+ /** Callback to start comparison view. */
43
+ readonly onStartComparison?: () => void;
44
+ /** Callback to delete an entity. */
45
+ readonly onDelete?: (id: TId) => void;
46
+ /** Predicate to control per-entity delete button visibility. */
47
+ readonly canDelete?: (id: TId) => boolean;
48
+ }
49
+ /**
50
+ * Entity list with sticky group headers.
51
+ *
52
+ * Groups entities by a key extracted via the descriptor, renders a sticky
53
+ * header per group, and delegates item rendering to the same visual pattern
54
+ * as {@link EntityList}.
55
+ *
56
+ * @public
57
+ */
58
+ export declare function GroupedEntityList<TEntity, TId extends string = string>(props: IGroupedEntityListProps<TEntity, TId>): React.ReactElement;
59
+ //# sourceMappingURL=GroupedEntityList.d.ts.map
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.GroupedEntityList = GroupedEntityList;
58
+ const react_1 = __importStar(require("react"));
59
+ function GroupedEntityRowInner(props) {
60
+ var _a, _b;
61
+ const { entity, descriptor, selectedId, compareMode, checkedIds, onSelect, onDrill, onCheckedChange, onDelete, canDelete } = props;
62
+ const id = descriptor.getId(entity);
63
+ const label = descriptor.getLabel(entity);
64
+ const sublabel = (_a = descriptor.getSublabel) === null || _a === void 0 ? void 0 : _a.call(descriptor, entity);
65
+ const status = (_b = descriptor.getStatus) === null || _b === void 0 ? void 0 : _b.call(descriptor, entity);
66
+ const isSelected = id === selectedId;
67
+ const isChecked = compareMode === true && checkedIds !== undefined && checkedIds.has(id);
68
+ return (react_1.default.createElement("div", { className: `group flex items-center gap-2 w-full border-b border-border-subtle transition-colors ${isChecked
69
+ ? 'bg-selected border-l-2 border-l-selected-border'
70
+ : isSelected && !compareMode
71
+ ? 'bg-selected border-l-2 border-l-selected-border'
72
+ : 'hover:bg-hover border-l-2 border-l-transparent'}` },
73
+ react_1.default.createElement("button", { "data-entity-id": id, onClick: () => {
74
+ if (compareMode && onCheckedChange) {
75
+ onCheckedChange(id);
76
+ }
77
+ else if (isSelected && onDrill) {
78
+ onDrill();
79
+ }
80
+ else {
81
+ onSelect(id);
82
+ }
83
+ }, className: "flex items-center gap-2 flex-1 min-w-0 px-3 py-2 text-left" },
84
+ compareMode && (react_1.default.createElement("span", { className: `flex items-center justify-center w-4 h-4 rounded border shrink-0 transition-colors ${isChecked ? 'bg-brand-accent border-brand-accent text-white' : 'border-border bg-surface'}` }, isChecked && (react_1.default.createElement("svg", { className: "w-3 h-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 },
85
+ react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))))),
86
+ react_1.default.createElement("div", { className: "flex-1 min-w-0" },
87
+ react_1.default.createElement("div", { className: `text-sm truncate ${(isSelected && !compareMode) || isChecked ? 'font-medium text-brand-primary' : 'text-primary'}` }, label),
88
+ sublabel && react_1.default.createElement("div", { className: "text-xs text-muted truncate mt-0.5" }, sublabel)),
89
+ status && (react_1.default.createElement("span", { className: "flex items-center gap-1 shrink-0 mt-0.5" },
90
+ react_1.default.createElement("span", { className: `w-2 h-2 rounded-full ${status.colorClass}` }),
91
+ react_1.default.createElement("span", { className: "text-xs text-muted" }, status.label)))),
92
+ onDelete && !compareMode && (!canDelete || canDelete(id)) && (react_1.default.createElement("button", { onClick: (e) => {
93
+ e.stopPropagation();
94
+ onDelete(id);
95
+ }, className: "shrink-0 mr-1 w-6 h-6 flex items-center justify-center text-faint hover:text-status-error-icon opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity rounded", title: `Delete ${label}`, "aria-label": `Delete ${label}` },
96
+ react_1.default.createElement("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
97
+ react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }))))));
98
+ }
99
+ const GroupedEntityRow = react_1.default.memo(GroupedEntityRowInner);
100
+ // ============================================================================
101
+ // GroupedEntityList Component
102
+ // ============================================================================
103
+ /**
104
+ * Entity list with sticky group headers.
105
+ *
106
+ * Groups entities by a key extracted via the descriptor, renders a sticky
107
+ * header per group, and delegates item rendering to the same visual pattern
108
+ * as {@link EntityList}.
109
+ *
110
+ * @public
111
+ */
112
+ function GroupedEntityList(props) {
113
+ const { entities, descriptor, selectedId, onSelect, onDrill, compareMode, checkedIds, onCheckedChange, emptyState, onDelete, canDelete } = props;
114
+ const listRef = (0, react_1.useRef)(null);
115
+ // Build groups and flat entity list for keyboard navigation
116
+ const { groups, flatEntities } = (0, react_1.useMemo)(() => {
117
+ const groupMap = new Map();
118
+ for (const entity of entities) {
119
+ const key = descriptor.getGroupKey(entity);
120
+ let group = groupMap.get(key);
121
+ if (!group) {
122
+ group = { label: descriptor.getGroupLabel(entity), items: [] };
123
+ groupMap.set(key, group);
124
+ }
125
+ group.items.push(entity);
126
+ }
127
+ const sortedKeys = Array.from(groupMap.keys());
128
+ if (descriptor.compareGroups) {
129
+ const cmp = descriptor.compareGroups;
130
+ sortedKeys.sort((a, b) => cmp(a, b));
131
+ }
132
+ const builtGroups = [];
133
+ const flat = [];
134
+ for (const key of sortedKeys) {
135
+ const group = groupMap.get(key);
136
+ builtGroups.push({ key, label: group.label, items: group.items });
137
+ flat.push(...group.items);
138
+ }
139
+ return { groups: builtGroups, flatEntities: flat };
140
+ }, [entities, descriptor]);
141
+ // Scroll selected item into view
142
+ (0, react_1.useEffect)(() => {
143
+ if (selectedId && listRef.current) {
144
+ const selectedButton = listRef.current.querySelector(`[data-entity-id="${selectedId}"]`);
145
+ if (selectedButton) {
146
+ selectedButton.scrollIntoView({ block: 'nearest' });
147
+ }
148
+ }
149
+ }, [selectedId]);
150
+ const selectedIndex = flatEntities.findIndex((e) => descriptor.getId(e) === selectedId);
151
+ const handleKeyDown = (0, react_1.useCallback)((e) => {
152
+ switch (e.key) {
153
+ case 'ArrowDown': {
154
+ e.preventDefault();
155
+ const nextIndex = selectedIndex < flatEntities.length - 1 ? selectedIndex + 1 : 0;
156
+ onSelect(descriptor.getId(flatEntities[nextIndex]));
157
+ break;
158
+ }
159
+ case 'ArrowUp': {
160
+ e.preventDefault();
161
+ const prevIndex = selectedIndex > 0 ? selectedIndex - 1 : flatEntities.length - 1;
162
+ onSelect(descriptor.getId(flatEntities[prevIndex]));
163
+ break;
164
+ }
165
+ case 'Enter':
166
+ case 'ArrowRight': {
167
+ if (selectedId !== undefined && onDrill) {
168
+ e.preventDefault();
169
+ onDrill();
170
+ }
171
+ break;
172
+ }
173
+ default:
174
+ break;
175
+ }
176
+ }, [flatEntities, descriptor, selectedId, selectedIndex, onSelect, onDrill]);
177
+ // Empty state
178
+ if (entities.length === 0 && emptyState) {
179
+ return (react_1.default.createElement("div", { className: "flex flex-col items-center justify-center flex-1 p-6 text-center" },
180
+ react_1.default.createElement("h3", { className: "text-sm font-medium text-secondary mb-1" }, emptyState.title),
181
+ react_1.default.createElement("p", { className: "text-xs text-muted mb-4" }, emptyState.description),
182
+ emptyState.action && (react_1.default.createElement("button", { onClick: emptyState.action.onClick, className: "px-3 py-1.5 text-xs font-medium text-white bg-brand-accent rounded-md hover:bg-brand-primary transition-colors" }, emptyState.action.label))));
183
+ }
184
+ return (react_1.default.createElement("div", { className: "flex flex-col flex-1 overflow-hidden", onKeyDown: handleKeyDown },
185
+ flatEntities.length > 0 && (react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border-subtle" },
186
+ react_1.default.createElement("span", { className: "text-xs text-muted" },
187
+ flatEntities.length,
188
+ " item",
189
+ flatEntities.length !== 1 ? 's' : '',
190
+ compareMode && props.compareCount !== undefined && props.compareCount > 0 && (react_1.default.createElement("span", { className: "ml-1.5 text-brand-accent" },
191
+ "\u00B7 ",
192
+ props.compareCount,
193
+ " selected"))),
194
+ react_1.default.createElement("div", { className: "flex items-center gap-1" },
195
+ compareMode &&
196
+ props.onStartComparison &&
197
+ props.compareCount !== undefined &&
198
+ props.compareCount >= 2 && (react_1.default.createElement("button", { onClick: (e) => {
199
+ var _a;
200
+ e.stopPropagation();
201
+ (_a = props.onStartComparison) === null || _a === void 0 ? void 0 : _a.call(props);
202
+ }, className: "px-2 py-0.5 text-[11px] rounded border transition-colors bg-brand-primary text-white border-brand-primary hover:bg-brand-primary/90" }, "Compare Now")),
203
+ props.onToggleCompare && (react_1.default.createElement("button", { onClick: (e) => {
204
+ var _a;
205
+ e.stopPropagation();
206
+ (_a = props.onToggleCompare) === null || _a === void 0 ? void 0 : _a.call(props);
207
+ }, className: `px-2 py-0.5 text-[11px] rounded border transition-colors ${compareMode
208
+ ? 'bg-brand-accent text-white border-brand-accent'
209
+ : 'bg-surface text-muted border-border hover:border-brand-accent hover:text-brand-accent'}` }, compareMode ? 'Cancel' : 'Compare'))))),
210
+ react_1.default.createElement("div", { ref: listRef, className: "flex-1 overflow-y-auto" }, groups.map((group) => (react_1.default.createElement("div", { key: group.key },
211
+ react_1.default.createElement("div", { className: "px-3 py-1.5 text-xs font-medium text-muted uppercase tracking-wider bg-surface-alt border-b border-border-subtle sticky top-0 z-10" },
212
+ group.label,
213
+ react_1.default.createElement("span", { className: "text-muted normal-case font-normal ml-1" },
214
+ "(",
215
+ group.items.length,
216
+ ")")),
217
+ group.items.map((entity) => (react_1.default.createElement(GroupedEntityRow, { key: descriptor.getId(entity), entity: entity, descriptor: descriptor, selectedId: selectedId, compareMode: compareMode, checkedIds: checkedIds, onSelect: onSelect, onDrill: onDrill, onCheckedChange: onCheckedChange, onDelete: onDelete, canDelete: canDelete })))))))));
218
+ }
219
+ //# sourceMappingURL=GroupedEntityList.js.map
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ /**
3
+ * Props for the SearchBar component.
4
+ * @public
5
+ */
6
+ export interface ISearchBarProps {
7
+ /** Current search query */
8
+ readonly value: string;
9
+ /** Callback when the search query changes */
10
+ readonly onChange: (value: string) => void;
11
+ /** Placeholder text (default: 'Search...') */
12
+ readonly placeholder?: string;
13
+ }
14
+ /**
15
+ * Search input bar for the sidebar.
16
+ * @public
17
+ */
18
+ export declare function SearchBar(props: ISearchBarProps): React.ReactElement;
19
+ //# sourceMappingURL=SearchBar.d.ts.map
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.SearchBar = SearchBar;
28
+ const react_1 = __importDefault(require("react"));
29
+ /**
30
+ * Search input bar for the sidebar.
31
+ * @public
32
+ */
33
+ function SearchBar(props) {
34
+ const { value, onChange, placeholder = 'Search...' } = props;
35
+ return (react_1.default.createElement("div", { className: "px-3 py-2" },
36
+ react_1.default.createElement("div", { className: "relative" },
37
+ react_1.default.createElement("input", { type: "text", value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, className: "w-full pl-3 pr-8 py-1.5 text-sm border border-border rounded-md bg-surface text-primary focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring" }),
38
+ value.length > 0 && (react_1.default.createElement("button", { onClick: () => onChange(''), className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted hover:text-secondary text-xs", "aria-label": "Clear search" }, "\u00D7")))));
39
+ }
40
+ //# sourceMappingURL=SearchBar.js.map
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ /**
3
+ * Props for the SidebarLayout component.
4
+ * @public
5
+ */
6
+ export interface ISidebarLayoutProps {
7
+ /** Sidebar content (filter bar, collection section, etc.) */
8
+ readonly sidebar: React.ReactNode;
9
+ /** Main content area */
10
+ readonly children: React.ReactNode;
11
+ /** Sidebar width in CSS units (default: '280px') */
12
+ readonly sidebarWidth?: string;
13
+ /** Whether the sidebar drawer is open (compact/mobile only). */
14
+ readonly isSidebarOpen?: boolean;
15
+ /** Called when the drawer should close (backdrop click, etc.). */
16
+ readonly onSidebarClose?: () => void;
17
+ }
18
+ /**
19
+ * Layout component that renders a persistent left sidebar alongside a main content area.
20
+ *
21
+ * On `full` layout mode, the sidebar is a fixed-width panel.
22
+ * On `compact` and `mobile` layout modes, the sidebar becomes a slide-out drawer
23
+ * controlled by {@link ISidebarLayoutProps.isSidebarOpen | isSidebarOpen} and
24
+ * {@link ISidebarLayoutProps.onSidebarClose | onSidebarClose}.
25
+ * @public
26
+ */
27
+ export declare function SidebarLayout(props: ISidebarLayoutProps): React.ReactElement;
28
+ //# sourceMappingURL=SidebarLayout.d.ts.map