@svadmin/ui 0.24.1 → 0.25.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -28,7 +28,7 @@
28
28
  import * as DropdownMenu from './ui/dropdown-menu/index.js';
29
29
  import * as PaginationUI from './ui/pagination/index.js';
30
30
  import * as ContextMenu from './ui/context-menu/index.js';
31
- import { Select } from './ui/select/index.js';
31
+ import { Pagination } from './ui/pagination/index.js';
32
32
  import {
33
33
  Plus, Pencil, Trash2,
34
34
  Search, Download, Upload, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
@@ -60,6 +60,8 @@
60
60
  pagination?: { current: number; pageSize: number };
61
61
  /** Externally controlled sorters */
62
62
  sorters?: Sort[];
63
+ /** Custom batch actions to render when rows are selected */
64
+ batchActions?: Snippet<[{ selectedIds: string[] }]>;
63
65
  }
64
66
 
65
67
  let {
@@ -71,6 +73,7 @@
71
73
  rowActions,
72
74
  emptyState,
73
75
  expandedRowRender,
76
+ batchActions,
74
77
  pagination: externalPagination,
75
78
  sorters: externalSorters,
76
79
  }: Props = $props();
@@ -353,10 +356,20 @@
353
356
  </Button>
354
357
  {/if}
355
358
  {#if selectedCount > 0 && canDelete}
356
- <Button variant="destructive" size="sm" onclick={confirmBatchDelete}>
359
+ <Button
360
+ variant="destructive"
361
+ size="sm"
362
+ class="h-9 whitespace-nowrap"
363
+ onclick={confirmBatchDelete}
364
+ >
357
365
  <Trash2 class="h-4 w-4" data-icon="inline-start" /> {t('common.batchDelete', { count: selectedCount })}
358
366
  </Button>
359
367
  {/if}
368
+
369
+ {#if selectedCount > 0 && batchActions}
370
+ {@render batchActions({ selectedIds: Object.keys(rowSelection) })}
371
+ {/if}
372
+
360
373
  <!-- Column Visibility Picker (DropdownMenu) -->
361
374
  <DropdownMenu.Root>
362
375
  <DropdownMenu.Trigger>
@@ -423,21 +436,23 @@
423
436
  <div class="space-y-1">
424
437
  <label class="text-xs text-muted-foreground" for="filter-{field.key}">{field.label}</label>
425
438
  {#if field.type === 'select' && field.options}
426
- <Select
439
+ <select
427
440
  id="filter-{field.key}"
428
- class="h-9 text-sm w-full font-normal"
429
- bind:value={filterValues[field.key]}
441
+ class="h-9 text-sm w-full font-normal flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
442
+ value={filterValues[field.key] ?? ''}
443
+ onchange={(e) => filterValues[field.key] = (e.currentTarget as HTMLSelectElement).value}
430
444
  >
431
445
  <option value="">全部</option>
432
446
  {#each field.options as opt}
433
447
  <option value={opt.value}>{opt.label}</option>
434
448
  {/each}
435
- </Select>
449
+ </select>
436
450
  {:else}
437
451
  <Input
438
452
  id="filter-{field.key}"
439
453
  type="text"
440
- bind:value={filterValues[field.key]}
454
+ value={filterValues[field.key] ?? ''}
455
+ oninput={(e) => filterValues[field.key] = (e.currentTarget as HTMLInputElement).value}
441
456
  placeholder={field.label}
442
457
  class="h-9 text-sm"
443
458
  />
@@ -755,18 +770,21 @@
755
770
  <div class="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
756
771
  <div class="flex items-center gap-2">
757
772
  <span>{t('common.total', { total: query.data?.total ?? 0 })}</span>
758
- <Select
759
- class="hidden sm:inline-block h-8 w-auto text-xs"
773
+ <select
774
+ class="h-8 w-[70px] px-1 py-1 flex items-center justify-between rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
760
775
  value={String(pagination.pageSize ?? 10)}
761
- onchange={(e: Event) => {
776
+ onchange={(e) => {
762
777
  const size = Number((e.target as HTMLSelectElement).value);
763
- pagination = { ...pagination, pageSize: size, current: 1 };
778
+ if (!isNaN(size)) {
779
+ pagination = { ...pagination, pageSize: size, current: 1 };
780
+ }
764
781
  }}
765
782
  >
766
- {#each [10, 20, 50, 100] as size}
767
- <option value={size}>{size} / {t('common.perPage')}</option>
768
- {/each}
769
- </Select>
783
+ <option value="10">10</option>
784
+ <option value="20">20</option>
785
+ <option value="50">50</option>
786
+ <option value="100">100</option>
787
+ </select>
770
788
  </div>
771
789
  <PaginationUI.Root>
772
790
  <PaginationUI.Content>