@starasia/admin 8.2.9 → 8.4.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.
@@ -24,6 +24,7 @@ interface MobileFilterBarProps {
24
24
  pagination?: PaginationMeta;
25
25
  perPageKey: string;
26
26
  pageSizeOptions: number[];
27
+ defaultPerPage: number;
27
28
  showPagination: boolean;
28
29
  showResultCount: boolean;
29
30
  showPageSize: boolean;
@@ -0,0 +1,39 @@
1
+ import { FilterFieldConfig } from '../types';
2
+
3
+ export interface UseFilterBarControlsOptions {
4
+ /** Katalog filter yang SAMA dengan yang dipakai `<FilterBar>`. */
5
+ filterConfigs: FilterFieldConfig[];
6
+ /** Mirror key URL FilterBar (default sama dgn FilterBar). */
7
+ filterKey?: string;
8
+ activeFilterKey?: string;
9
+ pageKey?: string;
10
+ searchKey?: string;
11
+ }
12
+ export interface FilterBarControls {
13
+ /** Set nilai filter (replace). Array kosong / `[]` → hapus filter. */
14
+ set: (key: string, value: string | string[]) => void;
15
+ /** Tambah/lepas satu nilai (cocok untuk klik kartu shortcut). */
16
+ toggle: (key: string, value: string) => void;
17
+ /** Hapus filter ini sepenuhnya (nilai + pill). */
18
+ clear: (key: string) => void;
19
+ /** Reset semua filter + search. */
20
+ clearAll: () => void;
21
+ /** Apakah `value` sedang aktif pada filter `key` (untuk styling kartu aktif). */
22
+ isActive: (key: string, value: string) => boolean;
23
+ /** Nilai-nilai aktif sekarang pada filter `key`. */
24
+ values: (key: string) => string[];
25
+ /** Ada filter aktif (apa pun). */
26
+ hasActiveFilter: boolean;
27
+ }
28
+ /**
29
+ * Kontrol filter dari LUAR `<FilterBar>` — untuk shortcut/quick-filter
30
+ * (mis. summary card status yang saat diklik memfilter tabel).
31
+ *
32
+ * Sumber kebenaran sama dengan FilterBar: URL. Jadi begitu helper ini menulis,
33
+ * pill FilterBar, tabel, dan `useFilterBarApiParams` ikut otomatis — tanpa
34
+ * lifting state. Companion arah-tulis dari `useFilterBarApiParams` (arah-baca).
35
+ *
36
+ * Khusus untuk filter select-type (`dropdown` / `dropdown-multiple` /
37
+ * `checkbox-multiple` / `radio`). Field lain (date) diabaikan.
38
+ */
39
+ export declare const useFilterBarControls: ({ filterConfigs, filterKey, activeFilterKey, pageKey, searchKey }: UseFilterBarControlsOptions) => FilterBarControls;
@@ -3,7 +3,7 @@ import { FilterStorageAdapter } from '../types';
3
3
  interface UseLastFilterStateOptions {
4
4
  scopeId?: string;
5
5
  storage: FilterStorageAdapter;
6
- /** URL params to snapshot (e.g. [activeFilterKey, filterKey, perPageKey]). */
6
+ /** URL params to snapshot (e.g. [activeFilterKey, filterKey, pageKey]). */
7
7
  snapshotKeys: string[];
8
8
  /** Opt-out toggle (default true when scopeId is provided). */
9
9
  enabled?: boolean;
@@ -11,17 +11,20 @@ interface UseLastFilterStateOptions {
11
11
  /**
12
12
  * Persists the latest filter URL params per scope, so link-based back
13
13
  * navigation (which drops the query string) still restores the previous
14
- * filters on remount. URL always wins — restoration only kicks in when the
15
- * URL has none of the snapshot keys.
14
+ * filters. URL always wins.
16
15
  *
17
- * - **Autosave**: writes to `localStorage[filterbar:last:{scopeId}]` on every
18
- * `searchParams` change.
19
- * - **Autorestore**: on mount, hydrates the URL from storage if empty.
20
- * - **`clear()`**: removes the snapshot; call this from Reset so the user's
21
- * explicit reset isn't undone on the next navigation back.
16
+ * NOTE: restore-ke-URL TIDAK dilakukan di sini — dikoordinasikan oleh
17
+ * `useUrlHydration` (satu `setSearchParams` gabungan) supaya tidak bentrok
18
+ * dengan restore perPage. Hook ini hanya autosave + expose `read()` + `clear()`.
19
+ *
20
+ * - **Autosave**: writes to `filterbar:last:{scopeId}` on every URL change.
21
+ * Snapshot KOSONG tidak ditulis (agar tidak menghapus snapshot saat URL
22
+ * kosong transient / pindah route).
23
+ * - **`clear()`**: removes the snapshot (dipanggil dari Reset).
22
24
  */
23
25
  export declare function useLastFilterState({ scopeId, storage, snapshotKeys, enabled }: UseLastFilterStateOptions): {
24
26
  enabled: boolean;
27
+ read: () => Record<string, string> | null;
25
28
  clear: () => void;
26
29
  };
27
30
  export {};
@@ -0,0 +1,23 @@
1
+ import { FilterStorageAdapter } from '../types';
2
+
3
+ interface UsePerPagePrefOptions {
4
+ scopeId?: string;
5
+ storage: FilterStorageAdapter;
6
+ perPageKey: string;
7
+ /** When provided, the restored value is validated against this list. */
8
+ pageSizeOptions?: number[];
9
+ /** Opt-out toggle (default true when scopeId is provided). */
10
+ enabled?: boolean;
11
+ }
12
+ /**
13
+ * Persists the rows-per-page choice independently of the filter snapshot.
14
+ *
15
+ * UI preference — NOT wiped by Reset. Restore-ke-URL dikoordinasikan oleh
16
+ * `useUrlHydration`; hook ini hanya autosave + expose `read()` + `clear()`.
17
+ */
18
+ export declare function usePerPagePref({ scopeId, storage, perPageKey, pageSizeOptions, enabled }: UsePerPagePrefOptions): {
19
+ enabled: boolean;
20
+ read: () => string | null;
21
+ clear: () => void;
22
+ };
23
+ export {};
@@ -0,0 +1,23 @@
1
+ interface UseUrlHydrationOptions {
2
+ enabled: boolean;
3
+ /** Keys yang dianggap "filter snapshot" (mis. [filters, filter, page]). */
4
+ snapshotKeys: string[];
5
+ perPageKey: string;
6
+ /** Snapshot filter tersimpan (atau null). */
7
+ readSnapshot: () => Record<string, string> | null;
8
+ /** Page size tersimpan (string) atau null. */
9
+ readPerPage: () => string | null;
10
+ }
11
+ /**
12
+ * Hidrasi URL dari preferensi tersimpan. Restore filter-snapshot & perPage
13
+ * digabung lewat micro-batcher → tidak saling menimpa, dan tahan banting untuk
14
+ * >1 FilterBar dalam satu halaman.
15
+ *
16
+ * Jalan saat "tiba" di halaman: mount ATAU `location.pathname` berubah (navigasi
17
+ * balik), jadi halaman yang di-keep-alive router tetap ter-restore tanpa hard
18
+ * refresh. Di-gate ke perubahan pathname agar TIDAK loop saat user mengubah
19
+ * filter di halaman yang sama. URL selalu menang (key yang sudah ada di URL
20
+ * tidak ditimpa).
21
+ */
22
+ export declare function useUrlHydration({ enabled, snapshotKeys, perPageKey, readSnapshot, readPerPage }: UseUrlHydrationOptions): void;
23
+ export {};
@@ -1,5 +1,6 @@
1
1
  export { FilterBar } from './FilterBar';
2
2
  export { useFilterBarApiParams, type UseFilterBarApiParamsOptions, type FilterBarApiParams } from './hooks/useFilterBarApiParams';
3
+ export { useFilterBarControls, type UseFilterBarControlsOptions, type FilterBarControls } from './hooks/useFilterBarControls';
3
4
  export { expandDateCompact, type ExpandDateCompactOptions } from './utils/expand-date-compact';
4
5
  export { localStorageAdapter, storageKeys } from './utils/storage-adapter';
5
6
  export type { FilterBarProps, FilterFieldConfig, FilterFieldType, SelectFilterField, CheckboxMultipleFilterField, DateFilterField, DateCompactFilterField, DateCompactOp, DateCompactValue, RelativeDirection, RelativePeriod, ColumnConfig, ColumnPrefState, SavedView, FilterStorageAdapter } from './types';
@@ -132,6 +132,12 @@ export interface FilterBarProps {
132
132
  perPageKey?: string;
133
133
  /** Page-size choices. Default `[10, 25, 50, 100]`. */
134
134
  pageSizeOptions?: number[];
135
+ /**
136
+ * Initial page size used when the URL (and the persisted preference under
137
+ * `savedViews.scopeId`) is empty. Defaults to `pageSizeOptions[0]`, so
138
+ * `pageSizeOptions={[25, 50, 100]}` naturally defaults to 25.
139
+ */
140
+ defaultPerPage?: number;
135
141
  showPagination?: boolean;
136
142
  showResultCount?: boolean;
137
143
  /** Show the rows-per-page selector. Default `true`. */
@@ -146,15 +152,24 @@ export interface FilterBarProps {
146
152
  * `onColumnsChange`. `frozenKey` & field lain di luar order/hidden diabaikan.
147
153
  */
148
154
  columnsState?: Partial<ColumnPrefState>;
149
- /** Enable named saved views. `scopeId` namespaces persisted data per table. */
155
+ /**
156
+ * Namespace persistensi per tabel — dipakai untuk column prefs, last-filter
157
+ * snapshot, perPage, & saved views. WAJIB diisi agar fitur persist aktif.
158
+ * Boleh juga lewat `savedViews.scopeId` (kompatibilitas lama); kalau dua-duanya
159
+ * ada, prop top-level ini menang.
160
+ */
161
+ scopeId?: string;
162
+ /**
163
+ * Saved views. `enabled` mengontrol tampil/sembunyinya tombol Saved Views.
164
+ * `scopeId` di sini opsional & deprecated — pakai prop `scopeId` top-level.
165
+ */
150
166
  savedViews?: {
151
167
  enabled?: boolean;
152
- scopeId: string;
168
+ scopeId?: string;
153
169
  };
154
170
  /**
155
171
  * Auto-restore the last applied filter state when the URL has no filter
156
- * params (e.g. link-based back navigation). Requires `savedViews.scopeId`.
157
- * Default `true`.
172
+ * params (e.g. link-based back navigation). Requires `scopeId`. Default `true`.
158
173
  */
159
174
  persistLastState?: boolean;
160
175
  /** Persistence adapter (default: localStorage via @starasia/storage). */
@@ -13,5 +13,6 @@ export declare const storageKeys: {
13
13
  columns: (scopeId: string) => string;
14
14
  savedViews: (scopeId: string) => string;
15
15
  lastState: (scopeId: string) => string;
16
+ perPage: (scopeId: string) => string;
16
17
  tableSort: (scopeId: string) => string;
17
18
  };
@@ -1,4 +1,4 @@
1
1
  import { default as React } from 'react';
2
2
  import { TableLayoutProps } from './types';
3
3
 
4
- export declare const TableLayout: <T>({ columns, data, getRowKey, size, resizable, widthStorageKey, columnState, onColumnStateChange, columnMenu, sort, defaultSort, onSortChange, height, mobileLayout, mobileBreakpoint, groupSeparator, summary, emptyState }: TableLayoutProps<T>) => React.JSX.Element;
4
+ export declare const TableLayout: <T>({ columns, data, getRowKey, size, resizable, widthStorageKey, columnState, onColumnStateChange, columnMenu, sort, defaultSort, onSortChange, height, mobileLayout, mobileBreakpoint, groupSeparator, summary, emptyState, rowActions, }: TableLayoutProps<T>) => React.JSX.Element;
@@ -1,4 +1,4 @@
1
1
  export { TableLayout } from './TableLayout';
2
2
  export { ColumnMenu } from './ColumnMenu';
3
3
  export { useTableSortApiParams, type UseTableSortApiParamsOptions, type TableSortApiParams } from './hooks/useTableSortApiParams';
4
- export type { TableLayoutProps, TableLayoutColumn, TableLayoutAlign, TableLayoutColumnState, TableLayoutCellProps, TableLayoutSort, TableLayoutSortDirection } from './types';
4
+ export type { TableLayoutProps, TableLayoutColumn, TableLayoutAlign, TableLayoutColumnState, TableLayoutCellProps, TableLayoutSort, TableLayoutSortDirection, TableLayoutRowAction } from './types';
@@ -1,6 +1,32 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { IconName } from '@starasia/icon';
3
3
 
4
+ export interface TableLayoutRowAction<T> {
5
+ key: string;
6
+ /** icon dari @starasia/icon (opsional) */
7
+ icon?: IconName;
8
+ /** teks tombol (mobile + kebab) + aria-label */
9
+ label: string;
10
+ /** Tooltip saat hover tombol. Default tidak ada tooltip — harus diset eksplisit. */
11
+ tooltip?: string;
12
+ /** Tampilkan teks label di sebelah icon (default false = icon only) */
13
+ showLabel?: boolean;
14
+ onClick: (row: T, index: number) => void;
15
+ /** return true → action tidak tampil untuk row ini */
16
+ hidden?: (row: T) => boolean;
17
+ /** return true → tombol dinonaktifkan */
18
+ disabled?: (row: T) => boolean;
19
+ /**
20
+ * Variant Button dari @starasia/button (default 'tertiary').
21
+ * Gunakan 'tertiary' untuk aksi normal, sesuaikan untuk aksi utama/destruktif.
22
+ */
23
+ variant?: 'primary' | 'secondary' | 'tertiary' | 'outline' | 'link';
24
+ /**
25
+ * Tone/warna Button dari @starasia/button.
26
+ * Contoh: 'error' untuk hapus, 'success' untuk approve.
27
+ */
28
+ tone?: 'brand' | 'error' | 'success' | 'info' | 'neutral' | 'warning';
29
+ }
4
30
  export type TableLayoutAlign = 'left' | 'center' | 'right';
5
31
  export type TableLayoutSortDirection = 'asc' | 'desc';
6
32
  export interface TableLayoutSort {
@@ -88,6 +114,13 @@ export interface TableLayoutProps<T> {
88
114
  columns: TableLayoutColumn<T>[];
89
115
  /** baris data (wajib) */
90
116
  data: T[];
117
+ /**
118
+ * Action buttons per baris — muncul sebagai overlay di atas row saat hover
119
+ * (Desktop/pointer: tersembunyi, fade-in saat hover · Touch: selalu terlihat).
120
+ * ≤ 5 visible action → icon buttons inline.
121
+ * > 5 visible action → kebab menu ⋮.
122
+ */
123
+ rowActions?: TableLayoutRowAction<T>[];
91
124
  /** key unik per row; default pakai index */
92
125
  getRowKey?: (row: T, index: number) => string | number;
93
126
  /** padding scale, diteruskan ke primitif Table (default 'md') */