@dimaan/ui 0.0.24 → 0.0.27
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.cjs +128 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +128 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1628,6 +1628,17 @@ interface ListPageSelectFilter extends ListPageFilterBase {
|
|
|
1628
1628
|
interface ListPageTextFilter extends ListPageFilterBase {
|
|
1629
1629
|
type: 'text';
|
|
1630
1630
|
placeholder?: string;
|
|
1631
|
+
/**
|
|
1632
|
+
* Debounce window in milliseconds so a request fires only after the user
|
|
1633
|
+
* pauses typing — instead of on every keystroke. The displayed value still
|
|
1634
|
+
* updates instantly; only `onFilterChange` is delayed. **Live mode only**
|
|
1635
|
+
* (ignored when `filterMode="manual"`, which already emits on Apply).
|
|
1636
|
+
*
|
|
1637
|
+
* Defaults to `400` — text filters are debounced out of the box, so you don't
|
|
1638
|
+
* need to pass anything. Set a different number to tune it, or `0` to opt out
|
|
1639
|
+
* and emit on every keystroke.
|
|
1640
|
+
*/
|
|
1641
|
+
debounceMs?: number;
|
|
1631
1642
|
}
|
|
1632
1643
|
/** Date filter — rendered as a `DatePicker`. ISO `YYYY-MM-DD`; empty means "no filter". */
|
|
1633
1644
|
interface ListPageDateFilter extends ListPageFilterBase {
|
|
@@ -1667,6 +1678,8 @@ interface ListPageEmptyState {
|
|
|
1667
1678
|
interface ListPageLabels extends TableLabels {
|
|
1668
1679
|
/** "Reset filters" button label. */
|
|
1669
1680
|
reset?: string;
|
|
1681
|
+
/** "Apply" button label — shown in `manual` filter mode (the default). */
|
|
1682
|
+
apply?: string;
|
|
1670
1683
|
/** "No results matching filters" title. */
|
|
1671
1684
|
emptyTitle?: string;
|
|
1672
1685
|
/** "No results matching filters" description. */
|
|
@@ -1703,8 +1716,19 @@ interface ListPageProps<T> {
|
|
|
1703
1716
|
filters?: ListPageFilter[];
|
|
1704
1717
|
/** Current filter selections, keyed by `filter.key` (date values are ISO `YYYY-MM-DD`). */
|
|
1705
1718
|
filterValues?: Record<string, string>;
|
|
1706
|
-
/** Fires when
|
|
1719
|
+
/** Fires when a filter changes (`live`) or when Apply is pressed (`manual`). */
|
|
1707
1720
|
onFilterChange?: (key: string, value: string) => void;
|
|
1721
|
+
/**
|
|
1722
|
+
* How filter edits reach `onFilterChange`. Defaults to `'manual'`.
|
|
1723
|
+
* - `'manual'` (default) — edits are held locally; an **Apply** button (a real
|
|
1724
|
+
* form submit, so Enter also applies) flushes them in one go. Filtering only
|
|
1725
|
+
* fires on submit, so it never refetches on every keystroke/selection.
|
|
1726
|
+
* - `'live'` — control changes fire `onFilterChange` as they happen. Text
|
|
1727
|
+
* filters are **debounced by default (400ms)** so they don't refetch on
|
|
1728
|
+
* every keystroke; tune it per filter with `debounceMs`, or set `0` to emit
|
|
1729
|
+
* immediately. Selects / dates emit instantly.
|
|
1730
|
+
*/
|
|
1731
|
+
filterMode?: 'live' | 'manual';
|
|
1708
1732
|
enableRowSelection?: boolean;
|
|
1709
1733
|
bulkActions?: (selected: T[]) => ReactNode;
|
|
1710
1734
|
/** Current page state from the consumer's data layer. */
|
|
@@ -1780,7 +1804,7 @@ interface ListPageProps<T> {
|
|
|
1780
1804
|
* );
|
|
1781
1805
|
* ```
|
|
1782
1806
|
*/
|
|
1783
|
-
declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, filters, filterValues, onFilterChange, enableRowSelection, bulkActions, pagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
|
|
1807
|
+
declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, filters, filterValues, onFilterChange, filterMode, enableRowSelection, bulkActions, pagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
|
|
1784
1808
|
|
|
1785
1809
|
interface MultiSelectLabels {
|
|
1786
1810
|
/** Search input placeholder. Direction-aware default: `"Search…"` / `"بحث…"`. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1628,6 +1628,17 @@ interface ListPageSelectFilter extends ListPageFilterBase {
|
|
|
1628
1628
|
interface ListPageTextFilter extends ListPageFilterBase {
|
|
1629
1629
|
type: 'text';
|
|
1630
1630
|
placeholder?: string;
|
|
1631
|
+
/**
|
|
1632
|
+
* Debounce window in milliseconds so a request fires only after the user
|
|
1633
|
+
* pauses typing — instead of on every keystroke. The displayed value still
|
|
1634
|
+
* updates instantly; only `onFilterChange` is delayed. **Live mode only**
|
|
1635
|
+
* (ignored when `filterMode="manual"`, which already emits on Apply).
|
|
1636
|
+
*
|
|
1637
|
+
* Defaults to `400` — text filters are debounced out of the box, so you don't
|
|
1638
|
+
* need to pass anything. Set a different number to tune it, or `0` to opt out
|
|
1639
|
+
* and emit on every keystroke.
|
|
1640
|
+
*/
|
|
1641
|
+
debounceMs?: number;
|
|
1631
1642
|
}
|
|
1632
1643
|
/** Date filter — rendered as a `DatePicker`. ISO `YYYY-MM-DD`; empty means "no filter". */
|
|
1633
1644
|
interface ListPageDateFilter extends ListPageFilterBase {
|
|
@@ -1667,6 +1678,8 @@ interface ListPageEmptyState {
|
|
|
1667
1678
|
interface ListPageLabels extends TableLabels {
|
|
1668
1679
|
/** "Reset filters" button label. */
|
|
1669
1680
|
reset?: string;
|
|
1681
|
+
/** "Apply" button label — shown in `manual` filter mode (the default). */
|
|
1682
|
+
apply?: string;
|
|
1670
1683
|
/** "No results matching filters" title. */
|
|
1671
1684
|
emptyTitle?: string;
|
|
1672
1685
|
/** "No results matching filters" description. */
|
|
@@ -1703,8 +1716,19 @@ interface ListPageProps<T> {
|
|
|
1703
1716
|
filters?: ListPageFilter[];
|
|
1704
1717
|
/** Current filter selections, keyed by `filter.key` (date values are ISO `YYYY-MM-DD`). */
|
|
1705
1718
|
filterValues?: Record<string, string>;
|
|
1706
|
-
/** Fires when
|
|
1719
|
+
/** Fires when a filter changes (`live`) or when Apply is pressed (`manual`). */
|
|
1707
1720
|
onFilterChange?: (key: string, value: string) => void;
|
|
1721
|
+
/**
|
|
1722
|
+
* How filter edits reach `onFilterChange`. Defaults to `'manual'`.
|
|
1723
|
+
* - `'manual'` (default) — edits are held locally; an **Apply** button (a real
|
|
1724
|
+
* form submit, so Enter also applies) flushes them in one go. Filtering only
|
|
1725
|
+
* fires on submit, so it never refetches on every keystroke/selection.
|
|
1726
|
+
* - `'live'` — control changes fire `onFilterChange` as they happen. Text
|
|
1727
|
+
* filters are **debounced by default (400ms)** so they don't refetch on
|
|
1728
|
+
* every keystroke; tune it per filter with `debounceMs`, or set `0` to emit
|
|
1729
|
+
* immediately. Selects / dates emit instantly.
|
|
1730
|
+
*/
|
|
1731
|
+
filterMode?: 'live' | 'manual';
|
|
1708
1732
|
enableRowSelection?: boolean;
|
|
1709
1733
|
bulkActions?: (selected: T[]) => ReactNode;
|
|
1710
1734
|
/** Current page state from the consumer's data layer. */
|
|
@@ -1780,7 +1804,7 @@ interface ListPageProps<T> {
|
|
|
1780
1804
|
* );
|
|
1781
1805
|
* ```
|
|
1782
1806
|
*/
|
|
1783
|
-
declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, filters, filterValues, onFilterChange, enableRowSelection, bulkActions, pagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
|
|
1807
|
+
declare function ListPage<T>({ title, description, bordered, actions, data, columns, getRowId, isLoading, loadingRowCount, filters, filterValues, onFilterChange, filterMode, enableRowSelection, bulkActions, pagination, onPaginationChange, totalCount, pageSizeOptions, emptyState, noDataState, labels: labelsProp, className, }: ListPageProps<T>): react_jsx_runtime.JSX.Element;
|
|
1784
1808
|
|
|
1785
1809
|
interface MultiSelectLabels {
|
|
1786
1810
|
/** Search input placeholder. Direction-aware default: `"Search…"` / `"بحث…"`. */
|
package/dist/index.js
CHANGED
|
@@ -2920,37 +2920,130 @@ function hasActiveFilters(filters, values) {
|
|
|
2920
2920
|
}
|
|
2921
2921
|
return false;
|
|
2922
2922
|
}
|
|
2923
|
+
function DebouncedFilterInput({
|
|
2924
|
+
value,
|
|
2925
|
+
onChange,
|
|
2926
|
+
debounceMs,
|
|
2927
|
+
ariaLabel,
|
|
2928
|
+
placeholder,
|
|
2929
|
+
wrapperClassName,
|
|
2930
|
+
disabled
|
|
2931
|
+
}) {
|
|
2932
|
+
const [local, setLocal] = useState(value);
|
|
2933
|
+
const timerRef = useRef(null);
|
|
2934
|
+
const onChangeRef = useRef(onChange);
|
|
2935
|
+
onChangeRef.current = onChange;
|
|
2936
|
+
useEffect(() => {
|
|
2937
|
+
setLocal(value);
|
|
2938
|
+
if (timerRef.current) {
|
|
2939
|
+
clearTimeout(timerRef.current);
|
|
2940
|
+
timerRef.current = null;
|
|
2941
|
+
}
|
|
2942
|
+
}, [value]);
|
|
2943
|
+
useEffect(
|
|
2944
|
+
() => () => {
|
|
2945
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2946
|
+
},
|
|
2947
|
+
[]
|
|
2948
|
+
);
|
|
2949
|
+
const handleChange = (next) => {
|
|
2950
|
+
setLocal(next);
|
|
2951
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2952
|
+
if (debounceMs <= 0) {
|
|
2953
|
+
onChangeRef.current(next);
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
timerRef.current = setTimeout(() => {
|
|
2957
|
+
timerRef.current = null;
|
|
2958
|
+
onChangeRef.current(next);
|
|
2959
|
+
}, debounceMs);
|
|
2960
|
+
};
|
|
2961
|
+
return /* @__PURE__ */ jsx(
|
|
2962
|
+
Input,
|
|
2963
|
+
{
|
|
2964
|
+
type: "search",
|
|
2965
|
+
"aria-label": ariaLabel,
|
|
2966
|
+
placeholder,
|
|
2967
|
+
value: local,
|
|
2968
|
+
onChange: (e) => handleChange(e.target.value),
|
|
2969
|
+
leadingIcon: /* @__PURE__ */ jsx(Search, { className: "size-4" }),
|
|
2970
|
+
wrapperClassName,
|
|
2971
|
+
disabled
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
var DEFAULT_TEXT_DEBOUNCE_MS = 400;
|
|
2923
2976
|
function ListPageFilterBar({
|
|
2924
2977
|
filters,
|
|
2925
2978
|
values,
|
|
2926
2979
|
onChange,
|
|
2927
2980
|
disabled = false,
|
|
2981
|
+
mode = "live",
|
|
2928
2982
|
labels
|
|
2929
2983
|
}) {
|
|
2984
|
+
const manual = mode === "manual";
|
|
2930
2985
|
const active = hasActiveFilters(filters, values);
|
|
2986
|
+
const appliedKey = JSON.stringify(values ?? {});
|
|
2987
|
+
const [draft, setDraft] = useState(values ?? {});
|
|
2988
|
+
useEffect(() => {
|
|
2989
|
+
if (manual) setDraft(values ?? {});
|
|
2990
|
+
}, [appliedKey, manual]);
|
|
2991
|
+
const effectiveValues = manual ? draft : values ?? {};
|
|
2992
|
+
const handleChange = (key, value) => {
|
|
2993
|
+
if (manual) {
|
|
2994
|
+
setDraft((prev) => ({ ...prev, [key]: value }));
|
|
2995
|
+
} else {
|
|
2996
|
+
onChange?.(key, value);
|
|
2997
|
+
}
|
|
2998
|
+
};
|
|
2999
|
+
const dirty = manual && (filters ?? []).some((filter) => {
|
|
3000
|
+
const next = draft[filter.key] ?? filterDefaultValue(filter);
|
|
3001
|
+
const current = values?.[filter.key] ?? filterDefaultValue(filter);
|
|
3002
|
+
return next !== current;
|
|
3003
|
+
});
|
|
3004
|
+
const apply = (event) => {
|
|
3005
|
+
event.preventDefault();
|
|
3006
|
+
for (const filter of filters ?? []) {
|
|
3007
|
+
const next = draft[filter.key] ?? filterDefaultValue(filter);
|
|
3008
|
+
const current = values?.[filter.key] ?? filterDefaultValue(filter);
|
|
3009
|
+
if (next !== current) onChange?.(filter.key, next);
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
2931
3012
|
const reset = () => {
|
|
2932
3013
|
for (const filter of filters ?? []) {
|
|
2933
3014
|
onChange?.(filter.key, filterDefaultValue(filter));
|
|
2934
3015
|
}
|
|
2935
3016
|
};
|
|
3017
|
+
const controls = /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filters?.map((filter) => /* @__PURE__ */ jsx(
|
|
3018
|
+
FilterControl,
|
|
3019
|
+
{
|
|
3020
|
+
filter,
|
|
3021
|
+
value: effectiveValues[filter.key],
|
|
3022
|
+
onChange: handleChange,
|
|
3023
|
+
disabled,
|
|
3024
|
+
mode
|
|
3025
|
+
},
|
|
3026
|
+
filter.key
|
|
3027
|
+
)) });
|
|
3028
|
+
const resetButton = active && !disabled ? /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: reset, children: [
|
|
3029
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }),
|
|
3030
|
+
labels.reset
|
|
3031
|
+
] }) : null;
|
|
3032
|
+
if (manual) {
|
|
3033
|
+
return /* @__PURE__ */ jsxs("form", { "data-slot": "list-page-filter-bar", className: "space-y-3", onSubmit: apply, children: [
|
|
3034
|
+
controls,
|
|
3035
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
3036
|
+
resetButton,
|
|
3037
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", disabled: disabled || !dirty, children: labels.apply ?? "Apply" })
|
|
3038
|
+
] })
|
|
3039
|
+
] });
|
|
3040
|
+
}
|
|
2936
3041
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "list-page-filter-bar", className: "space-y-3", children: [
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
{
|
|
2940
|
-
filter,
|
|
2941
|
-
value: values?.[filter.key],
|
|
2942
|
-
onChange,
|
|
2943
|
-
disabled
|
|
2944
|
-
},
|
|
2945
|
-
filter.key
|
|
2946
|
-
)) }),
|
|
2947
|
-
active && !disabled ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: reset, children: [
|
|
2948
|
-
/* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }),
|
|
2949
|
-
labels.reset
|
|
2950
|
-
] }) }) : null
|
|
3042
|
+
controls,
|
|
3043
|
+
resetButton ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: resetButton }) : null
|
|
2951
3044
|
] });
|
|
2952
3045
|
}
|
|
2953
|
-
function FilterControl({ filter, value, onChange, disabled }) {
|
|
3046
|
+
function FilterControl({ filter, value, onChange, disabled, mode }) {
|
|
2954
3047
|
const spanClass = FILTER_SPAN_CLASS[filter.width ?? "default"];
|
|
2955
3048
|
const ariaLabel = typeof filter.label === "string" ? filter.label : filter.key;
|
|
2956
3049
|
switch (filter.type) {
|
|
@@ -2968,14 +3061,13 @@ function FilterControl({ filter, value, onChange, disabled }) {
|
|
|
2968
3061
|
);
|
|
2969
3062
|
case "text":
|
|
2970
3063
|
return /* @__PURE__ */ jsx(
|
|
2971
|
-
|
|
3064
|
+
DebouncedFilterInput,
|
|
2972
3065
|
{
|
|
2973
|
-
type: "search",
|
|
2974
|
-
"aria-label": ariaLabel,
|
|
2975
|
-
placeholder: filter.placeholder,
|
|
2976
3066
|
value: value ?? "",
|
|
2977
|
-
onChange: (
|
|
2978
|
-
|
|
3067
|
+
onChange: (v) => onChange?.(filter.key, v),
|
|
3068
|
+
debounceMs: mode === "live" ? filter.debounceMs ?? DEFAULT_TEXT_DEBOUNCE_MS : 0,
|
|
3069
|
+
ariaLabel,
|
|
3070
|
+
placeholder: filter.placeholder,
|
|
2979
3071
|
wrapperClassName: spanClass,
|
|
2980
3072
|
disabled
|
|
2981
3073
|
}
|
|
@@ -3007,13 +3099,22 @@ function FilterControl({ filter, value, onChange, disabled }) {
|
|
|
3007
3099
|
);
|
|
3008
3100
|
}
|
|
3009
3101
|
}
|
|
3010
|
-
var
|
|
3102
|
+
var EN_LABELS2 = {
|
|
3011
3103
|
reset: "Reset filters",
|
|
3104
|
+
apply: "Apply",
|
|
3012
3105
|
emptyTitle: "No results",
|
|
3013
3106
|
emptyDescription: "Try clearing the search or adjusting the filters.",
|
|
3014
3107
|
noDataTitle: "No data yet",
|
|
3015
3108
|
noDataDescription: "Nothing has been added here so far."
|
|
3016
3109
|
};
|
|
3110
|
+
var AR_LABELS2 = {
|
|
3111
|
+
reset: "\u0625\u0639\u0627\u062F\u0629 \u062A\u0639\u064A\u064A\u0646 \u0627\u0644\u0641\u0644\u0627\u062A\u0631",
|
|
3112
|
+
apply: "\u062A\u0637\u0628\u064A\u0642",
|
|
3113
|
+
emptyTitle: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0646\u062A\u0627\u0626\u062C",
|
|
3114
|
+
emptyDescription: "\u062C\u0631\u0651\u0628 \u0645\u0633\u062D \u0627\u0644\u0628\u062D\u062B \u0623\u0648 \u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0641\u0644\u0627\u062A\u0631.",
|
|
3115
|
+
noDataTitle: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0628\u064A\u0627\u0646\u0627\u062A \u0628\u0639\u062F",
|
|
3116
|
+
noDataDescription: "\u0644\u0645 \u062A\u062A\u0645 \u0625\u0636\u0627\u0641\u0629 \u0623\u064A \u0634\u064A\u0621 \u0647\u0646\u0627 \u062D\u062A\u0649 \u0627\u0644\u0622\u0646."
|
|
3117
|
+
};
|
|
3017
3118
|
function ListPage({
|
|
3018
3119
|
title,
|
|
3019
3120
|
description,
|
|
@@ -3027,6 +3128,7 @@ function ListPage({
|
|
|
3027
3128
|
filters,
|
|
3028
3129
|
filterValues,
|
|
3029
3130
|
onFilterChange,
|
|
3131
|
+
filterMode = "manual",
|
|
3030
3132
|
enableRowSelection,
|
|
3031
3133
|
bulkActions,
|
|
3032
3134
|
pagination,
|
|
@@ -3038,7 +3140,8 @@ function ListPage({
|
|
|
3038
3140
|
labels: labelsProp,
|
|
3039
3141
|
className
|
|
3040
3142
|
}) {
|
|
3041
|
-
const
|
|
3143
|
+
const dir = useDirection();
|
|
3144
|
+
const labels = { ...dir === "rtl" ? AR_LABELS2 : EN_LABELS2, ...labelsProp };
|
|
3042
3145
|
const showFilterBar = Boolean(filters?.length);
|
|
3043
3146
|
const hasActiveQuery = useMemo(
|
|
3044
3147
|
() => hasActiveFilters(filters, filterValues),
|
|
@@ -3058,8 +3161,8 @@ function ListPage({
|
|
|
3058
3161
|
filters,
|
|
3059
3162
|
values: filterValues,
|
|
3060
3163
|
onChange: onFilterChange,
|
|
3061
|
-
|
|
3062
|
-
labels: { reset: labels.reset }
|
|
3164
|
+
mode: filterMode,
|
|
3165
|
+
labels: { reset: labels.reset, apply: labels.apply }
|
|
3063
3166
|
}
|
|
3064
3167
|
) : null,
|
|
3065
3168
|
tableMode === "loading" || tableMode === "rows" ? /* @__PURE__ */ jsx(
|