@l3mpire/ui 2.16.4 → 2.17.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/USAGE.md CHANGED
@@ -963,7 +963,7 @@ import {
963
963
 
964
964
  **Advanced filter shortcut:** the PropertySelector footer shows an "Advanced filter · N rule(s)" item. Clicking it closes the property selector and opens the advanced popover directly in its initial empty state — where the user picks the first property inline via a "Where | [Select property ▾]" draft row. When no advanced filters exist yet, clicking this shortcut makes the AdvancedChip appear with the popover already open; closing without adding a filter removes the chip automatically.
965
965
 
966
- **Dynamic options ("Me", "Unassigned", …):** enum/tags/relation properties accept a `dynamicOptions` array. Each entry is `{ value, label, description? }` and is rendered inline with regular options in the SingleSelect / MultiSelect dropdown. The `value` is a sentinel string stored on `FilterCondition.value` — the DS renders the `label` in the filter chip (not the raw sentinel) and the consuming app resolves it at query time (e.g. `"__me__"` → `currentUser.id`). This keeps session/business logic out of the DS while still getting a consistent visual treatment.
966
+ **Dynamic options ("Me", "Unassigned", …):** enum/tags/relation properties accept a `dynamicOptions` array. Each entry is `{ value, label, description?, icon? }` and is rendered at the top of the SingleSelect / MultiSelect dropdown with a divider separating it from the regular options. The `value` is a sentinel string stored on `FilterCondition.value` — the DS only renders, the consuming app resolves it at query time (e.g. `"__me__"` → `currentUser.id`). This keeps session/business logic out of the DS while still getting a consistent visual treatment.
967
967
 
968
968
  ```tsx
969
969
  {
@@ -979,8 +979,9 @@ import {
979
979
  value: "__me__",
980
980
  label: "Me",
981
981
  description: "This value is dynamically applied to the current user",
982
+ icon: faBoltOutline,
982
983
  },
983
- { value: "__deactivated__", label: "All deactivated and removed owners" },
984
+ { value: "__deactivated__", label: "All deactivated and removed owners", icon: faBoltOutline },
984
985
  ],
985
986
  }
986
987
  ```
@@ -992,7 +993,7 @@ import {
992
993
  | `createFilterWithDefaults(propertyId, type)` | Creates FilterCondition with default operator |
993
994
  | `isNoValueOperator(op)` | True for is empty / is not empty / is true / is false |
994
995
  | `getValueInputType(type, op)` | Returns the input component name for a type+operator combo |
995
- | `formatFilterValue(value, dynamicOptions?)` | Stringifies a `FilterValue` for display resolves sentinel values via `dynamicOptions` labels |
996
+ | `formatFilterValue(value)` | Stringifies a `FilterValue` for display (handles Date, ranges, arrays, booleans) |
996
997
  | `getBadgeCount(value)` | Returns `+N` count for multi-value selections, `undefined` otherwise |
997
998
  | `createEmptyGroup()` / `isFilterGroup(node)` | Tree helpers for nested groups |
998
999
  | `wrapInGroup(condition)` / `unwrapGroup(group)` | Convert between condition and single-child group |
package/dist/index.d.mts CHANGED
@@ -935,7 +935,7 @@ interface InteractiveFilterChipProps {
935
935
  }
936
936
  declare const InteractiveFilterChip: React.FC<InteractiveFilterChipProps>;
937
937
 
938
- type FilterBarMode = "default" | "minimal";
938
+ type FilterBarMode = "default" | "minimal" | "icon";
939
939
  /**
940
940
  * Observes the container width and returns the appropriate FilterBar mode.
941
941
  * - default: > breakpoint
package/dist/index.d.ts CHANGED
@@ -935,7 +935,7 @@ interface InteractiveFilterChipProps {
935
935
  }
936
936
  declare const InteractiveFilterChip: React.FC<InteractiveFilterChipProps>;
937
937
 
938
- type FilterBarMode = "default" | "minimal";
938
+ type FilterBarMode = "default" | "minimal" | "icon";
939
939
  /**
940
940
  * Observes the container width and returns the appropriate FilterBar mode.
941
941
  * - default: > breakpoint
package/dist/index.js CHANGED
@@ -5440,6 +5440,23 @@ function updateNodeInTree(nodes, id, updater) {
5440
5440
  return node;
5441
5441
  });
5442
5442
  }
5443
+ function toggleGroupLogicInTree(nodes, id) {
5444
+ const idx = nodes.findIndex((n) => n.id === id);
5445
+ if (idx >= 0) {
5446
+ const current = nodes[idx].logicOperator ?? "and";
5447
+ const next = current === "and" ? "or" : "and";
5448
+ return nodes.map(
5449
+ (n, i) => i === 0 ? n : { ...n, logicOperator: next }
5450
+ );
5451
+ }
5452
+ return nodes.map((node) => {
5453
+ if (isFilterGroup(node)) {
5454
+ const updated = toggleGroupLogicInTree(node.children, id);
5455
+ if (updated !== node.children) return { ...node, children: updated };
5456
+ }
5457
+ return node;
5458
+ });
5459
+ }
5443
5460
  function removeNodeFromTree(nodes, id) {
5444
5461
  const result = [];
5445
5462
  for (const node of nodes) {
@@ -8121,12 +8138,7 @@ var AdvancedPopover = ({
8121
8138
  }
8122
8139
  };
8123
8140
  const toggleLogicOp = (id) => {
8124
- onFiltersChange(
8125
- updateNodeInTree(filters, id, (n) => ({
8126
- ...n,
8127
- logicOperator: (n.logicOperator ?? "and") === "and" ? "or" : "and"
8128
- }))
8129
- );
8141
+ onFiltersChange(toggleGroupLogicInTree(filters, id));
8130
8142
  };
8131
8143
  const handleGroupChildrenChange = (groupId, children2) => {
8132
8144
  onFiltersChange(
@@ -8415,12 +8427,7 @@ var SummaryChip = ({
8415
8427
  }
8416
8428
  };
8417
8429
  const toggleLogicOp = (id) => {
8418
- onFiltersChange(
8419
- updateNodeInTree(filters, id, (n) => ({
8420
- ...n,
8421
- logicOperator: (n.logicOperator ?? "and") === "and" ? "or" : "and"
8422
- }))
8423
- );
8430
+ onFiltersChange(toggleGroupLogicInTree(filters, id));
8424
8431
  };
8425
8432
  const handleGroupChildrenChange = (groupId, newChildren) => {
8426
8433
  onFiltersChange(
@@ -8713,7 +8720,8 @@ var FilterSystem = ({
8713
8720
  };
8714
8721
  const getPropertyDef = (propertyId) => properties.find((p) => p.id === propertyId);
8715
8722
  const hasAdvanced = filterState.advancedFilters.length > 0;
8716
- const isMinimal = mode === "minimal";
8723
+ const isMinimal = mode === "minimal" || mode === "icon";
8724
+ const isIconOnly = mode === "icon";
8717
8725
  const handleOpenAdvanced = () => {
8718
8726
  setPropertySelectorOpen(false);
8719
8727
  requestAnimationFrame(() => {
@@ -8726,7 +8734,6 @@ var FilterSystem = ({
8726
8734
  };
8727
8735
  const advancedFilterCount = filterState.advancedFilters.length;
8728
8736
  const showAdvancedChip = hasAdvanced || advancedOpen;
8729
- const showSummaryChip = totalCount > 0 || summaryOpen;
8730
8737
  return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FilterBar, { ref: containerRef, className, children: [
8731
8738
  /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FilterBarLeft, { className: "flex-nowrap flex-1 min-w-0 overflow-x-auto scrollbar-none outline-none [&>*]:shrink-0", children: [
8732
8739
  children,
@@ -8740,38 +8747,32 @@ var FilterSystem = ({
8740
8747
  iconOnly: isMinimal
8741
8748
  }
8742
8749
  ),
8743
- isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_jsx_runtime60.Fragment, { children: [
8744
- showSummaryChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8745
- SummaryChip,
8746
- {
8747
- count: totalCount,
8748
- filters: [...filterState.basicFilters, ...filterState.advancedFilters],
8749
- properties,
8750
- onFiltersChange: (nodes) => {
8751
- onFilterStateChange({
8752
- ...filterState,
8753
- basicFilters: [],
8754
- advancedFilters: nodes
8755
- });
8756
- },
8757
- onClearAll: handleClearAll,
8758
- open: summaryOpen,
8759
- onOpenChange: setSummaryOpen
8760
- }
8761
- ),
8762
- !showSummaryChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8763
- PropertySelector,
8764
- {
8765
- properties,
8766
- onSelect: handleAddFilter,
8767
- open: propertySelectorOpen,
8768
- onOpenChange: setPropertySelectorOpen,
8769
- onAdvancedFilter: handleOpenAdvanced,
8770
- advancedFilterCount,
8771
- children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FilterBarButton, { iconOnly: true })
8772
- }
8773
- )
8774
- ] }) : (
8750
+ isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_jsx_runtime60.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8751
+ SummaryChip,
8752
+ {
8753
+ count: totalCount,
8754
+ filters: [...filterState.basicFilters, ...filterState.advancedFilters],
8755
+ properties,
8756
+ onFiltersChange: (nodes) => {
8757
+ onFilterStateChange({
8758
+ ...filterState,
8759
+ basicFilters: [],
8760
+ advancedFilters: nodes
8761
+ });
8762
+ },
8763
+ onClearAll: handleClearAll,
8764
+ open: summaryOpen,
8765
+ onOpenChange: setSummaryOpen,
8766
+ children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8767
+ FilterBarButton,
8768
+ {
8769
+ iconOnly: isIconOnly,
8770
+ count: totalCount > 0 ? totalCount : void 0,
8771
+ title: buildFilterSummary(filterState.basicFilters, filterState.advancedFilters.length, properties)
8772
+ }
8773
+ )
8774
+ }
8775
+ ) }) : (
8775
8776
  /* ── DEFAULT MODE ────────────────────────────────────── */
8776
8777
  /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_jsx_runtime60.Fragment, { children: [
8777
8778
  showAdvancedChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
@@ -8832,13 +8833,13 @@ var FilterSystem = ({
8832
8833
  )
8833
8834
  ] })
8834
8835
  ),
8835
- totalCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8836
+ totalCount > 0 && !isIconOnly && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8836
8837
  "button",
8837
8838
  {
8838
8839
  type: "button",
8839
8840
  onClick: handleClearAll,
8840
- className: "shrink-0 flex items-center gap-sm px-base py-sm min-h-[32px] max-h-[32px] rounded-md cursor-pointer transition-colors hover:bg-[var(--color-accent)]",
8841
- children: isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_icons38.Icon, { icon: import_icons38.faXmarkOutline, size: "sm", className: "text-[var(--color-foreground)]" }) : /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { className: "text-sm font-semibold leading-sm text-[var(--color-foreground)]", children: "Clear" })
8841
+ className: "shrink-0 flex items-center gap-sm px-base py-sm min-h-[32px] max-h-[32px] rounded-md cursor-pointer transition-colors text-btn-ghost-brand-text-default hover:bg-btn-ghost-brand-bg-hover hover:text-btn-ghost-brand-text-hover active:bg-btn-ghost-brand-bg-pressed active:text-btn-ghost-brand-text-pressed",
8842
+ children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { className: "text-sm font-semibold leading-sm", children: "Clear" })
8842
8843
  }
8843
8844
  )
8844
8845
  ] }),
@@ -8846,6 +8847,20 @@ var FilterSystem = ({
8846
8847
  ] });
8847
8848
  };
8848
8849
  FilterSystem.displayName = "FilterSystem";
8850
+ function buildFilterSummary(filters, advancedCount, properties) {
8851
+ if (filters.length === 0 && advancedCount === 0) return void 0;
8852
+ const lines = [];
8853
+ for (const f of filters) {
8854
+ const prop = properties.find((p) => p.id === f.propertyId);
8855
+ if (!prop) continue;
8856
+ const val = formatFilterValue(f.value, prop.dynamicOptions);
8857
+ lines.push(val ? `${prop.label} ${f.operator} ${val}` : `${prop.label} ${f.operator ?? ""}`);
8858
+ }
8859
+ if (advancedCount > 0) {
8860
+ lines.push(`+ ${advancedCount} advanced filter${advancedCount > 1 ? "s" : ""}`);
8861
+ }
8862
+ return lines.join("\n");
8863
+ }
8849
8864
  // Annotate the CommonJS export names for ESM import in node:
8850
8865
  0 && (module.exports = {
8851
8866
  AdvancedChip,