@mshuja288/aksuicore 0.4.1 → 0.4.3
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/CHANGELOG.md +7 -0
- package/dist/aksui.css +75 -1
- package/dist/index.cjs +226 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +226 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/patterns/RecordWorkspace.tsx +272 -10
- package/src/patterns/index.ts +3 -0
- package/src/theme/patterns.css +75 -1
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mshuja288/aksuicore",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Shared UI components and templates for Ikssam applications",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"private": false,
|
|
7
6
|
"type": "module",
|
|
8
7
|
"main": "./dist/index.cjs",
|
|
9
8
|
"module": "./dist/index.js",
|
|
@@ -65,12 +65,34 @@ export type ExportColumnRef = {
|
|
|
65
65
|
header: string;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
export type BulkImportMode = "insert" | "update" | "upsert";
|
|
69
|
+
|
|
70
|
+
export type BulkImportError = {
|
|
71
|
+
row: number;
|
|
72
|
+
field?: string | null;
|
|
73
|
+
code: string;
|
|
74
|
+
message: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type BulkImportResult = {
|
|
78
|
+
totalRows: number;
|
|
79
|
+
failed: number;
|
|
80
|
+
applied: boolean;
|
|
81
|
+
errors: BulkImportError[];
|
|
82
|
+
};
|
|
83
|
+
|
|
68
84
|
const EXPORT_FORMAT_OPTIONS: { format: ExportDownloadFormat; label: string }[] = [
|
|
69
85
|
{ format: "csv", label: "CSV" },
|
|
70
86
|
{ format: "xlsx", label: "Excel" },
|
|
71
87
|
{ format: "json", label: "JSON" },
|
|
72
88
|
];
|
|
73
89
|
|
|
90
|
+
const BULK_IMPORT_MODE_OPTIONS: { mode: BulkImportMode; label: string }[] = [
|
|
91
|
+
{ mode: "insert", label: "Insert" },
|
|
92
|
+
{ mode: "update", label: "Update" },
|
|
93
|
+
{ mode: "upsert", label: "Upsert" },
|
|
94
|
+
];
|
|
95
|
+
|
|
74
96
|
type SortDirection = "asc" | "desc";
|
|
75
97
|
type SortState = { key: string; direction: SortDirection };
|
|
76
98
|
type DetailLine = { label: string; value: string };
|
|
@@ -80,6 +102,48 @@ function defaultVisibleKeys<T>(columns: TableColumn<T>[]) {
|
|
|
80
102
|
return columns.filter((column) => column.defaultVisible !== false).map((column) => column.key);
|
|
81
103
|
}
|
|
82
104
|
|
|
105
|
+
/** Shared across all RecordWorkspace tables (preview + side rail). */
|
|
106
|
+
const WORKSPACE_CHROME_KEY = "aksui.workspace.chrome";
|
|
107
|
+
|
|
108
|
+
type WorkspaceChrome = {
|
|
109
|
+
previewEnabled: boolean;
|
|
110
|
+
sideOpen: boolean;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function readWorkspaceChrome(): WorkspaceChrome {
|
|
114
|
+
if (typeof window === "undefined") {
|
|
115
|
+
return { previewEnabled: false, sideOpen: false };
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const raw = window.localStorage.getItem(WORKSPACE_CHROME_KEY);
|
|
119
|
+
if (!raw) {
|
|
120
|
+
return { previewEnabled: false, sideOpen: false };
|
|
121
|
+
}
|
|
122
|
+
const parsed = JSON.parse(raw) as Partial<WorkspaceChrome>;
|
|
123
|
+
return {
|
|
124
|
+
previewEnabled: Boolean(parsed.previewEnabled),
|
|
125
|
+
sideOpen: Boolean(parsed.sideOpen),
|
|
126
|
+
};
|
|
127
|
+
} catch {
|
|
128
|
+
return { previewEnabled: false, sideOpen: false };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeWorkspaceChrome(chrome: WorkspaceChrome) {
|
|
133
|
+
if (typeof window === "undefined") {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
window.localStorage.setItem(WORKSPACE_CHROME_KEY, JSON.stringify(chrome));
|
|
138
|
+
} catch {
|
|
139
|
+
/* ignore quota / private mode */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function filtersPrefsKey(columnPrefsKey: string | undefined) {
|
|
144
|
+
return columnPrefsKey ? `${columnPrefsKey}.filters` : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
83
147
|
function readStoredColumnKeys(prefsKey: string | undefined, fallback: string[], allowed: Set<string>) {
|
|
84
148
|
if (!prefsKey || typeof window === "undefined") {
|
|
85
149
|
return fallback;
|
|
@@ -111,6 +175,48 @@ function writeStoredColumnKeys(prefsKey: string | undefined, keys: string[]) {
|
|
|
111
175
|
}
|
|
112
176
|
}
|
|
113
177
|
|
|
178
|
+
function readStoredFilters(prefsKey: string | undefined, allowed: Set<string>): Record<string, string> {
|
|
179
|
+
if (!prefsKey || typeof window === "undefined") {
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const raw = window.localStorage.getItem(prefsKey);
|
|
184
|
+
if (!raw) {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
188
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
const next: Record<string, string> = {};
|
|
192
|
+
for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
193
|
+
if (allowed.has(key) && typeof value === "string" && value.trim()) {
|
|
194
|
+
next[key] = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return next;
|
|
198
|
+
} catch {
|
|
199
|
+
return {};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function writeStoredFilters(prefsKey: string | undefined, filters: Record<string, string>) {
|
|
204
|
+
if (!prefsKey || typeof window === "undefined") {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const trimmed: Record<string, string> = {};
|
|
209
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
210
|
+
if (value.trim()) {
|
|
211
|
+
trimmed[key] = value;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
window.localStorage.setItem(prefsKey, JSON.stringify(trimmed));
|
|
215
|
+
} catch {
|
|
216
|
+
/* ignore quota / private mode */
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
114
220
|
function compareColumnValues<T>(column: TableColumn<T>, left: T, right: T) {
|
|
115
221
|
const leftRaw = column.display ? column.display(left) : column.value(left);
|
|
116
222
|
const rightRaw = column.display ? column.display(right) : column.value(right);
|
|
@@ -194,6 +300,16 @@ function PreviewIcon() {
|
|
|
194
300
|
);
|
|
195
301
|
}
|
|
196
302
|
|
|
303
|
+
function UploadIcon() {
|
|
304
|
+
return (
|
|
305
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
306
|
+
<path d="M10 16.5V7.3" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
307
|
+
<path d="M6.5 10.8 10 7.2l3.5 3.6" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
|
308
|
+
<path d="M4 4.5h12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
309
|
+
</svg>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
197
313
|
function DownloadIcon() {
|
|
198
314
|
return (
|
|
199
315
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
@@ -258,7 +374,11 @@ export function RecordWorkspace<T>({
|
|
|
258
374
|
canDelete,
|
|
259
375
|
onDownload,
|
|
260
376
|
canDownload = true,
|
|
377
|
+
canExport,
|
|
261
378
|
downloadBusy = false,
|
|
379
|
+
onBulkImport,
|
|
380
|
+
canImport = true,
|
|
381
|
+
importBusy = false,
|
|
262
382
|
showColumnPicker = true,
|
|
263
383
|
columnPrefsKey,
|
|
264
384
|
visibleColumnKeys,
|
|
@@ -285,11 +405,20 @@ export function RecordWorkspace<T>({
|
|
|
285
405
|
* Second argument lists currently visible columns (exportKey ?? key).
|
|
286
406
|
*/
|
|
287
407
|
onDownload?: (format: ExportDownloadFormat, columns: ExportColumnRef[]) => void;
|
|
408
|
+
/** Alias for `canDownload` (RBAC). When set, export requires this and `canDownload` to be true. */
|
|
409
|
+
canExport?: boolean;
|
|
288
410
|
canDownload?: boolean;
|
|
289
411
|
downloadBusy?: boolean;
|
|
412
|
+
onBulkImport?: (mode: BulkImportMode, file: File) => Promise<BulkImportResult>;
|
|
413
|
+
canImport?: boolean;
|
|
414
|
+
importBusy?: boolean;
|
|
290
415
|
/** Column checklist in the side rail. Default true. */
|
|
291
416
|
showColumnPicker?: boolean;
|
|
292
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Persist visible column keys in localStorage under this key.
|
|
419
|
+
* Also persists filter values at `${columnPrefsKey}.filters`.
|
|
420
|
+
* Preview + side-rail open/collapsed use a shared key across all tables (`aksui.workspace.chrome`).
|
|
421
|
+
*/
|
|
293
422
|
columnPrefsKey?: string;
|
|
294
423
|
/** Controlled visible column keys. */
|
|
295
424
|
visibleColumnKeys?: string[];
|
|
@@ -309,13 +438,21 @@ export function RecordWorkspace<T>({
|
|
|
309
438
|
const [internalVisibleKeys, setInternalVisibleKeys] = useState<string[]>(() =>
|
|
310
439
|
readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys),
|
|
311
440
|
);
|
|
312
|
-
const
|
|
441
|
+
const filterStorageKey = filtersPrefsKey(columnPrefsKey);
|
|
442
|
+
const [filters, setFilters] = useState<Record<string, string>>(() =>
|
|
443
|
+
readStoredFilters(filterStorageKey, allowedKeys),
|
|
444
|
+
);
|
|
313
445
|
const [sort, setSort] = useState<SortState | null>(null);
|
|
314
|
-
const [sideOpen, setSideOpen] = useState(
|
|
446
|
+
const [sideOpen, setSideOpen] = useState(() => readWorkspaceChrome().sideOpen);
|
|
315
447
|
const [toolsDrawerOpen, setToolsDrawerOpen] = useState(false);
|
|
316
448
|
const [downloadOpen, setDownloadOpen] = useState(false);
|
|
449
|
+
const [bulkOpen, setBulkOpen] = useState(false);
|
|
450
|
+
const [bulkMode, setBulkMode] = useState<BulkImportMode>("upsert");
|
|
451
|
+
const [bulkFile, setBulkFile] = useState<File | null>(null);
|
|
452
|
+
const [bulkReport, setBulkReport] = useState<BulkImportResult | null>(null);
|
|
453
|
+
const bulkFileInputRef = useRef<HTMLInputElement | null>(null);
|
|
317
454
|
const [columnsOpen, setColumnsOpen] = useState(false);
|
|
318
|
-
const [previewEnabled, setPreviewEnabled] = useState(
|
|
455
|
+
const [previewEnabled, setPreviewEnabled] = useState(() => readWorkspaceChrome().previewEnabled);
|
|
319
456
|
const [previewKey, setPreviewKey] = useState<string | number | null>(null);
|
|
320
457
|
const [tooltip, setTooltip] = useState<TooltipState | null>(null);
|
|
321
458
|
const [pageSize, setPageSize] = useState("15");
|
|
@@ -337,6 +474,10 @@ export function RecordWorkspace<T>({
|
|
|
337
474
|
setInternalVisibleKeys(readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys));
|
|
338
475
|
}, [allowedKeys, columnPrefsKey, fallbackKeys, isControlled]);
|
|
339
476
|
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
setFilters(readStoredFilters(filtersPrefsKey(columnPrefsKey), allowedKeys));
|
|
479
|
+
}, [allowedKeys, columnPrefsKey]);
|
|
480
|
+
|
|
340
481
|
const resolvedVisibleKeys = useMemo(() => {
|
|
341
482
|
const source = isControlled ? visibleColumnKeys! : internalVisibleKeys;
|
|
342
483
|
const filtered = source.filter((key) => allowedKeys.has(key));
|
|
@@ -425,7 +566,9 @@ export function RecordWorkspace<T>({
|
|
|
425
566
|
const start = showAll ? 0 : (currentPage - 1) * numericPageSize;
|
|
426
567
|
const pageItems = showAll ? sorted : sorted.slice(start, start + numericPageSize);
|
|
427
568
|
const showAdd = canAdd && Boolean(onAdd);
|
|
428
|
-
const
|
|
569
|
+
const exportAllowed = (canExport ?? true) && canDownload;
|
|
570
|
+
const showDownload = Boolean(onDownload) && exportAllowed;
|
|
571
|
+
const showBulkImport = Boolean(onBulkImport) && canImport;
|
|
429
572
|
const showFilters = filterableColumns.length > 0;
|
|
430
573
|
const showColumns = showColumnPicker && columns.length > 0;
|
|
431
574
|
const previewItem = previewKey === null ? null : sorted.find((item) => rowKey(item) === previewKey) ?? null;
|
|
@@ -478,6 +621,30 @@ export function RecordWorkspace<T>({
|
|
|
478
621
|
setDownloadOpen(false);
|
|
479
622
|
}
|
|
480
623
|
|
|
624
|
+
async function submitBulkImport() {
|
|
625
|
+
if (!onBulkImport || importBusy || !bulkFile) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
setBulkReport(null);
|
|
629
|
+
try {
|
|
630
|
+
const report = await onBulkImport(bulkMode, bulkFile);
|
|
631
|
+
setBulkReport(report);
|
|
632
|
+
if (report.applied) {
|
|
633
|
+
setBulkFile(null);
|
|
634
|
+
if (bulkFileInputRef.current) {
|
|
635
|
+
bulkFileInputRef.current.value = "";
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
setBulkReport({
|
|
640
|
+
totalRows: 0,
|
|
641
|
+
failed: 1,
|
|
642
|
+
applied: false,
|
|
643
|
+
errors: [{ row: 0, code: "ImportFailed", message: "Import request failed." }],
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
481
648
|
function cellText(column: TableColumn<T>, item: T) {
|
|
482
649
|
const raw = column.display ? column.display(item) : column.value(item);
|
|
483
650
|
return raw === null || raw === undefined || raw === "" ? "—" : String(raw);
|
|
@@ -493,15 +660,25 @@ export function RecordWorkspace<T>({
|
|
|
493
660
|
const previewTitle = previewItem && visibleColumns[0] ? cellText(visibleColumns[0], previewItem) : null;
|
|
494
661
|
|
|
495
662
|
function changeFilter(key: string, value: string) {
|
|
496
|
-
setFilters((current) =>
|
|
663
|
+
setFilters((current) => {
|
|
664
|
+
const next = { ...current, [key]: value };
|
|
665
|
+
writeStoredFilters(filterStorageKey, next);
|
|
666
|
+
return next;
|
|
667
|
+
});
|
|
497
668
|
setPage(1);
|
|
498
669
|
}
|
|
499
670
|
|
|
500
671
|
function clearFilters() {
|
|
501
672
|
setFilters({});
|
|
673
|
+
writeStoredFilters(filterStorageKey, {});
|
|
502
674
|
setPage(1);
|
|
503
675
|
}
|
|
504
676
|
|
|
677
|
+
function setSideOpenValue(open: boolean) {
|
|
678
|
+
setSideOpen(open);
|
|
679
|
+
writeWorkspaceChrome({ previewEnabled, sideOpen: open });
|
|
680
|
+
}
|
|
681
|
+
|
|
505
682
|
function toggleSort(column: TableColumn<T>) {
|
|
506
683
|
if (column.sortable === false) {
|
|
507
684
|
return;
|
|
@@ -520,6 +697,7 @@ export function RecordWorkspace<T>({
|
|
|
520
697
|
|
|
521
698
|
function setPreviewEnabledValue(enabled: boolean) {
|
|
522
699
|
setPreviewEnabled(enabled);
|
|
700
|
+
writeWorkspaceChrome({ previewEnabled: enabled, sideOpen });
|
|
523
701
|
setTooltip(null);
|
|
524
702
|
if (!enabled) {
|
|
525
703
|
setPreviewKey(null);
|
|
@@ -591,7 +769,7 @@ export function RecordWorkspace<T>({
|
|
|
591
769
|
title="Columns"
|
|
592
770
|
aria-label="Columns"
|
|
593
771
|
onClick={() => {
|
|
594
|
-
|
|
772
|
+
setSideOpenValue(true);
|
|
595
773
|
setColumnsOpen(true);
|
|
596
774
|
}}
|
|
597
775
|
>
|
|
@@ -607,20 +785,35 @@ export function RecordWorkspace<T>({
|
|
|
607
785
|
aria-label="Download"
|
|
608
786
|
disabled={downloadBusy}
|
|
609
787
|
onClick={() => {
|
|
610
|
-
|
|
788
|
+
setSideOpenValue(true);
|
|
611
789
|
setDownloadOpen(true);
|
|
612
790
|
}}
|
|
613
791
|
>
|
|
614
792
|
<DownloadIcon />
|
|
615
793
|
</button>
|
|
616
794
|
) : null}
|
|
795
|
+
{showBulkImport ? (
|
|
796
|
+
<button
|
|
797
|
+
type="button"
|
|
798
|
+
className="ghost side-rail-icon"
|
|
799
|
+
title="Bulk entry"
|
|
800
|
+
aria-label="Bulk entry"
|
|
801
|
+
disabled={importBusy}
|
|
802
|
+
onClick={() => {
|
|
803
|
+
setSideOpenValue(true);
|
|
804
|
+
setBulkOpen(true);
|
|
805
|
+
}}
|
|
806
|
+
>
|
|
807
|
+
<UploadIcon />
|
|
808
|
+
</button>
|
|
809
|
+
) : null}
|
|
617
810
|
{showFilters ? (
|
|
618
811
|
<button
|
|
619
812
|
type="button"
|
|
620
813
|
className="ghost side-rail-icon"
|
|
621
814
|
title="Show filters"
|
|
622
815
|
aria-label="Show filters"
|
|
623
|
-
onClick={() =>
|
|
816
|
+
onClick={() => setSideOpenValue(true)}
|
|
624
817
|
>
|
|
625
818
|
<FilterIcon />
|
|
626
819
|
{activeFilterCount > 0 ? <span className="filter-badge">{activeFilterCount}</span> : null}
|
|
@@ -742,6 +935,75 @@ export function RecordWorkspace<T>({
|
|
|
742
935
|
</div>
|
|
743
936
|
) : null}
|
|
744
937
|
|
|
938
|
+
{showBulkImport ? (
|
|
939
|
+
<div className="side-rail-bulk">
|
|
940
|
+
<button
|
|
941
|
+
type="button"
|
|
942
|
+
className="ghost side-rail-bulk-toggle"
|
|
943
|
+
aria-expanded={bulkOpen}
|
|
944
|
+
disabled={importBusy}
|
|
945
|
+
onClick={() => setBulkOpen((open) => !open)}
|
|
946
|
+
>
|
|
947
|
+
<UploadIcon />
|
|
948
|
+
<span>{importBusy ? "Importing…" : "Bulk entry"}</span>
|
|
949
|
+
</button>
|
|
950
|
+
{bulkOpen ? (
|
|
951
|
+
<div className="side-rail-bulk-panel" role="group" aria-label="Bulk entry">
|
|
952
|
+
<label className="side-rail-bulk-mode">
|
|
953
|
+
Mode
|
|
954
|
+
<Select
|
|
955
|
+
value={bulkMode}
|
|
956
|
+
options={BULK_IMPORT_MODE_OPTIONS.map((option) => ({ value: option.mode, label: option.label }))}
|
|
957
|
+
onChange={(value) => setBulkMode(value as BulkImportMode)}
|
|
958
|
+
/>
|
|
959
|
+
</label>
|
|
960
|
+
<label className="side-rail-bulk-file">
|
|
961
|
+
File
|
|
962
|
+
<input
|
|
963
|
+
ref={bulkFileInputRef}
|
|
964
|
+
type="file"
|
|
965
|
+
accept=".csv,.xlsx,.json"
|
|
966
|
+
disabled={importBusy}
|
|
967
|
+
onChange={(event) => {
|
|
968
|
+
setBulkFile(event.target.files?.[0] ?? null);
|
|
969
|
+
setBulkReport(null);
|
|
970
|
+
}}
|
|
971
|
+
/>
|
|
972
|
+
</label>
|
|
973
|
+
<button
|
|
974
|
+
type="button"
|
|
975
|
+
className="side-rail-bulk-submit"
|
|
976
|
+
disabled={importBusy || !bulkFile}
|
|
977
|
+
onClick={() => void submitBulkImport()}
|
|
978
|
+
>
|
|
979
|
+
{importBusy ? "Importing…" : "Import"}
|
|
980
|
+
</button>
|
|
981
|
+
{bulkReport ? (
|
|
982
|
+
<div className="side-rail-bulk-report" role="status">
|
|
983
|
+
<p>
|
|
984
|
+
{bulkReport.applied
|
|
985
|
+
? `Applied ${bulkReport.totalRows} row(s).`
|
|
986
|
+
: `${bulkReport.failed} error(s) in ${bulkReport.totalRows} row(s).`}
|
|
987
|
+
</p>
|
|
988
|
+
{bulkReport.errors.length > 0 ? (
|
|
989
|
+
<ul className="side-rail-bulk-errors">
|
|
990
|
+
{bulkReport.errors.slice(0, 8).map((error, index) => (
|
|
991
|
+
<li key={`${error.row}-${error.code}-${index}`}>
|
|
992
|
+
Row {error.row}: {error.message}
|
|
993
|
+
</li>
|
|
994
|
+
))}
|
|
995
|
+
{bulkReport.errors.length > 8 ? (
|
|
996
|
+
<li>…and {bulkReport.errors.length - 8} more</li>
|
|
997
|
+
) : null}
|
|
998
|
+
</ul>
|
|
999
|
+
) : null}
|
|
1000
|
+
</div>
|
|
1001
|
+
) : null}
|
|
1002
|
+
</div>
|
|
1003
|
+
) : null}
|
|
1004
|
+
</div>
|
|
1005
|
+
) : null}
|
|
1006
|
+
|
|
745
1007
|
{showFilters ? (
|
|
746
1008
|
<div className="side-rail-filters">
|
|
747
1009
|
<div className="filter-panel-head">
|
|
@@ -1002,7 +1264,7 @@ export function RecordWorkspace<T>({
|
|
|
1002
1264
|
className="ghost side-rail-toggle"
|
|
1003
1265
|
aria-expanded={sideOpen}
|
|
1004
1266
|
title={sideOpen ? "Collapse" : "Expand"}
|
|
1005
|
-
onClick={() =>
|
|
1267
|
+
onClick={() => setSideOpenValue(!sideOpen)}
|
|
1006
1268
|
>
|
|
1007
1269
|
<ChevronIcon open={sideOpen} />
|
|
1008
1270
|
{sideOpen ? <span>Collapse</span> : null}
|
package/src/patterns/index.ts
CHANGED
package/src/theme/patterns.css
CHANGED
|
@@ -405,7 +405,8 @@ button.side-rail-add:hover {
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
.side-rail-columns,
|
|
408
|
-
.side-rail-download
|
|
408
|
+
.side-rail-download,
|
|
409
|
+
.side-rail-bulk {
|
|
409
410
|
display: grid;
|
|
410
411
|
gap: 8px;
|
|
411
412
|
}
|
|
@@ -533,6 +534,79 @@ button.side-rail-download-format:disabled {
|
|
|
533
534
|
cursor: default;
|
|
534
535
|
}
|
|
535
536
|
|
|
537
|
+
button.side-rail-bulk-toggle {
|
|
538
|
+
display: inline-flex;
|
|
539
|
+
align-items: center;
|
|
540
|
+
justify-content: center;
|
|
541
|
+
gap: 8px;
|
|
542
|
+
width: 100%;
|
|
543
|
+
padding: 10px 12px;
|
|
544
|
+
border: 1px solid var(--aks-border, #d7dde5);
|
|
545
|
+
border-radius: var(--aks-radius);
|
|
546
|
+
background: #fff;
|
|
547
|
+
color: var(--aks-brand);
|
|
548
|
+
box-shadow: none;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
button.side-rail-bulk-toggle svg {
|
|
552
|
+
width: 18px;
|
|
553
|
+
height: 18px;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
button.side-rail-bulk-toggle:hover:not(:disabled) {
|
|
557
|
+
background: #f5f7fa;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
button.side-rail-bulk-toggle:disabled {
|
|
561
|
+
opacity: 0.55;
|
|
562
|
+
cursor: default;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.side-rail-bulk-panel {
|
|
566
|
+
display: grid;
|
|
567
|
+
gap: 10px;
|
|
568
|
+
padding: 8px;
|
|
569
|
+
border: 1px solid var(--aks-line);
|
|
570
|
+
border-radius: var(--aks-radius);
|
|
571
|
+
background: #faf9f7;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
label.side-rail-bulk-mode,
|
|
575
|
+
label.side-rail-bulk-file {
|
|
576
|
+
display: grid;
|
|
577
|
+
gap: 6px;
|
|
578
|
+
margin: 0;
|
|
579
|
+
font-size: 13px;
|
|
580
|
+
font-weight: 600;
|
|
581
|
+
color: var(--aks-brand);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
button.side-rail-bulk-submit {
|
|
585
|
+
width: 100%;
|
|
586
|
+
padding: 10px 12px;
|
|
587
|
+
border: none;
|
|
588
|
+
border-radius: var(--aks-radius);
|
|
589
|
+
background: var(--aks-accent, #c45c26);
|
|
590
|
+
color: #fff;
|
|
591
|
+
font-weight: 600;
|
|
592
|
+
box-shadow: none;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
button.side-rail-bulk-submit:disabled {
|
|
596
|
+
opacity: 0.5;
|
|
597
|
+
cursor: default;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.side-rail-bulk-report {
|
|
601
|
+
font-size: 12px;
|
|
602
|
+
color: var(--aks-brand);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.side-rail-bulk-errors {
|
|
606
|
+
margin: 6px 0 0;
|
|
607
|
+
padding-left: 18px;
|
|
608
|
+
}
|
|
609
|
+
|
|
536
610
|
.side-rail-filters {
|
|
537
611
|
display: grid;
|
|
538
612
|
gap: 12px;
|