@jinaan/uikit 0.1.21 → 0.1.23
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/dist/data-table.d.ts.map +1 -1
- package/dist/dropdown-menu.d.ts.map +1 -1
- package/dist/index.js +891 -837
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/data-table.tsx +106 -18
- package/src/dropdown-menu.tsx +4 -1
- package/src/select.tsx +1 -1
- package/src/utils.ts +11 -0
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ClassValue } from "clsx";
|
|
2
2
|
/** Merge conditional class names with Tailwind conflict resolution. */
|
|
3
3
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
4
|
+
/** Move the id at `from` so it sits at `to`; no-op when indexes match. */
|
|
5
|
+
export declare function reorderIds(ids: string[], from: number, to: number): string[];
|
|
4
6
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,uEAAuE;AACvE,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAElD"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,uEAAuE;AACvE,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAElD;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ5E"}
|
package/package.json
CHANGED
package/src/data-table.tsx
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
CaretUp,
|
|
4
4
|
CaretUpDown,
|
|
5
5
|
Columns as ColumnsIcon,
|
|
6
|
+
DotsSixVertical,
|
|
6
7
|
FunnelSimple,
|
|
7
8
|
Lock,
|
|
9
|
+
LockOpen,
|
|
8
10
|
MagnifyingGlass,
|
|
9
11
|
WarningCircle,
|
|
10
12
|
X,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
DropdownMenu,
|
|
20
22
|
DropdownMenuCheckboxItem,
|
|
21
23
|
DropdownMenuContent,
|
|
24
|
+
DropdownMenuChevron,
|
|
22
25
|
DropdownMenuItem,
|
|
23
26
|
DropdownMenuLabel,
|
|
24
27
|
DropdownMenuSeparator,
|
|
@@ -38,7 +41,7 @@ import {
|
|
|
38
41
|
type TableSurfaceAction,
|
|
39
42
|
type TableSurfaceTone,
|
|
40
43
|
} from "./table-surface";
|
|
41
|
-
import { cn } from "./utils";
|
|
44
|
+
import { cn, reorderIds } from "./utils";
|
|
42
45
|
|
|
43
46
|
export interface Column<T> {
|
|
44
47
|
id: string;
|
|
@@ -210,6 +213,10 @@ export function DataTable<T>({
|
|
|
210
213
|
columns.filter((c) => c.defaultHidden && !c.required).map((c) => [c.id, true]),
|
|
211
214
|
),
|
|
212
215
|
);
|
|
216
|
+
// Column order and runtime pins are per-mount, like `hidden`.
|
|
217
|
+
const [order, setOrder] = useState<string[] | null>(null);
|
|
218
|
+
const [locked, setLocked] = useState<string[]>([]);
|
|
219
|
+
const [dragId, setDragId] = useState<string | null>(null);
|
|
213
220
|
const [sort, setSort] = useState<{ id: string; dir: "asc" | "desc" } | null>(
|
|
214
221
|
defaultSort ?? null,
|
|
215
222
|
);
|
|
@@ -218,8 +225,36 @@ export function DataTable<T>({
|
|
|
218
225
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
219
226
|
const [activeFilterIds, setActiveFilterIds] = useState<string[]>([]);
|
|
220
227
|
|
|
221
|
-
const
|
|
222
|
-
const
|
|
228
|
+
const propOrder = columns.map((c) => c.id);
|
|
229
|
+
const byId = new Map(columns.map((c) => [c.id, c] as const));
|
|
230
|
+
const orderedColumns = order
|
|
231
|
+
? order.map((id) => byId.get(id)).filter((c): c is Column<T> => Boolean(c))
|
|
232
|
+
: columns;
|
|
233
|
+
const visibleColumns = orderedColumns.filter((c) => !hidden[c.id]);
|
|
234
|
+
const hiddenColumns = orderedColumns.filter((c) => hidden[c.id] && !c.required);
|
|
235
|
+
// ponytail: mouse-only drag reorder; add keyboard move (Alt+Arrow) if asked.
|
|
236
|
+
const isPinned = (c: Column<T>) => c.required || locked.includes(c.id);
|
|
237
|
+
const columnAt = (id: string) => byId.get(id);
|
|
238
|
+
|
|
239
|
+
const moveBefore = (draggedId: string, targetId: string) => {
|
|
240
|
+
const target = targetId ? columnAt(targetId) : undefined;
|
|
241
|
+
if (!target || isPinned(target) || draggedId === targetId) return;
|
|
242
|
+
setOrder((prev) => {
|
|
243
|
+
const ids = prev ?? propOrder;
|
|
244
|
+
return reorderIds(ids, ids.indexOf(draggedId), ids.indexOf(targetId));
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const showColumn = (id: string) => {
|
|
249
|
+
setHidden((prev) => ({ ...prev, [id]: false }));
|
|
250
|
+
// Re-showing appends at the end of the current order.
|
|
251
|
+
setOrder((prev) => (prev && prev.includes(id) ? prev : [...(prev ?? propOrder), id]));
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const togglePin = (id: string) =>
|
|
255
|
+
setLocked((prev) =>
|
|
256
|
+
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
|
|
257
|
+
);
|
|
223
258
|
const activeFilters = (filters ?? []).filter((f) => activeFilterIds.includes(f.id));
|
|
224
259
|
const filterSignature = (filters ?? [])
|
|
225
260
|
.map((f) => `${f.id}:${f.value.join(",")}`)
|
|
@@ -371,26 +406,79 @@ export function DataTable<T>({
|
|
|
371
406
|
variant="secondary"
|
|
372
407
|
size="md"
|
|
373
408
|
leadingIcon={<ColumnsIcon />}
|
|
374
|
-
trailingIcon={<
|
|
409
|
+
trailingIcon={<DropdownMenuChevron />}
|
|
375
410
|
>
|
|
376
411
|
Columns
|
|
377
412
|
</Button>
|
|
378
413
|
</DropdownMenuTrigger>
|
|
379
414
|
<DropdownMenuContent align="end" className="w-54">
|
|
380
415
|
<DropdownMenuLabel className="text-[13px]">Active columns</DropdownMenuLabel>
|
|
381
|
-
{visibleColumns.map((c) =>
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
416
|
+
{visibleColumns.map((c) => {
|
|
417
|
+
const pinned = isPinned(c);
|
|
418
|
+
return (
|
|
419
|
+
<DropdownMenuCheckboxItem
|
|
420
|
+
key={c.id}
|
|
421
|
+
checked
|
|
422
|
+
draggable={!pinned}
|
|
423
|
+
onDragStart={(e) => {
|
|
424
|
+
e.dataTransfer.effectAllowed = "move";
|
|
425
|
+
e.dataTransfer.setData("text/plain", c.id);
|
|
426
|
+
setDragId(c.id);
|
|
427
|
+
}}
|
|
428
|
+
onDragOver={(e) => {
|
|
429
|
+
if (dragId) {
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
moveBefore(dragId, c.id);
|
|
432
|
+
}
|
|
433
|
+
}}
|
|
434
|
+
onDrop={(e) => e.preventDefault()}
|
|
435
|
+
onDragEnd={() => setDragId(null)}
|
|
436
|
+
onCheckedChange={(next) => {
|
|
437
|
+
if (!next && !pinned) {
|
|
438
|
+
setHidden((prev) => ({ ...prev, [c.id]: true }));
|
|
439
|
+
}
|
|
440
|
+
}}
|
|
441
|
+
onSelect={(e) => e.preventDefault()}
|
|
442
|
+
className="justify-between"
|
|
443
|
+
>
|
|
444
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
445
|
+
<DotsSixVertical
|
|
446
|
+
aria-hidden
|
|
447
|
+
className={cn(
|
|
448
|
+
"size-3.5 shrink-0",
|
|
449
|
+
pinned
|
|
450
|
+
? "text-subtle/60"
|
|
451
|
+
: dragId === c.id
|
|
452
|
+
? "text-primary"
|
|
453
|
+
: "text-subtle",
|
|
454
|
+
)}
|
|
455
|
+
/>
|
|
456
|
+
<span className="truncate">{c.header}</span>
|
|
457
|
+
</span>
|
|
458
|
+
{c.required ? (
|
|
459
|
+
<Lock className="size-3.5 shrink-0 text-subtle" aria-hidden />
|
|
460
|
+
) : (
|
|
461
|
+
<button
|
|
462
|
+
type="button"
|
|
463
|
+
aria-label={pinned ? `Unlock ${c.header}` : `Lock ${c.header}`}
|
|
464
|
+
onClick={(e) => {
|
|
465
|
+
e.stopPropagation();
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
togglePin(c.id);
|
|
468
|
+
}}
|
|
469
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
470
|
+
className="-m-1 rounded p-1 text-subtle transition-colors hover:bg-(--menu-highlight) hover:text-fg"
|
|
471
|
+
>
|
|
472
|
+
{pinned ? (
|
|
473
|
+
<LockOpen className="size-3.5" aria-hidden />
|
|
474
|
+
) : (
|
|
475
|
+
<Lock className="size-3.5" aria-hidden />
|
|
476
|
+
)}
|
|
477
|
+
</button>
|
|
478
|
+
)}
|
|
479
|
+
</DropdownMenuCheckboxItem>
|
|
480
|
+
);
|
|
481
|
+
})}
|
|
394
482
|
{hiddenColumns.length > 0 ? (
|
|
395
483
|
<>
|
|
396
484
|
<DropdownMenuSeparator />
|
|
@@ -399,7 +487,7 @@ export function DataTable<T>({
|
|
|
399
487
|
<DropdownMenuCheckboxItem
|
|
400
488
|
key={c.id}
|
|
401
489
|
checked={false}
|
|
402
|
-
onCheckedChange={() =>
|
|
490
|
+
onCheckedChange={() => showColumn(c.id)}
|
|
403
491
|
onSelect={(e) => e.preventDefault()}
|
|
404
492
|
>
|
|
405
493
|
{c.header}
|
package/src/dropdown-menu.tsx
CHANGED
|
@@ -35,12 +35,15 @@ export function DropdownMenu({
|
|
|
35
35
|
export const DropdownMenuTrigger = forwardRef<
|
|
36
36
|
ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
|
37
37
|
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
|
|
38
|
-
>(({ disabled, ...props }, ref) => {
|
|
38
|
+
>(({ className, disabled, ...props }, ref) => {
|
|
39
39
|
const empty = useContext(DropdownMenuEmptyContext);
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<DropdownMenuPrimitive.Trigger
|
|
43
43
|
ref={ref}
|
|
44
|
+
// `group` lets DropdownMenuChevron rotate with the open state, even
|
|
45
|
+
// when the trigger renders through asChild onto a custom button.
|
|
46
|
+
className={cn("group", className)}
|
|
44
47
|
disabled={disabled || empty}
|
|
45
48
|
{...props}
|
|
46
49
|
/>
|
package/src/select.tsx
CHANGED
|
@@ -54,7 +54,7 @@ export const SelectTrigger = forwardRef<
|
|
|
54
54
|
>
|
|
55
55
|
<span className="min-w-0 flex-1 truncate text-left">{children}</span>
|
|
56
56
|
<SelectPrimitive.Icon asChild>
|
|
57
|
-
<CaretDown className="ml-auto size-3.5 shrink-0 text-
|
|
57
|
+
<CaretDown className="ml-auto size-3.5 shrink-0 text-muted transition-transform duration-200 ease-out group-data-[state=open]:rotate-180 motion-reduce:transition-none" />
|
|
58
58
|
</SelectPrimitive.Icon>
|
|
59
59
|
</SelectPrimitive.Trigger>
|
|
60
60
|
));
|
package/src/utils.ts
CHANGED
|
@@ -5,3 +5,14 @@ import { twMerge } from "tailwind-merge";
|
|
|
5
5
|
export function cn(...inputs: ClassValue[]): string {
|
|
6
6
|
return twMerge(clsx(inputs));
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
/** Move the id at `from` so it sits at `to`; no-op when indexes match. */
|
|
10
|
+
export function reorderIds(ids: string[], from: number, to: number): string[] {
|
|
11
|
+
if (from === to || from < 0 || to < 0 || from >= ids.length || to >= ids.length) {
|
|
12
|
+
return ids;
|
|
13
|
+
}
|
|
14
|
+
const next = [...ids];
|
|
15
|
+
const [moved] = next.splice(from, 1);
|
|
16
|
+
next.splice(to, 0, moved);
|
|
17
|
+
return next;
|
|
18
|
+
}
|