@mshuja288/aksuicore 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/aksui.css +59 -0
- package/dist/index.cjs +83 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +83 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/patterns/RecordWorkspace.tsx +82 -0
- package/src/patterns/index.ts +6 -1
- package/src/primitives/ConfirmDeleteDialog.tsx +34 -0
- package/src/primitives/index.ts +1 -0
- package/src/theme/patterns.css +59 -0
|
@@ -25,6 +25,15 @@ export type TableColumn<T> = {
|
|
|
25
25
|
filterPlaceholder?: string;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
/** Formats supported by the shared table download control (matches akscore TableExport). */
|
|
29
|
+
export type ExportDownloadFormat = "csv" | "xlsx" | "json";
|
|
30
|
+
|
|
31
|
+
const EXPORT_FORMAT_OPTIONS: { format: ExportDownloadFormat; label: string }[] = [
|
|
32
|
+
{ format: "csv", label: "CSV" },
|
|
33
|
+
{ format: "xlsx", label: "Excel" },
|
|
34
|
+
{ format: "json", label: "JSON" },
|
|
35
|
+
];
|
|
36
|
+
|
|
28
37
|
type SortDirection = "asc" | "desc";
|
|
29
38
|
type SortState = { key: string; direction: SortDirection };
|
|
30
39
|
type DetailLine = { label: string; value: string };
|
|
@@ -105,6 +114,16 @@ function PreviewIcon() {
|
|
|
105
114
|
);
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
function DownloadIcon() {
|
|
118
|
+
return (
|
|
119
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
120
|
+
<path d="M10 3.5v8.2" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
121
|
+
<path d="M6.5 9.2 10 12.8l3.5-3.6" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
|
122
|
+
<path d="M4 15.5h12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
123
|
+
</svg>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
function DetailList({ lines }: { lines: DetailLine[] }) {
|
|
109
128
|
return (
|
|
110
129
|
<dl className="record-detail-list">
|
|
@@ -132,6 +151,9 @@ export function RecordWorkspace<T>({
|
|
|
132
151
|
onDelete,
|
|
133
152
|
canUpdate,
|
|
134
153
|
canDelete,
|
|
154
|
+
onDownload,
|
|
155
|
+
canDownload = true,
|
|
156
|
+
downloadBusy = false,
|
|
135
157
|
toolbar,
|
|
136
158
|
form,
|
|
137
159
|
}: {
|
|
@@ -148,6 +170,10 @@ export function RecordWorkspace<T>({
|
|
|
148
170
|
onDelete?: (item: T) => void;
|
|
149
171
|
canUpdate?: (item: T) => boolean;
|
|
150
172
|
canDelete?: (item: T) => boolean;
|
|
173
|
+
/** When set, shows CSV / Excel / JSON download controls in the side rail. */
|
|
174
|
+
onDownload?: (format: ExportDownloadFormat) => void;
|
|
175
|
+
canDownload?: boolean;
|
|
176
|
+
downloadBusy?: boolean;
|
|
151
177
|
toolbar?: ReactNode;
|
|
152
178
|
/** Slot for Add/Update/View dialogs owned by the host app. */
|
|
153
179
|
form?: ReactNode;
|
|
@@ -156,6 +182,7 @@ export function RecordWorkspace<T>({
|
|
|
156
182
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
157
183
|
const [sort, setSort] = useState<SortState | null>(null);
|
|
158
184
|
const [sideOpen, setSideOpen] = useState(false);
|
|
185
|
+
const [downloadOpen, setDownloadOpen] = useState(false);
|
|
159
186
|
const [previewEnabled, setPreviewEnabled] = useState(false);
|
|
160
187
|
const [previewKey, setPreviewKey] = useState<string | number | null>(null);
|
|
161
188
|
const [tooltip, setTooltip] = useState<TooltipState | null>(null);
|
|
@@ -205,9 +232,18 @@ export function RecordWorkspace<T>({
|
|
|
205
232
|
const start = showAll ? 0 : (currentPage - 1) * numericPageSize;
|
|
206
233
|
const pageItems = showAll ? sorted : sorted.slice(start, start + numericPageSize);
|
|
207
234
|
const showAdd = canAdd && Boolean(onAdd);
|
|
235
|
+
const showDownload = Boolean(onDownload) && canDownload;
|
|
208
236
|
const showFilters = filterableColumns.length > 0;
|
|
209
237
|
const previewItem = previewKey === null ? null : sorted.find((item) => rowKey(item) === previewKey) ?? null;
|
|
210
238
|
|
|
239
|
+
function requestDownload(format: ExportDownloadFormat) {
|
|
240
|
+
if (!onDownload || downloadBusy) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
onDownload(format);
|
|
244
|
+
setDownloadOpen(false);
|
|
245
|
+
}
|
|
246
|
+
|
|
211
247
|
function cellText(column: TableColumn<T>, item: T) {
|
|
212
248
|
const raw = column.display ? column.display(item) : column.value(item);
|
|
213
249
|
return raw === null || raw === undefined || raw === "" ? "—" : String(raw);
|
|
@@ -463,6 +499,36 @@ export function RecordWorkspace<T>({
|
|
|
463
499
|
</button>
|
|
464
500
|
) : null}
|
|
465
501
|
|
|
502
|
+
{showDownload ? (
|
|
503
|
+
<div className="side-rail-download">
|
|
504
|
+
<button
|
|
505
|
+
type="button"
|
|
506
|
+
className="ghost side-rail-download-toggle"
|
|
507
|
+
aria-expanded={downloadOpen}
|
|
508
|
+
disabled={downloadBusy}
|
|
509
|
+
onClick={() => setDownloadOpen((open) => !open)}
|
|
510
|
+
>
|
|
511
|
+
<DownloadIcon />
|
|
512
|
+
<span>{downloadBusy ? "Downloading…" : "Download"}</span>
|
|
513
|
+
</button>
|
|
514
|
+
{downloadOpen ? (
|
|
515
|
+
<div className="side-rail-download-formats" role="group" aria-label="Download format">
|
|
516
|
+
{EXPORT_FORMAT_OPTIONS.map((option) => (
|
|
517
|
+
<button
|
|
518
|
+
key={option.format}
|
|
519
|
+
type="button"
|
|
520
|
+
className="ghost side-rail-download-format"
|
|
521
|
+
disabled={downloadBusy}
|
|
522
|
+
onClick={() => requestDownload(option.format)}
|
|
523
|
+
>
|
|
524
|
+
{option.label}
|
|
525
|
+
</button>
|
|
526
|
+
))}
|
|
527
|
+
</div>
|
|
528
|
+
) : null}
|
|
529
|
+
</div>
|
|
530
|
+
) : null}
|
|
531
|
+
|
|
466
532
|
{showFilters ? (
|
|
467
533
|
<div className="side-rail-filters">
|
|
468
534
|
<div className="filter-panel-head">
|
|
@@ -519,6 +585,22 @@ export function RecordWorkspace<T>({
|
|
|
519
585
|
<PlusIcon />
|
|
520
586
|
</button>
|
|
521
587
|
) : null}
|
|
588
|
+
{showDownload ? (
|
|
589
|
+
<button
|
|
590
|
+
type="button"
|
|
591
|
+
className="ghost side-rail-icon"
|
|
592
|
+
title="Download"
|
|
593
|
+
aria-label="Download"
|
|
594
|
+
aria-expanded={downloadOpen}
|
|
595
|
+
disabled={downloadBusy}
|
|
596
|
+
onClick={() => {
|
|
597
|
+
setSideOpen(true);
|
|
598
|
+
setDownloadOpen(true);
|
|
599
|
+
}}
|
|
600
|
+
>
|
|
601
|
+
<DownloadIcon />
|
|
602
|
+
</button>
|
|
603
|
+
) : null}
|
|
522
604
|
{showFilters ? (
|
|
523
605
|
<button
|
|
524
606
|
type="button"
|
package/src/patterns/index.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export { AppShell, type AppShellProps } from "./AppShell";
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
RecordWorkspace,
|
|
4
|
+
RecordTable,
|
|
5
|
+
type TableColumn,
|
|
6
|
+
type ExportDownloadFormat,
|
|
7
|
+
} from "./RecordWorkspace";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { Dialog } from "./Dialog";
|
|
5
|
+
|
|
6
|
+
export function ConfirmDeleteDialog({
|
|
7
|
+
open,
|
|
8
|
+
message,
|
|
9
|
+
pending = false,
|
|
10
|
+
onCancel,
|
|
11
|
+
onConfirm,
|
|
12
|
+
}: {
|
|
13
|
+
open: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
pending?: boolean;
|
|
16
|
+
onCancel: () => void;
|
|
17
|
+
onConfirm: () => void;
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<Dialog title="Confirm delete" open={open} onClose={pending ? () => undefined : onCancel} wide={false}>
|
|
21
|
+
<div className="card-form">
|
|
22
|
+
<p className="hint">{message}</p>
|
|
23
|
+
<div className="actions">
|
|
24
|
+
<Button variant="ghost" onClick={onCancel} disabled={pending}>
|
|
25
|
+
Cancel
|
|
26
|
+
</Button>
|
|
27
|
+
<Button variant="danger" onClick={onConfirm} disabled={pending}>
|
|
28
|
+
{pending ? "Deleting…" : "Delete"}
|
|
29
|
+
</Button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</Dialog>
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/primitives/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { Button, type ButtonVariant } from "./Button";
|
|
|
2
2
|
export { Select, AppSelect, type SelectOption, type AppSelectOption } from "./Select";
|
|
3
3
|
export { DateField } from "./DateField";
|
|
4
4
|
export { Dialog, FormDialog } from "./Dialog";
|
|
5
|
+
export { ConfirmDeleteDialog } from "./ConfirmDeleteDialog";
|
|
5
6
|
export { Snackbar, StatusMessage } from "./Snackbar";
|
|
6
7
|
export { Topbar } from "./Topbar";
|
|
7
8
|
export { SideNav, type SideNavItem } from "./SideNav";
|
package/src/theme/patterns.css
CHANGED
|
@@ -404,6 +404,65 @@ button.side-rail-add:hover {
|
|
|
404
404
|
background: #2a3546;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
.side-rail-download {
|
|
408
|
+
display: grid;
|
|
409
|
+
gap: 8px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
button.side-rail-download-toggle {
|
|
413
|
+
display: inline-flex;
|
|
414
|
+
align-items: center;
|
|
415
|
+
justify-content: center;
|
|
416
|
+
gap: 8px;
|
|
417
|
+
width: 100%;
|
|
418
|
+
padding: 10px 12px;
|
|
419
|
+
border: 1px solid var(--aks-border, #d7dde5);
|
|
420
|
+
border-radius: var(--aks-radius);
|
|
421
|
+
background: #fff;
|
|
422
|
+
color: var(--aks-brand);
|
|
423
|
+
box-shadow: none;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
button.side-rail-download-toggle svg {
|
|
427
|
+
width: 18px;
|
|
428
|
+
height: 18px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
button.side-rail-download-toggle:hover:not(:disabled) {
|
|
432
|
+
background: #f5f7fa;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
button.side-rail-download-toggle:disabled {
|
|
436
|
+
opacity: 0.55;
|
|
437
|
+
cursor: default;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.side-rail-download-formats {
|
|
441
|
+
display: grid;
|
|
442
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
443
|
+
gap: 6px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
button.side-rail-download-format {
|
|
447
|
+
padding: 8px 6px;
|
|
448
|
+
border: 1px solid var(--aks-border, #d7dde5);
|
|
449
|
+
border-radius: var(--aks-radius);
|
|
450
|
+
background: #fff;
|
|
451
|
+
color: var(--aks-brand);
|
|
452
|
+
font-size: 12px;
|
|
453
|
+
font-weight: 600;
|
|
454
|
+
box-shadow: none;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
button.side-rail-download-format:hover:not(:disabled) {
|
|
458
|
+
background: #f5f7fa;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
button.side-rail-download-format:disabled {
|
|
462
|
+
opacity: 0.45;
|
|
463
|
+
cursor: default;
|
|
464
|
+
}
|
|
465
|
+
|
|
407
466
|
.side-rail-filters {
|
|
408
467
|
display: grid;
|
|
409
468
|
gap: 12px;
|