@dimaan/ui 0.0.18 → 0.0.19

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/index.d.cts CHANGED
@@ -1468,8 +1468,16 @@ interface TableProps<T> {
1468
1468
  data: readonly T[];
1469
1469
  /** Column definitions. */
1470
1470
  columns: ReadonlyArray<Column<T>>;
1471
- /** Stable id for each row. Required for selection; recommended always. */
1472
- getRowId: (row: T, index: number) => string;
1471
+ /**
1472
+ * Stable id for each row. Required for selection; recommended always.
1473
+ *
1474
+ * **Optional with a default**: when omitted, Table reads `row.id` (most
1475
+ * APIs use it) and falls back to the row index. Provide this explicitly
1476
+ * for records that use a different identifier (`_id`, `uuid`) or any
1477
+ * time the list can reorder — index-based ids break selection across
1478
+ * reorders.
1479
+ */
1480
+ getRowId?: (row: T, index: number) => string;
1473
1481
  /**
1474
1482
  * Total row count across all pages. Pass this when the parent paginates/sorts
1475
1483
  * server-side — Table will skip its local sort + slice.
@@ -1576,14 +1584,40 @@ interface ListPageProps<T> {
1576
1584
  actions?: ReactNode;
1577
1585
  data: T[];
1578
1586
  columns: Column<T>[];
1579
- getRowId: (row: T) => string;
1587
+ /**
1588
+ * Stable id for each row. Required for row selection across re-renders and
1589
+ * for stable React keys.
1590
+ *
1591
+ * Defaults to reading `row.id` when present, falling back to the row index.
1592
+ * Provide this explicitly when your records use a different identifier
1593
+ * (e.g. `_id`, `uuid`, a composite key) — index-based ids break selection
1594
+ * when the data list reorders.
1595
+ */
1596
+ getRowId?: (row: T) => string;
1580
1597
  /** Show skeleton rows in the table area while data is fetching. */
1581
1598
  isLoading?: boolean;
1582
1599
  /** Number of skeleton rows rendered while loading. Defaults to the table page size. */
1583
1600
  loadingRowCount?: number;
1584
1601
  /** Keys on `T` to search. Search input only renders when this is provided. */
1585
1602
  searchKeys?: Array<keyof T>;
1603
+ /**
1604
+ * Controlled search input value. Pair with `onSearchChange` to drive the
1605
+ * search from outside (typical for server-side filtering). When both are
1606
+ * provided, ListPage **stops filtering `data` locally** for search —
1607
+ * `data` is trusted to already match the query.
1608
+ */
1609
+ searchValue?: string;
1610
+ /** Fires on every search input change. Pair with `searchValue` for controlled mode. */
1611
+ onSearchChange?: (value: string) => void;
1586
1612
  filters?: ListPageFilter<T>[];
1613
+ /**
1614
+ * Controlled filter values keyed by `filter.key`. When provided alongside
1615
+ * `onFilterChange`, ListPage **stops filtering `data` locally** — `data`
1616
+ * is trusted to already match the active filter selections.
1617
+ */
1618
+ filterValues?: Record<string, string>;
1619
+ /** Fires when any filter Select changes. Pair with `filterValues` for controlled mode. */
1620
+ onFilterChange?: (key: string, value: string) => void;
1587
1621
  enableRowSelection?: boolean;
1588
1622
  bulkActions?: (selected: T[]) => ReactNode;
1589
1623
  /**
@@ -1686,7 +1720,7 @@ interface ListPageProps<T> {
1686
1720
  * />
1687
1721
  * ```
1688
1722
  */
1689
- declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, searchKeys, filters, enableRowSelection, bulkActions, pagination, defaultPagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
1723
+ declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, searchKeys, searchValue: searchValueProp, onSearchChange, filters, filterValues: filterValuesProp, onFilterChange, enableRowSelection, bulkActions, pagination, defaultPagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
1690
1724
 
1691
1725
  type RadioGroupSize = 'sm' | 'md' | 'lg';
1692
1726
  /** Outer circle (radio button itself) — sized + bordered. */
package/dist/index.d.ts CHANGED
@@ -1468,8 +1468,16 @@ interface TableProps<T> {
1468
1468
  data: readonly T[];
1469
1469
  /** Column definitions. */
1470
1470
  columns: ReadonlyArray<Column<T>>;
1471
- /** Stable id for each row. Required for selection; recommended always. */
1472
- getRowId: (row: T, index: number) => string;
1471
+ /**
1472
+ * Stable id for each row. Required for selection; recommended always.
1473
+ *
1474
+ * **Optional with a default**: when omitted, Table reads `row.id` (most
1475
+ * APIs use it) and falls back to the row index. Provide this explicitly
1476
+ * for records that use a different identifier (`_id`, `uuid`) or any
1477
+ * time the list can reorder — index-based ids break selection across
1478
+ * reorders.
1479
+ */
1480
+ getRowId?: (row: T, index: number) => string;
1473
1481
  /**
1474
1482
  * Total row count across all pages. Pass this when the parent paginates/sorts
1475
1483
  * server-side — Table will skip its local sort + slice.
@@ -1576,14 +1584,40 @@ interface ListPageProps<T> {
1576
1584
  actions?: ReactNode;
1577
1585
  data: T[];
1578
1586
  columns: Column<T>[];
1579
- getRowId: (row: T) => string;
1587
+ /**
1588
+ * Stable id for each row. Required for row selection across re-renders and
1589
+ * for stable React keys.
1590
+ *
1591
+ * Defaults to reading `row.id` when present, falling back to the row index.
1592
+ * Provide this explicitly when your records use a different identifier
1593
+ * (e.g. `_id`, `uuid`, a composite key) — index-based ids break selection
1594
+ * when the data list reorders.
1595
+ */
1596
+ getRowId?: (row: T) => string;
1580
1597
  /** Show skeleton rows in the table area while data is fetching. */
1581
1598
  isLoading?: boolean;
1582
1599
  /** Number of skeleton rows rendered while loading. Defaults to the table page size. */
1583
1600
  loadingRowCount?: number;
1584
1601
  /** Keys on `T` to search. Search input only renders when this is provided. */
1585
1602
  searchKeys?: Array<keyof T>;
1603
+ /**
1604
+ * Controlled search input value. Pair with `onSearchChange` to drive the
1605
+ * search from outside (typical for server-side filtering). When both are
1606
+ * provided, ListPage **stops filtering `data` locally** for search —
1607
+ * `data` is trusted to already match the query.
1608
+ */
1609
+ searchValue?: string;
1610
+ /** Fires on every search input change. Pair with `searchValue` for controlled mode. */
1611
+ onSearchChange?: (value: string) => void;
1586
1612
  filters?: ListPageFilter<T>[];
1613
+ /**
1614
+ * Controlled filter values keyed by `filter.key`. When provided alongside
1615
+ * `onFilterChange`, ListPage **stops filtering `data` locally** — `data`
1616
+ * is trusted to already match the active filter selections.
1617
+ */
1618
+ filterValues?: Record<string, string>;
1619
+ /** Fires when any filter Select changes. Pair with `filterValues` for controlled mode. */
1620
+ onFilterChange?: (key: string, value: string) => void;
1587
1621
  enableRowSelection?: boolean;
1588
1622
  bulkActions?: (selected: T[]) => ReactNode;
1589
1623
  /**
@@ -1686,7 +1720,7 @@ interface ListPageProps<T> {
1686
1720
  * />
1687
1721
  * ```
1688
1722
  */
1689
- declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, searchKeys, filters, enableRowSelection, bulkActions, pagination, defaultPagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
1723
+ declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, searchKeys, searchValue: searchValueProp, onSearchChange, filters, filterValues: filterValuesProp, onFilterChange, enableRowSelection, bulkActions, pagination, defaultPagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
1690
1724
 
1691
1725
  type RadioGroupSize = 'sm' | 'md' | 'lg';
1692
1726
  /** Outer circle (radio button itself) — sized + bordered. */
package/dist/index.js CHANGED
@@ -2164,11 +2164,15 @@ function useTableState(props) {
2164
2164
  };
2165
2165
  }
2166
2166
  var DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
2167
+ function defaultGetRowId(row, index) {
2168
+ const rowId = row.id;
2169
+ return rowId === void 0 || rowId === null ? String(index) : String(rowId);
2170
+ }
2167
2171
  function Table(props) {
2168
2172
  const {
2169
2173
  data,
2170
2174
  columns,
2171
- getRowId,
2175
+ getRowId = defaultGetRowId,
2172
2176
  enableRowSelection = false,
2173
2177
  isRowSelectable,
2174
2178
  bulkActions,
@@ -2493,7 +2497,11 @@ function ListPage({
2493
2497
  isLoading = false,
2494
2498
  loadingRowCount,
2495
2499
  searchKeys,
2500
+ searchValue: searchValueProp,
2501
+ onSearchChange,
2496
2502
  filters,
2503
+ filterValues: filterValuesProp,
2504
+ onFilterChange,
2497
2505
  enableRowSelection,
2498
2506
  bulkActions,
2499
2507
  pagination,
@@ -2514,14 +2522,30 @@ function ListPage({
2514
2522
  }
2515
2523
  return init;
2516
2524
  }, [filters]);
2517
- const [search, setSearch] = useState("");
2518
- const [filterValues, setFilterValues] = useState(initialFilterValues);
2525
+ const isSearchControlled = searchValueProp !== void 0;
2526
+ const isFiltersControlled = filterValuesProp !== void 0;
2527
+ const [internalSearch, setInternalSearch] = useState("");
2528
+ const [internalFilterValues, setInternalFilterValues] = useState(initialFilterValues);
2529
+ const search = isSearchControlled ? searchValueProp : internalSearch;
2530
+ const filterValues = isFiltersControlled ? filterValuesProp : internalFilterValues;
2531
+ const setSearch = (next) => {
2532
+ if (!isSearchControlled) setInternalSearch(next);
2533
+ onSearchChange?.(next);
2534
+ };
2519
2535
  const setFilter = (key, value) => {
2520
- setFilterValues((prev) => ({ ...prev, [key]: value }));
2536
+ if (!isFiltersControlled) {
2537
+ setInternalFilterValues((prev) => ({ ...prev, [key]: value }));
2538
+ }
2539
+ onFilterChange?.(key, value);
2521
2540
  };
2522
2541
  const reset = () => {
2523
- setSearch("");
2524
- setFilterValues(initialFilterValues);
2542
+ if (!isSearchControlled) setInternalSearch("");
2543
+ onSearchChange?.("");
2544
+ if (!isFiltersControlled) setInternalFilterValues(initialFilterValues);
2545
+ for (const f of filters ?? []) {
2546
+ const def = f.defaultValue ?? f.options[0]?.value ?? "";
2547
+ onFilterChange?.(f.key, def);
2548
+ }
2525
2549
  };
2526
2550
  const hasActiveFilters = useMemo(() => {
2527
2551
  if (search.trim() !== "") return true;
@@ -2534,7 +2558,7 @@ function ListPage({
2534
2558
  }, [search, filters, filterValues]);
2535
2559
  const filtered = useMemo(() => {
2536
2560
  return data.filter((row) => {
2537
- if (search.trim() && searchKeys && searchKeys.length > 0) {
2561
+ if (!isSearchControlled && search.trim() && searchKeys && searchKeys.length > 0) {
2538
2562
  const q = search.trim().toLowerCase();
2539
2563
  const matches = searchKeys.some((key) => {
2540
2564
  const val = row[key];
@@ -2542,16 +2566,18 @@ function ListPage({
2542
2566
  });
2543
2567
  if (!matches) return false;
2544
2568
  }
2545
- for (const f of filters ?? []) {
2546
- const value = filterValues[f.key];
2547
- const def = f.defaultValue ?? f.options[0]?.value ?? "";
2548
- if (value !== void 0 && value !== def) {
2549
- if (f.accessor(row) !== value) return false;
2569
+ if (!isFiltersControlled) {
2570
+ for (const f of filters ?? []) {
2571
+ const value = filterValues[f.key];
2572
+ const def = f.defaultValue ?? f.options[0]?.value ?? "";
2573
+ if (value !== void 0 && value !== def) {
2574
+ if (f.accessor(row) !== value) return false;
2575
+ }
2550
2576
  }
2551
2577
  }
2552
2578
  return true;
2553
2579
  });
2554
- }, [data, search, searchKeys, filters, filterValues]);
2580
+ }, [data, search, searchKeys, filters, filterValues, isSearchControlled, isFiltersControlled]);
2555
2581
  const showFilterBar = Boolean(searchKeys?.length) || Boolean(filters?.length);
2556
2582
  const tableMode = isLoading ? "loading" : data.length === 0 && !hasActiveFilters ? "no-data" : filtered.length === 0 ? "no-results" : "rows";
2557
2583
  return /* @__PURE__ */ jsxs("div", { "data-slot": "list-page", className: cn("space-y-6", className), children: [