@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 CHANGED
@@ -2948,37 +2948,130 @@ function hasActiveFilters(filters, values) {
2948
2948
  }
2949
2949
  return false;
2950
2950
  }
2951
+ function DebouncedFilterInput({
2952
+ value,
2953
+ onChange,
2954
+ debounceMs,
2955
+ ariaLabel,
2956
+ placeholder,
2957
+ wrapperClassName,
2958
+ disabled
2959
+ }) {
2960
+ const [local, setLocal] = react.useState(value);
2961
+ const timerRef = react.useRef(null);
2962
+ const onChangeRef = react.useRef(onChange);
2963
+ onChangeRef.current = onChange;
2964
+ react.useEffect(() => {
2965
+ setLocal(value);
2966
+ if (timerRef.current) {
2967
+ clearTimeout(timerRef.current);
2968
+ timerRef.current = null;
2969
+ }
2970
+ }, [value]);
2971
+ react.useEffect(
2972
+ () => () => {
2973
+ if (timerRef.current) clearTimeout(timerRef.current);
2974
+ },
2975
+ []
2976
+ );
2977
+ const handleChange = (next) => {
2978
+ setLocal(next);
2979
+ if (timerRef.current) clearTimeout(timerRef.current);
2980
+ if (debounceMs <= 0) {
2981
+ onChangeRef.current(next);
2982
+ return;
2983
+ }
2984
+ timerRef.current = setTimeout(() => {
2985
+ timerRef.current = null;
2986
+ onChangeRef.current(next);
2987
+ }, debounceMs);
2988
+ };
2989
+ return /* @__PURE__ */ jsxRuntime.jsx(
2990
+ Input,
2991
+ {
2992
+ type: "search",
2993
+ "aria-label": ariaLabel,
2994
+ placeholder,
2995
+ value: local,
2996
+ onChange: (e) => handleChange(e.target.value),
2997
+ leadingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "size-4" }),
2998
+ wrapperClassName,
2999
+ disabled
3000
+ }
3001
+ );
3002
+ }
3003
+ var DEFAULT_TEXT_DEBOUNCE_MS = 400;
2951
3004
  function ListPageFilterBar({
2952
3005
  filters,
2953
3006
  values,
2954
3007
  onChange,
2955
3008
  disabled = false,
3009
+ mode = "live",
2956
3010
  labels
2957
3011
  }) {
3012
+ const manual = mode === "manual";
2958
3013
  const active = hasActiveFilters(filters, values);
3014
+ const appliedKey = JSON.stringify(values ?? {});
3015
+ const [draft, setDraft] = react.useState(values ?? {});
3016
+ react.useEffect(() => {
3017
+ if (manual) setDraft(values ?? {});
3018
+ }, [appliedKey, manual]);
3019
+ const effectiveValues = manual ? draft : values ?? {};
3020
+ const handleChange = (key, value) => {
3021
+ if (manual) {
3022
+ setDraft((prev) => ({ ...prev, [key]: value }));
3023
+ } else {
3024
+ onChange?.(key, value);
3025
+ }
3026
+ };
3027
+ const dirty = manual && (filters ?? []).some((filter) => {
3028
+ const next = draft[filter.key] ?? filterDefaultValue(filter);
3029
+ const current = values?.[filter.key] ?? filterDefaultValue(filter);
3030
+ return next !== current;
3031
+ });
3032
+ const apply = (event) => {
3033
+ event.preventDefault();
3034
+ for (const filter of filters ?? []) {
3035
+ const next = draft[filter.key] ?? filterDefaultValue(filter);
3036
+ const current = values?.[filter.key] ?? filterDefaultValue(filter);
3037
+ if (next !== current) onChange?.(filter.key, next);
3038
+ }
3039
+ };
2959
3040
  const reset = () => {
2960
3041
  for (const filter of filters ?? []) {
2961
3042
  onChange?.(filter.key, filterDefaultValue(filter));
2962
3043
  }
2963
3044
  };
3045
+ const controls = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filters?.map((filter) => /* @__PURE__ */ jsxRuntime.jsx(
3046
+ FilterControl,
3047
+ {
3048
+ filter,
3049
+ value: effectiveValues[filter.key],
3050
+ onChange: handleChange,
3051
+ disabled,
3052
+ mode
3053
+ },
3054
+ filter.key
3055
+ )) });
3056
+ const resetButton = active && !disabled ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: reset, children: [
3057
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "size-4" }),
3058
+ labels.reset
3059
+ ] }) : null;
3060
+ if (manual) {
3061
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { "data-slot": "list-page-filter-bar", className: "space-y-3", onSubmit: apply, children: [
3062
+ controls,
3063
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
3064
+ resetButton,
3065
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", size: "sm", disabled: disabled || !dirty, children: labels.apply ?? "Apply" })
3066
+ ] })
3067
+ ] });
3068
+ }
2964
3069
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "list-page-filter-bar", className: "space-y-3", children: [
2965
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filters?.map((filter) => /* @__PURE__ */ jsxRuntime.jsx(
2966
- FilterControl,
2967
- {
2968
- filter,
2969
- value: values?.[filter.key],
2970
- onChange,
2971
- disabled
2972
- },
2973
- filter.key
2974
- )) }),
2975
- active && !disabled ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: reset, children: [
2976
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "size-4" }),
2977
- labels.reset
2978
- ] }) }) : null
3070
+ controls,
3071
+ resetButton ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: resetButton }) : null
2979
3072
  ] });
