@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.
- package/README.md +26 -0
- package/dist/index.browser.js +3 -0
- package/dist/index.js +43 -0
- package/dist/packlets/ai-assist/index.js +6 -0
- package/dist/packlets/ai-assist/useAiAssist.js +219 -0
- package/dist/packlets/cascade/CascadeContainer.js +83 -0
- package/dist/packlets/cascade/ComparisonView.js +48 -0
- package/dist/packlets/cascade/EntityTabLayout.js +104 -0
- package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
- package/dist/packlets/cascade/index.js +37 -0
- package/dist/packlets/cascade/model.js +30 -0
- package/dist/packlets/cascade/useCascadeOps.js +206 -0
- package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
- package/dist/packlets/detail/DetailHelpers.js +103 -0
- package/dist/packlets/detail/index.js +6 -0
- package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
- package/dist/packlets/drop-zone/index.js +6 -0
- package/dist/packlets/editing/EditFieldHelpers.js +130 -0
- package/dist/packlets/editing/MultiActionButton.js +73 -0
- package/dist/packlets/editing/NumericInput.js +119 -0
- package/dist/packlets/editing/TypeaheadInput.js +207 -0
- package/dist/packlets/editing/index.js +10 -0
- package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
- package/dist/packlets/keyboard/index.js +7 -0
- package/dist/packlets/keyboard/registry.js +133 -0
- package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
- package/dist/packlets/messages/MessagesContext.js +76 -0
- package/dist/packlets/messages/MessagesLogger.js +103 -0
- package/dist/packlets/messages/StatusBar.js +154 -0
- package/dist/packlets/messages/Toast.js +68 -0
- package/dist/packlets/messages/index.js +11 -0
- package/dist/packlets/messages/model.js +56 -0
- package/dist/packlets/messages/useLogReporter.js +66 -0
- package/dist/packlets/modal/ConfirmDialog.js +78 -0
- package/dist/packlets/modal/Modal.js +55 -0
- package/dist/packlets/modal/index.js +7 -0
- package/dist/packlets/print/PrintEnclosure.js +60 -0
- package/dist/packlets/print/index.js +7 -0
- package/dist/packlets/print/openPrintWindow.js +112 -0
- package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
- package/dist/packlets/responsive/index.js +7 -0
- package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
- package/dist/packlets/selectors/EntityRow.js +276 -0
- package/dist/packlets/selectors/PreferredSelector.js +251 -0
- package/dist/packlets/selectors/index.js +24 -0
- package/dist/packlets/sidebar/CollectionSection.js +107 -0
- package/dist/packlets/sidebar/EntityList.js +164 -0
- package/dist/packlets/sidebar/FilterBar.js +42 -0
- package/dist/packlets/sidebar/FilterRow.js +182 -0
- package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
- package/dist/packlets/sidebar/SearchBar.js +34 -0
- package/dist/packlets/sidebar/SidebarLayout.js +62 -0
- package/dist/packlets/sidebar/index.js +12 -0
- package/dist/packlets/theme/ThemeProvider.js +141 -0
- package/dist/packlets/theme/index.js +6 -0
- package/dist/packlets/top-bar/ModeSelector.js +46 -0
- package/dist/packlets/top-bar/TabBar.js +37 -0
- package/dist/packlets/top-bar/index.js +7 -0
- package/dist/packlets/url-sync/index.js +6 -0
- package/dist/packlets/url-sync/useUrlSync.js +157 -0
- package/eslint.config.js +22 -0
- package/lib/index.browser.d.ts +2 -0
- package/lib/index.browser.js +19 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.js +59 -0
- package/lib/packlets/ai-assist/index.d.ts +6 -0
- package/lib/packlets/ai-assist/index.js +11 -0
- package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
- package/lib/packlets/ai-assist/useAiAssist.js +223 -0
- package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
- package/lib/packlets/cascade/CascadeContainer.js +119 -0
- package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
- package/lib/packlets/cascade/ComparisonView.js +54 -0
- package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
- package/lib/packlets/cascade/EntityTabLayout.js +110 -0
- package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
- package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
- package/lib/packlets/cascade/index.d.ts +12 -0
- package/lib/packlets/cascade/index.js +48 -0
- package/lib/packlets/cascade/model.d.ts +57 -0
- package/lib/packlets/cascade/model.js +33 -0
- package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
- package/lib/packlets/cascade/useCascadeOps.js +209 -0
- package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
- package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
- package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
- package/lib/packlets/detail/DetailHelpers.js +113 -0
- package/lib/packlets/detail/index.d.ts +6 -0
- package/lib/packlets/detail/index.js +14 -0
- package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
- package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
- package/lib/packlets/drop-zone/index.d.ts +6 -0
- package/lib/packlets/drop-zone/index.js +10 -0
- package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
- package/lib/packlets/editing/EditFieldHelpers.js +144 -0
- package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
- package/lib/packlets/editing/MultiActionButton.js +109 -0
- package/lib/packlets/editing/NumericInput.d.ts +47 -0
- package/lib/packlets/editing/NumericInput.js +155 -0
- package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
- package/lib/packlets/editing/TypeaheadInput.js +243 -0
- package/lib/packlets/editing/index.d.ts +10 -0
- package/lib/packlets/editing/index.js +26 -0
- package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
- package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
- package/lib/packlets/keyboard/index.d.ts +7 -0
- package/lib/packlets/keyboard/index.js +15 -0
- package/lib/packlets/keyboard/registry.d.ts +92 -0
- package/lib/packlets/keyboard/registry.js +138 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
- package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
- package/lib/packlets/messages/MessagesContext.d.ts +40 -0
- package/lib/packlets/messages/MessagesContext.js +113 -0
- package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
- package/lib/packlets/messages/MessagesLogger.js +107 -0
- package/lib/packlets/messages/StatusBar.d.ts +22 -0
- package/lib/packlets/messages/StatusBar.js +190 -0
- package/lib/packlets/messages/Toast.d.ts +31 -0
- package/lib/packlets/messages/Toast.js +105 -0
- package/lib/packlets/messages/index.d.ts +11 -0
- package/lib/packlets/messages/index.js +24 -0
- package/lib/packlets/messages/model.d.ts +59 -0
- package/lib/packlets/messages/model.js +61 -0
- package/lib/packlets/messages/useLogReporter.d.ts +22 -0
- package/lib/packlets/messages/useLogReporter.js +69 -0
- package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
- package/lib/packlets/modal/ConfirmDialog.js +114 -0
- package/lib/packlets/modal/Modal.d.ts +22 -0
- package/lib/packlets/modal/Modal.js +91 -0
- package/lib/packlets/modal/index.d.ts +7 -0
- package/lib/packlets/modal/index.js +12 -0
- package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
- package/lib/packlets/print/PrintEnclosure.js +96 -0
- package/lib/packlets/print/index.d.ts +7 -0
- package/lib/packlets/print/index.js +12 -0
- package/lib/packlets/print/openPrintWindow.d.ts +35 -0
- package/lib/packlets/print/openPrintWindow.js +118 -0
- package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
- package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
- package/lib/packlets/responsive/index.d.ts +7 -0
- package/lib/packlets/responsive/index.js +13 -0
- package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
- package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
- package/lib/packlets/selectors/EntityRow.d.ts +45 -0
- package/lib/packlets/selectors/EntityRow.js +315 -0
- package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
- package/lib/packlets/selectors/PreferredSelector.js +287 -0
- package/lib/packlets/selectors/index.d.ts +5 -0
- package/lib/packlets/selectors/index.js +29 -0
- package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
- package/lib/packlets/sidebar/CollectionSection.js +143 -0
- package/lib/packlets/sidebar/EntityList.d.ts +105 -0
- package/lib/packlets/sidebar/EntityList.js +200 -0
- package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
- package/lib/packlets/sidebar/FilterBar.js +48 -0
- package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
- package/lib/packlets/sidebar/FilterRow.js +218 -0
- package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
- package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
- package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
- package/lib/packlets/sidebar/SearchBar.js +40 -0
- package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
- package/lib/packlets/sidebar/SidebarLayout.js +98 -0
- package/lib/packlets/sidebar/index.d.ts +12 -0
- package/lib/packlets/sidebar/index.js +22 -0
- package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
- package/lib/packlets/theme/ThemeProvider.js +178 -0
- package/lib/packlets/theme/index.d.ts +6 -0
- package/lib/packlets/theme/index.js +11 -0
- package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
- package/lib/packlets/top-bar/ModeSelector.js +52 -0
- package/lib/packlets/top-bar/TabBar.d.ts +31 -0
- package/lib/packlets/top-bar/TabBar.js +43 -0
- package/lib/packlets/top-bar/index.d.ts +7 -0
- package/lib/packlets/top-bar/index.js +12 -0
- package/lib/packlets/url-sync/index.d.ts +6 -0
- package/lib/packlets/url-sync/index.js +12 -0
- package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
- package/lib/packlets/url-sync/useUrlSync.js +162 -0
- package/package.json +82 -0
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.PreferredSelector = exports.EntityRow = void 0;
|
|
25
|
+
var EntityRow_1 = require("./EntityRow");
|
|
26
|
+
Object.defineProperty(exports, "EntityRow", { enumerable: true, get: function () { return EntityRow_1.EntityRow; } });
|
|
27
|
+
var PreferredSelector_1 = require("./PreferredSelector");
|
|
28
|
+
Object.defineProperty(exports, "PreferredSelector", { enumerable: true, get: function () { return PreferredSelector_1.PreferredSelector; } });
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic collection management section for the sidebar.
|
|
3
|
+
*
|
|
4
|
+
* Renders a collapsible list of collections with visibility toggles,
|
|
5
|
+
* status indicators, and action buttons.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* Describes a single collection for rendering in the sidebar.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface ICollectionRowItem {
|
|
15
|
+
/** Collection identifier */
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** Display name (falls back to id if undefined) */
|
|
18
|
+
readonly name: string | undefined;
|
|
19
|
+
/** Number of items in this collection */
|
|
20
|
+
readonly itemCount: number;
|
|
21
|
+
/** Whether this collection can be modified */
|
|
22
|
+
readonly isMutable: boolean;
|
|
23
|
+
/** Whether this collection is encrypted/protected */
|
|
24
|
+
readonly isProtected: boolean;
|
|
25
|
+
/** Whether the protected collection has been unlocked */
|
|
26
|
+
readonly isUnlocked: boolean;
|
|
27
|
+
/** Whether this collection is currently visible */
|
|
28
|
+
readonly isVisible: boolean;
|
|
29
|
+
/** Whether this collection is the default target for new entities */
|
|
30
|
+
readonly isDefault: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether this loaded collection has an orphaned encrypted shadow with the same ID
|
|
33
|
+
* in another storage root. When true, a repair action should be offered.
|
|
34
|
+
*/
|
|
35
|
+
readonly hasConflict?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Props for the CollectionSection component.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export interface ICollectionSectionProps {
|
|
42
|
+
/** Collection items to display */
|
|
43
|
+
readonly collections: ReadonlyArray<ICollectionRowItem>;
|
|
44
|
+
/** Callback when visibility is toggled for a collection */
|
|
45
|
+
readonly onToggleVisibility: (collectionId: string) => void;
|
|
46
|
+
/** Callback when all-visible toggle is clicked; receives true to show all, false to hide all */
|
|
47
|
+
readonly onToggleAllVisibility?: (showAll: boolean) => void;
|
|
48
|
+
/** Callback when "Add Directory" is clicked */
|
|
49
|
+
readonly onAddDirectory?: () => void;
|
|
50
|
+
/** Callback when "New Collection" is clicked */
|
|
51
|
+
readonly onCreateCollection?: () => void;
|
|
52
|
+
/** Callback when delete is clicked for a mutable collection */
|
|
53
|
+
readonly onDeleteCollection?: (collectionId: string) => void;
|
|
54
|
+
/** Callback when the star/default is clicked for a collection */
|
|
55
|
+
readonly onSetDefaultCollection?: (collectionId: string) => void;
|
|
56
|
+
/** Callback when export is clicked for a mutable collection */
|
|
57
|
+
readonly onExportCollection?: (collectionId: string) => void;
|
|
58
|
+
/** Callback when "Export All as Zip" is clicked (header-level) */
|
|
59
|
+
readonly onExportAllAsZip?: () => void;
|
|
60
|
+
/** Callback when "Import Collection" is clicked (header-level) */
|
|
61
|
+
readonly onImportCollection?: () => void;
|
|
62
|
+
/** Callback when "Open from File" is clicked (header-level, File System Access API) */
|
|
63
|
+
readonly onOpenCollectionFromFile?: () => void;
|
|
64
|
+
/** Callback when the unlock button is clicked for a locked protected collection */
|
|
65
|
+
readonly onUnlockCollection?: (collectionId: string) => void;
|
|
66
|
+
/** Callback when rename is clicked for a mutable collection */
|
|
67
|
+
readonly onRenameCollection?: (collectionId: string) => void;
|
|
68
|
+
/** Callback when merge is clicked for a mutable collection */
|
|
69
|
+
readonly onMergeCollection?: (collectionId: string) => void;
|
|
70
|
+
/** Whether the section starts collapsed */
|
|
71
|
+
readonly defaultCollapsed?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Collapsible sidebar section for managing collections.
|
|
75
|
+
*
|
|
76
|
+
* Renders a header with action buttons and a list of collection rows
|
|
77
|
+
* with visibility toggles, status indicators, and delete actions.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export declare function CollectionSection(props: ICollectionSectionProps): React.ReactElement;
|
|
82
|
+
//# sourceMappingURL=CollectionSection.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
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.CollectionSection = CollectionSection;
|
|
58
|
+
/**
|
|
59
|
+
* Generic collection management section for the sidebar.
|
|
60
|
+
*
|
|
61
|
+
* Renders a collapsible list of collections with visibility toggles,
|
|
62
|
+
* status indicators, and action buttons.
|
|
63
|
+
*
|
|
64
|
+
* @packageDocumentation
|
|
65
|
+
*/
|
|
66
|
+
const react_1 = __importStar(require("react"));
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// CollectionRow (internal)
|
|
69
|
+
// ============================================================================
|
|
70
|
+
function CollectionRow(props) {
|
|
71
|
+
var _a, _b, _c, _d;
|
|
72
|
+
const { collection, onToggleVisibility, onSetDefault, onDelete, onExport, onUnlock, onRename, onMerge } = props;
|
|
73
|
+
const displayName = (_a = collection.name) !== null && _a !== void 0 ? _a : collection.id;
|
|
74
|
+
return (react_1.default.createElement("div", { className: `flex items-center gap-1.5 px-3 py-1.5 text-sm transition-colors hover:bg-hover ${collection.isVisible ? 'text-secondary' : 'text-muted'}` },
|
|
75
|
+
react_1.default.createElement("button", { onClick: () => onToggleVisibility(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs hover:text-brand-accent transition-colors", title: collection.isVisible ? 'Hide collection' : 'Show collection', "aria-label": `${collection.isVisible ? 'Hide' : 'Show'} ${displayName}` }, collection.isVisible ? '\u{1F441}' : '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}'),
|
|
76
|
+
onSetDefault && collection.isMutable && (react_1.default.createElement("button", { onClick: () => onSetDefault(collection.id), className: `shrink-0 w-5 h-5 flex items-center justify-center transition-colors ${collection.isDefault ? 'text-star hover:text-star' : 'text-faint hover:text-star'}`, title: collection.isDefault
|
|
77
|
+
? 'Default collection for new items'
|
|
78
|
+
: 'Set as default collection for new items', "aria-label": collection.isDefault
|
|
79
|
+
? `${(_b = collection.name) !== null && _b !== void 0 ? _b : collection.id} is default`
|
|
80
|
+
: `Set ${(_c = collection.name) !== null && _c !== void 0 ? _c : collection.id} as default`, "aria-pressed": collection.isDefault }, collection.isDefault ? '★' : '☆')),
|
|
81
|
+
collection.hasConflict && (react_1.default.createElement("span", { className: "shrink-0 text-xs text-status-warning-strong cursor-default", title: "An encrypted copy of this collection from another storage root has the same ID. Go to Settings \u2192 Storage to resolve the conflict.", "aria-label": `Conflict: encrypted shadow for ${displayName}` }, '⚠')),
|
|
82
|
+
!collection.isMutable && (react_1.default.createElement("span", { className: "shrink-0 text-xs text-muted", title: "Built-in collection (read-only)" }, '\uD83D\uDD12')),
|
|
83
|
+
collection.isProtected &&
|
|
84
|
+
(collection.isUnlocked || !onUnlock ? (react_1.default.createElement("span", { className: `shrink-0 text-xs ${collection.isUnlocked ? 'text-status-success-icon' : 'text-muted'}`, title: collection.isUnlocked ? 'Protected (unlocked)' : 'Protected (locked)' }, '\uD83D\uDEE1')) : (react_1.default.createElement("button", { onClick: () => onUnlock(collection.id), className: "shrink-0 text-xs text-muted hover:text-star transition-colors", title: "Click to unlock", "aria-label": `Unlock ${(_d = collection.name) !== null && _d !== void 0 ? _d : collection.id}` }, '\uD83D\uDEE1'))),
|
|
85
|
+
react_1.default.createElement("span", { className: "flex-1 truncate", title: displayName }, displayName),
|
|
86
|
+
react_1.default.createElement("span", { className: "shrink-0 text-xs text-muted" }, collection.itemCount),
|
|
87
|
+
collection.isMutable && onExport && (react_1.default.createElement("button", { onClick: () => onExport(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Export ${displayName}`, "aria-label": `Export ${displayName}` }, "\u2193")),
|
|
88
|
+
collection.isMutable && onRename && (react_1.default.createElement("button", { onClick: () => onRename(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Rename ${displayName}`, "aria-label": `Rename ${displayName}` }, '✎')),
|
|
89
|
+
collection.isMutable && onMerge && (react_1.default.createElement("button", { onClick: () => onMerge(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-brand-accent transition-colors", title: `Merge ${displayName} into another collection`, "aria-label": `Merge ${displayName}` }, '⤵')),
|
|
90
|
+
collection.isMutable && onDelete && (react_1.default.createElement("button", { onClick: () => onDelete(collection.id), className: "shrink-0 w-5 h-5 flex items-center justify-center text-xs text-faint hover:text-status-error-icon transition-colors", title: `Remove ${displayName}`, "aria-label": `Remove ${displayName}` }, '×'))));
|
|
91
|
+
}
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// CollectionSection
|
|
94
|
+
// ============================================================================
|
|
95
|
+
/**
|
|
96
|
+
* Collapsible sidebar section for managing collections.
|
|
97
|
+
*
|
|
98
|
+
* Renders a header with action buttons and a list of collection rows
|
|
99
|
+
* with visibility toggles, status indicators, and delete actions.
|
|
100
|
+
*
|
|
101
|
+
* @public
|
|
102
|
+
*/
|
|
103
|
+
function CollectionSection(props) {
|
|
104
|
+
const { collections, onToggleVisibility, onToggleAllVisibility, onAddDirectory, onCreateCollection, onDeleteCollection, onSetDefaultCollection, onExportCollection, onExportAllAsZip, onImportCollection, onOpenCollectionFromFile, onUnlockCollection, onRenameCollection, onMergeCollection, defaultCollapsed = false } = props;
|
|
105
|
+
const [collapsed, setCollapsed] = (0, react_1.useState)(defaultCollapsed);
|
|
106
|
+
const allVisible = collections.length > 0 && collections.every((c) => c.isVisible);
|
|
107
|
+
const handleToggleAllVisibility = (0, react_1.useCallback)(() => {
|
|
108
|
+
if (onToggleAllVisibility) {
|
|
109
|
+
onToggleAllVisibility(!allVisible);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const ids = allVisible
|
|
113
|
+
? collections.map((c) => c.id)
|
|
114
|
+
: collections.filter((c) => !c.isVisible).map((c) => c.id);
|
|
115
|
+
ids.forEach((id) => onToggleVisibility(id));
|
|
116
|
+
}
|
|
117
|
+
}, [onToggleAllVisibility, onToggleVisibility, allVisible, collections]);
|
|
118
|
+
const handleToggleCollapse = (0, react_1.useCallback)(() => {
|
|
119
|
+
setCollapsed((prev) => !prev);
|
|
120
|
+
}, []);
|
|
121
|
+
return (react_1.default.createElement("div", { className: "flex flex-col border-t border-border mt-1" },
|
|
122
|
+
react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-1.5" },
|
|
123
|
+
react_1.default.createElement("button", { onClick: handleToggleCollapse, className: "flex items-center gap-1 text-xs font-medium text-muted uppercase tracking-wider hover:text-secondary transition-colors" },
|
|
124
|
+
react_1.default.createElement("span", { className: `text-[10px] transition-transform ${collapsed ? '' : 'rotate-90'}` }, '\u203A'),
|
|
125
|
+
"Collections",
|
|
126
|
+
react_1.default.createElement("span", { className: "text-muted normal-case font-normal" },
|
|
127
|
+
"(",
|
|
128
|
+
collections.length,
|
|
129
|
+
")")),
|
|
130
|
+
collections.length > 1 && (react_1.default.createElement("button", { onClick: handleToggleAllVisibility, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: allVisible ? 'Hide all collections' : 'Show all collections', "aria-label": allVisible ? 'Hide all collections' : 'Show all collections' }, allVisible ? '\u{1F441}\u{FE0F}\u{200D}\u{1F5E8}\u{FE0F}' : '\u{1F441}')),
|
|
131
|
+
react_1.default.createElement("div", { className: "flex items-center gap-1" },
|
|
132
|
+
onAddDirectory && (react_1.default.createElement("button", { onClick: onAddDirectory, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Add directory", "aria-label": "Add directory" },
|
|
133
|
+
"+",
|
|
134
|
+
'\uD83D\uDCC1')),
|
|
135
|
+
onExportAllAsZip && (react_1.default.createElement("button", { onClick: onExportAllAsZip, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Export all mutable collections as zip", "aria-label": "Export all as zip" },
|
|
136
|
+
"\u2193",
|
|
137
|
+
'🗂')),
|
|
138
|
+
onOpenCollectionFromFile && (react_1.default.createElement("button", { onClick: onOpenCollectionFromFile, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Open collection file for in-place editing", "aria-label": "Open collection from file" }, '📂')),
|
|
139
|
+
onImportCollection && (react_1.default.createElement("button", { onClick: onImportCollection, className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "Import collection from file (in-memory)", "aria-label": "Import collection from file" }, "\u2191")),
|
|
140
|
+
onCreateCollection && (react_1.default.createElement("button", { onClick: onCreateCollection, "data-testid": "sidebar-new-collection-button", className: "text-xs text-muted hover:text-brand-accent transition-colors px-1", title: "New collection", "aria-label": "New collection" }, "+")))),
|
|
141
|
+
!collapsed && (react_1.default.createElement("div", { className: "flex flex-col" }, collections.length === 0 ? (react_1.default.createElement("div", { className: "px-3 py-2 text-xs text-muted" }, "No collections")) : (collections.map((collection) => (react_1.default.createElement(CollectionRow, { key: collection.id, collection: collection, onToggleVisibility: onToggleVisibility, onSetDefault: onSetDefaultCollection, onDelete: onDeleteCollection, onExport: onExportCollection, onUnlock: onUnlockCollection, onRename: onRenameCollection, onMerge: onMergeCollection }))))))));
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=CollectionSection.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Describes how to extract display properties from an entity.
|
|
4
|
+
* Consuming apps provide this to configure the list for their entity types.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface IEntityDescriptor<TEntity, TId extends string = string> {
|
|
8
|
+
/** Extract the unique ID from an entity */
|
|
9
|
+
readonly getId: (entity: TEntity) => TId;
|
|
10
|
+
/** Extract the primary display label */
|
|
11
|
+
readonly getLabel: (entity: TEntity) => string;
|
|
12
|
+
/** Extract an optional secondary line (subtitle, description) */
|
|
13
|
+
readonly getSublabel?: (entity: TEntity) => string | undefined;
|
|
14
|
+
/** Extract an optional status badge or indicator */
|
|
15
|
+
readonly getStatus?: (entity: TEntity) => IEntityStatus | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Status indicator for an entity list item.
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export interface IEntityStatus {
|
|
22
|
+
/** Status label */
|
|
23
|
+
readonly label: string;
|
|
24
|
+
/** CSS color class for the status dot */
|
|
25
|
+
readonly colorClass: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Props for the EntityList component.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export interface IEntityListProps<TEntity, TId extends string = string> {
|
|
32
|
+
/** The entities to display */
|
|
33
|
+
readonly entities: ReadonlyArray<TEntity>;
|
|
34
|
+
/** Descriptor for extracting display properties */
|
|
35
|
+
readonly descriptor: IEntityDescriptor<TEntity, TId>;
|
|
36
|
+
/** Currently selected entity ID (if any) */
|
|
37
|
+
readonly selectedId?: TId;
|
|
38
|
+
/** Callback when an entity is selected (browse — list stays open) */
|
|
39
|
+
readonly onSelect: (id: TId) => void;
|
|
40
|
+
/** Callback when the user drills into the selected entity (Enter/→ — collapses list) */
|
|
41
|
+
readonly onDrill?: () => void;
|
|
42
|
+
/** Whether compare mode is active (shows checkboxes for multi-select) */
|
|
43
|
+
readonly compareMode?: boolean;
|
|
44
|
+
/** Entity IDs currently checked for comparison */
|
|
45
|
+
readonly checkedIds?: ReadonlySet<string>;
|
|
46
|
+
/** Callback to toggle an entity ID in/out of the compare selection */
|
|
47
|
+
readonly onCheckedChange?: (id: TId) => void;
|
|
48
|
+
/** Empty state configuration */
|
|
49
|
+
readonly emptyState?: IEmptyStateConfig;
|
|
50
|
+
/** Optional header content (e.g., result count) */
|
|
51
|
+
readonly header?: React.ReactNode;
|
|
52
|
+
/** Callback to toggle compare mode on/off (shows compare button in header) */
|
|
53
|
+
readonly onToggleCompare?: () => void;
|
|
54
|
+
/** Number of items currently selected for comparison */
|
|
55
|
+
readonly compareCount?: number;
|
|
56
|
+
/** Callback to start the comparison view (user clicks 'Compare Now') */
|
|
57
|
+
readonly onStartComparison?: () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Optional callback to delete an entity.
|
|
60
|
+
* When provided, a delete button appears on each row (visible on hover/selection).
|
|
61
|
+
* The consumer is responsible for showing a confirmation dialog before calling this.
|
|
62
|
+
*/
|
|
63
|
+
readonly onDelete?: (id: TId) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Optional predicate to control per-entity delete button visibility.
|
|
66
|
+
* When `onDelete` is provided but `canDelete` is not, all rows show the delete button.
|
|
67
|
+
* When both are provided, only rows where `canDelete(id)` returns true show the button.
|
|
68
|
+
*/
|
|
69
|
+
readonly canDelete?: (id: TId) => boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configuration for the empty state display.
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
export interface IEmptyStateConfig {
|
|
76
|
+
/** Title for the empty state */
|
|
77
|
+
readonly title: string;
|
|
78
|
+
/** Description text */
|
|
79
|
+
readonly description: string;
|
|
80
|
+
/** Optional CTA button */
|
|
81
|
+
readonly action?: IEmptyStateAction;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Call-to-action for the empty state.
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export interface IEmptyStateAction {
|
|
88
|
+
/** Button label */
|
|
89
|
+
readonly label: string;
|
|
90
|
+
/** Callback when clicked */
|
|
91
|
+
readonly onClick: () => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Generic entity list component for the sidebar.
|
|
95
|
+
*
|
|
96
|
+
* Renders a scrollable list of entities with:
|
|
97
|
+
* - Primary label + optional sublabel
|
|
98
|
+
* - Optional status indicator
|
|
99
|
+
* - Selection highlighting
|
|
100
|
+
* - Empty state with optional CTA
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export declare function EntityList<TEntity, TId extends string = string>(props: IEntityListProps<TEntity, TId>): React.ReactElement;
|
|
105
|
+
//# sourceMappingURL=EntityList.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
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.EntityList = EntityList;
|
|
58
|
+
const react_1 = __importStar(require("react"));
|
|
59
|
+
const responsive_1 = require("../responsive");
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// EntityList Component
|
|
62
|
+
// ============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Generic entity list component for the sidebar.
|
|
65
|
+
*
|
|
66
|
+
* Renders a scrollable list of entities with:
|
|
67
|
+
* - Primary label + optional sublabel
|
|
68
|
+
* - Optional status indicator
|
|
69
|
+
* - Selection highlighting
|
|
70
|
+
* - Empty state with optional CTA
|
|
71
|
+
*
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
function EntityList(props) {
|
|
75
|
+
const { entities, descriptor, selectedId, onSelect, onDrill, compareMode, checkedIds, onCheckedChange, emptyState, header, onDelete, canDelete } = props;
|
|
76
|
+
const listRef = (0, react_1.useRef)(null);
|
|
77
|
+
const { layoutMode } = (0, responsive_1.useResponsive)();
|
|
78
|
+
const isMobile = layoutMode === 'mobile';
|
|
79
|
+
// Scroll the selected item into view when selection changes
|
|
80
|
+
(0, react_1.useEffect)(() => {
|
|
81
|
+
if (selectedId && listRef.current) {
|
|
82
|
+
const selectedButton = listRef.current.querySelector(`[data-entity-id="${selectedId}"]`);
|
|
83
|
+
if (selectedButton) {
|
|
84
|
+
selectedButton.scrollIntoView({ block: 'nearest' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, [selectedId]);
|
|
88
|
+
// Find the index of the currently selected entity
|
|
89
|
+
const selectedIndex = entities.findIndex((e) => descriptor.getId(e) === selectedId);
|
|
90
|
+
const handleKeyDown = (0, react_1.useCallback)((e) => {
|
|
91
|
+
switch (e.key) {
|
|
92
|
+
case 'ArrowDown': {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
const nextIndex = selectedIndex < entities.length - 1 ? selectedIndex + 1 : 0;
|
|
95
|
+
onSelect(descriptor.getId(entities[nextIndex]));
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'ArrowUp': {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
const prevIndex = selectedIndex > 0 ? selectedIndex - 1 : entities.length - 1;
|
|
101
|
+
onSelect(descriptor.getId(entities[prevIndex]));
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'Enter':
|
|
105
|
+
case 'ArrowRight': {
|
|
106
|
+
if (selectedId !== undefined && onDrill) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
onDrill();
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}, [entities, descriptor, selectedId, selectedIndex, onSelect, onDrill]);
|
|
116
|
+
if (entities.length === 0 && emptyState) {
|
|
117
|
+
return react_1.default.createElement(EmptyState, { config: emptyState });
|
|
118
|
+
}
|
|
119
|
+
return (react_1.default.createElement("div", { className: "flex flex-col flex-1 overflow-hidden", onKeyDown: handleKeyDown },
|
|
120
|
+
header !== undefined ? (react_1.default.createElement("div", { className: "px-3 py-1.5 text-xs text-muted border-b border-border-subtle" }, header)) : entities.length > 0 ? (react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border-subtle" },
|
|
121
|
+
react_1.default.createElement("span", { className: "text-xs text-muted" },
|
|
122
|
+
entities.length,
|
|
123
|
+
" item",
|
|
124
|
+
entities.length !== 1 ? 's' : '',
|
|
125
|
+
compareMode && props.compareCount !== undefined && props.compareCount > 0 && (react_1.default.createElement("span", { className: "ml-1.5 text-brand-accent" },
|
|
126
|
+
"\u00B7 ",
|
|
127
|
+
props.compareCount,
|
|
128
|
+
" selected"))),
|
|
129
|
+
react_1.default.createElement("div", { className: "flex items-center gap-1" },
|
|
130
|
+
compareMode &&
|
|
131
|
+
props.onStartComparison &&
|
|
132
|
+
props.compareCount !== undefined &&
|
|
133
|
+
props.compareCount >= 2 && (react_1.default.createElement("button", { onClick: (e) => {
|
|
134
|
+
var _a;
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
(_a = props.onStartComparison) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
137
|
+
}, 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")),
|
|
138
|
+
props.onToggleCompare && (react_1.default.createElement("button", { onClick: (e) => {
|
|
139
|
+
var _a;
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
(_a = props.onToggleCompare) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
142
|
+
}, className: `px-2 py-0.5 text-[11px] rounded border transition-colors ${compareMode
|
|
143
|
+
? 'bg-brand-accent text-white border-brand-accent'
|
|
144
|
+
: 'bg-surface text-muted border-border hover:border-brand-accent hover:text-brand-accent'}` }, compareMode ? 'Cancel' : 'Compare'))))) : null,
|
|
145
|
+
react_1.default.createElement("div", { ref: listRef, className: "flex-1 overflow-y-auto" }, entities.map((entity) => {
|
|
146
|
+
var _a, _b;
|
|
147
|
+
const id = descriptor.getId(entity);
|
|
148
|
+
const label = descriptor.getLabel(entity);
|
|
149
|
+
const sublabel = (_a = descriptor.getSublabel) === null || _a === void 0 ? void 0 : _a.call(descriptor, entity);
|
|
150
|
+
const status = (_b = descriptor.getStatus) === null || _b === void 0 ? void 0 : _b.call(descriptor, entity);
|
|
151
|
+
const isSelected = id === selectedId;
|
|
152
|
+
const isChecked = compareMode === true && checkedIds !== undefined && checkedIds.has(id);
|
|
153
|
+
return (react_1.default.createElement("div", { key: id, className: `group flex items-center gap-2 w-full border-b border-border-subtle transition-colors ${isChecked
|
|
154
|
+
? 'bg-selected border-l-2 border-l-selected-border'
|
|
155
|
+
: isSelected && !compareMode
|
|
156
|
+
? 'bg-selected border-l-2 border-l-selected-border'
|
|
157
|
+
: 'hover:bg-hover border-l-2 border-l-transparent'}` },
|
|
158
|
+
react_1.default.createElement("button", { "data-entity-id": id, onClick: () => {
|
|
159
|
+
if (compareMode && onCheckedChange) {
|
|
160
|
+
onCheckedChange(id);
|
|
161
|
+
}
|
|
162
|
+
else if (isSelected && onDrill) {
|
|
163
|
+
onDrill();
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
onSelect(id);
|
|
167
|
+
}
|
|
168
|
+
}, className: `flex items-center gap-2 flex-1 min-w-0 px-3 text-left ${isMobile ? 'py-3' : 'py-2'}` },
|
|
169
|
+
compareMode && (react_1.default.createElement("span", { className: `flex items-center justify-center w-4 h-4 rounded border shrink-0 transition-colors ${isChecked
|
|
170
|
+
? 'bg-brand-accent border-brand-accent text-white'
|
|
171
|
+
: '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 },
|
|
172
|
+
react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))))),
|
|
173
|
+
react_1.default.createElement("div", { className: "flex-1 min-w-0" },
|
|
174
|
+
react_1.default.createElement("div", { className: `text-sm truncate ${(isSelected && !compareMode) || isChecked
|
|
175
|
+
? 'font-medium text-brand-primary'
|
|
176
|
+
: 'text-primary'}` }, label),
|
|
177
|
+
sublabel && react_1.default.createElement("div", { className: "text-xs text-muted truncate mt-0.5" }, sublabel)),
|
|
178
|
+
status && (react_1.default.createElement("span", { className: "flex items-center gap-1 shrink-0 mt-0.5" },
|
|
179
|
+
react_1.default.createElement("span", { className: `w-2 h-2 rounded-full ${status.colorClass}` }),
|
|
180
|
+
react_1.default.createElement("span", { className: "text-xs text-muted" }, status.label)))),
|
|
181
|
+
onDelete && !compareMode && (!canDelete || canDelete(id)) && (react_1.default.createElement("button", { onClick: (e) => {
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
onDelete(id);
|
|
184
|
+
}, className: `shrink-0 mr-1 flex items-center justify-center text-muted hover:text-status-error-text transition-opacity rounded ${isMobile
|
|
185
|
+
? 'w-11 h-11 opacity-100'
|
|
186
|
+
: 'w-6 h-6 opacity-0 group-hover:opacity-100 focus:opacity-100'}`, title: `Delete ${label}`, "aria-label": `Delete ${label}` },
|
|
187
|
+
react_1.default.createElement("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 },
|
|
188
|
+
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" }))))));
|
|
189
|
+
}))));
|
|
190
|
+
}
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// Empty State
|
|
193
|
+
// ============================================================================
|
|
194
|
+
function EmptyState({ config }) {
|
|
195
|
+
return (react_1.default.createElement("div", { className: "flex flex-col items-center justify-center flex-1 p-6 text-center" },
|
|
196
|
+
react_1.default.createElement("h3", { className: "text-sm font-medium text-secondary mb-1" }, config.title),
|
|
197
|
+
react_1.default.createElement("p", { className: "text-xs text-muted mb-4" }, config.description),
|
|
198
|
+
config.action && (react_1.default.createElement("button", { onClick: config.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" }, config.action.label))));
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=EntityList.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ISearchBarProps } from './SearchBar';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the FilterBar component.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface IFilterBarProps {
|
|
8
|
+
/** Search bar props */
|
|
9
|
+
readonly search: ISearchBarProps;
|
|
10
|
+
/** Total number of active filters across all filter rows */
|
|
11
|
+
readonly activeFilterCount: number;
|
|
12
|
+
/** Callback to clear all filters (search + all filter rows) */
|
|
13
|
+
readonly onClearAll: () => void;
|
|
14
|
+
/** Filter row children (FilterRow components) */
|
|
15
|
+
readonly children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Composite sidebar filter bar: search input + filter rows + clear-all.
|
|
19
|
+
*
|
|
20
|
+
* Renders the search bar at the top, filter rows in the middle,
|
|
21
|
+
* and a "Clear all" button when any filters are active.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export declare function FilterBar(props: IFilterBarProps): React.ReactElement;
|
|
26
|
+
//# sourceMappingURL=FilterBar.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
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.FilterBar = FilterBar;
|
|
28
|
+
const react_1 = __importDefault(require("react"));
|
|
29
|
+
const SearchBar_1 = require("./SearchBar");
|
|
30
|
+
/**
|
|
31
|
+
* Composite sidebar filter bar: search input + filter rows + clear-all.
|
|
32
|
+
*
|
|
33
|
+
* Renders the search bar at the top, filter rows in the middle,
|
|
34
|
+
* and a "Clear all" button when any filters are active.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
function FilterBar(props) {
|
|
39
|
+
const { search, activeFilterCount, onClearAll, children } = props;
|
|
40
|
+
const hasActiveFilters = activeFilterCount > 0 || search.value.length > 0;
|
|
41
|
+
return (react_1.default.createElement("div", { className: "flex flex-col" },
|
|
42
|
+
react_1.default.createElement(SearchBar_1.SearchBar, Object.assign({}, search)),
|
|
43
|
+
react_1.default.createElement("div", { className: "flex items-center justify-between px-3 py-1" },
|
|
44
|
+
react_1.default.createElement("span", { className: "text-xs font-medium text-muted uppercase tracking-wider" }, "Filters"),
|
|
45
|
+
hasActiveFilters && (react_1.default.createElement("button", { onClick: onClearAll, className: "text-xs text-brand-accent hover:text-brand-primary" }, "Clear all"))),
|
|
46
|
+
react_1.default.createElement("div", { className: "flex flex-col" }, children)));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=FilterBar.js.map
|