2980
3073
  }
2981
- function FilterControl({ filter, value, onChange, disabled }) {
3074
+ function FilterControl({ filter, value, onChange, disabled, mode }) {
2982
3075
  const spanClass = FILTER_SPAN_CLASS[filter.width ?? "default"];
2983
3076
  const ariaLabel = typeof filter.label === "string" ? filter.label : filter.key;
2984
3077
  switch (filter.type) {
@@ -2996,14 +3089,13 @@ function FilterControl({ filter, value, onChange, disabled }) {
2996
3089
  );
2997
3090
  case "text":
2998
3091
  return /* @__PURE__ */ jsxRuntime.jsx(
2999
- Input,
3092
+ DebouncedFilterInput,
3000
3093
  {
3001
- type: "search",
3002
- "aria-label": ariaLabel,
3003
- placeholder: filter.placeholder,
3004
3094
  value: value ?? "",
3005
- onChange: (e) => onChange?.(filter.key, e.target.value),
3006
- leadingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "size-4" }),
3095
+ onChange: (v) => onChange?.(filter.key, v),
3096
+ debounceMs: mode === "live" ? filter.debounceMs ?? DEFAULT_TEXT_DEBOUNCE_MS : 0,
3097
+ ariaLabel,
3098
+ placeholder: filter.placeholder,
3007
3099
  wrapperClassName: spanClass,
3008
3100
  disabled
3009
3101
  }
@@ -3035,13 +3127,22 @@ function FilterControl({ filter, value, onChange, disabled }) {
3035
3127
  );
3036
3128
  }
3037
3129
  }
3038
- var DEFAULT_LABELS = {
3130
+ var EN_LABELS2 = {
3039
3131
  reset: "Reset filters",
3132
+ apply: "Apply",
3040
3133
  emptyTitle: "No results",
3041
3134
  emptyDescription: "Try clearing the search or adjusting the filters.",
3042
3135
  noDataTitle: "No data yet",
3043
3136
  noDataDescription: "Nothing has been added here so far."
3044
3137
  };
3138
+ var AR_LABELS2 = {
3139
+ reset: "\u0625\u0639\u0627\u062F\u0629 \u062A\u0639\u064A\u064A\u0646 \u0627\u0644\u0641\u0644\u0627\u062A\u0631",
3140
+ apply: "\u062A\u0637\u0628\u064A\u0642",
3141
+ emptyTitle: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0646\u062A\u0627\u0626\u062C",
3142
+ 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.",
3143
+ noDataTitle: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0628\u064A\u0627\u0646\u0627\u062A \u0628\u0639\u062F",
3144
+ 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."
3145
+ };
3045
3146
  function ListPage({
3046
3147
  title,
3047
3148
  description,
@@ -3055,6 +3156,7 @@ function ListPage({
3055
3156
  filters,
3056
3157
  filterValues,
3057
3158
  onFilterChange,
3159
+ filterMode = "manual",
3058
3160
  enableRowSelection,
3059
3161
  bulkActions,
3060
3162
  pagination,
@@ -3066,7 +3168,8 @@ function ListPage({
3066
3168
  labels: labelsProp,
3067
3169
  className
3068
3170
  }) {
3069
- const labels = { ...DEFAULT_LABELS, ...labelsProp };
3171
+ const dir = useDirection();
3172
+ const labels = { ...dir === "rtl" ? AR_LABELS2 : EN_LABELS2, ...labelsProp };
3070
3173
  const showFilterBar = Boolean(filters?.length);
3071
3174
  const hasActiveQuery = react.useMemo(
3072
3175
  () => hasActiveFilters(filters, filterValues),
@@ -3086,8 +3189,8 @@ function ListPage({
3086
3189
  filters,
3087
3190
  values: filterValues,
3088
3191
  onChange: onFilterChange,
3089
- disabled: isLoading,
3090
- labels: { reset: labels.reset }
3192
+ mode: filterMode,
3193
+ labels: { reset: labels.reset, apply: labels.apply }
3091
3194
  }
3092
3195
  ) : null,
3093
3196
  tableMode === "loading" || tableMode === "rows" ? /* @__PURE__ */ jsxRuntime.jsx(