@mieweb/ui 0.6.0 → 0.6.1-dev.120

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
@@ -1760,6 +1760,28 @@ interface CommandPaletteProps {
1760
1760
  isLoading?: boolean;
1761
1761
  /** Called when an item is selected */
1762
1762
  onSelect?: (item: CommandPaletteItem) => void;
1763
+ /**
1764
+ * Fires whenever the search query changes. Consumers should debounce
1765
+ * inside this callback if they're hitting a network. Provider's `items`
1766
+ * is still the source of truth for what renders.
1767
+ */
1768
+ onQueryChange?: (query: string) => void;
1769
+ /**
1770
+ * Items always shown at the top, regardless of query. Use for smart
1771
+ * actions ("Call John", "Ask AI: …") or other affordances that should
1772
+ * remain reachable while typing. Filtered out when a category filter
1773
+ * is active so the category view stays scoped.
1774
+ */
1775
+ pinnedItems?: CommandPaletteItem[];
1776
+ /** Group label rendered above pinned items. Defaults to "Actions". */
1777
+ pinnedCategoryLabel?: string;
1778
+ /**
1779
+ * Items shown only when the query is empty and there are no provider
1780
+ * items to display. Use for recent searches / recently opened records.
1781
+ */
1782
+ recentItems?: CommandPaletteItem[];
1783
+ /** Group label rendered above recent items. Defaults to "Recent". */
1784
+ recentCategoryLabel?: string;
1763
1785
  /** Custom empty state content */
1764
1786
  emptyState?: React__default.ReactNode;
1765
1787
  /** Custom render function for items */
@@ -1773,8 +1795,14 @@ interface CommandPaletteProps {
1773
1795
  className?: string;
1774
1796
  /** Test ID for testing */
1775
1797
  'data-testid'?: string;
1798
+ /**
1799
+ * Skip the built-in client-side query filter. Use when `items` are
1800
+ * already filtered server-side (e.g. semantic search) and labels
1801
+ * won't necessarily contain the query as a substring.
1802
+ */
1803
+ serverFiltered?: boolean;
1776
1804
  }
1777
- declare function CommandPalette({ placeholder, isLoading, onSelect, emptyState, renderItem, footer, className, 'data-testid': testId, }: CommandPaletteProps): React__default.JSX.Element | null;
1805
+ declare function CommandPalette({ placeholder, isLoading, onSelect, onQueryChange, pinnedItems, pinnedCategoryLabel, recentItems, recentCategoryLabel, emptyState, renderItem, footer, className, 'data-testid': testId, serverFiltered, }: CommandPaletteProps): React__default.JSX.Element | null;
1778
1806
  interface CommandPaletteTriggerProps {
1779
1807
  /** Button content (default shows search icon and keyboard hint) */
1780
1808
  children?: React__default.ReactNode;
@@ -2410,6 +2438,15 @@ interface DateRangePickerProps {
2410
2438
  placeholder?: string;
2411
2439
  /** Custom className */
2412
2440
  className?: string;
2441
+ /**
2442
+ * Horizontal alignment of the desktop popup relative to the trigger.
2443
+ * - `'start'` (default historical behavior): popup left edge aligns with trigger left edge.
2444
+ * - `'end'`: popup right edge aligns with trigger right edge.
2445
+ * - `'auto'`: starts as `'start'`, then automatically flips to `'end'` if the popup
2446
+ * would overflow the right edge of the viewport. Recommended for triggers placed
2447
+ * in the right side of a layout (e.g. page header action slots).
2448
+ */
2449
+ align?: 'start' | 'end' | 'auto';
2413
2450
  /** Whether to show the preset sidebar in the calendar popup (default: true) */
2414
2451
  showPresets?: boolean;
2415
2452
  /** Display variant: desktop (default), mobile (bottom sheet), or responsive (auto-adapts at md breakpoint) */
@@ -2471,7 +2508,7 @@ declare function calculateDateRange(presetKey: string): DateRange$1;
2471
2508
  * />
2472
2509
  * ```
2473
2510
  */
2474
- declare function DateRangePicker({ value, onChange, presets, activePreset, placeholder, className, showPresets, variant, labels, }: DateRangePickerProps): react_jsx_runtime.JSX.Element;
2511
+ declare function DateRangePicker({ value, onChange, presets, activePreset, placeholder, className, align, showPresets, variant, labels, }: DateRangePickerProps): react_jsx_runtime.JSX.Element;
2475
2512
  interface DateRangeFilterProps {
2476
2513
  /** Current date range value */
2477
2514
  value?: DateRange$1;
package/dist/index.d.ts CHANGED
@@ -1760,6 +1760,28 @@ interface CommandPaletteProps {
1760
1760
  isLoading?: boolean;
1761
1761
  /** Called when an item is selected */
1762
1762
  onSelect?: (item: CommandPaletteItem) => void;
1763
+ /**
1764
+ * Fires whenever the search query changes. Consumers should debounce
1765
+ * inside this callback if they're hitting a network. Provider's `items`
1766
+ * is still the source of truth for what renders.
1767
+ */
1768
+ onQueryChange?: (query: string) => void;
1769
+ /**
1770
+ * Items always shown at the top, regardless of query. Use for smart
1771
+ * actions ("Call John", "Ask AI: …") or other affordances that should
1772
+ * remain reachable while typing. Filtered out when a category filter
1773
+ * is active so the category view stays scoped.
1774
+ */
1775
+ pinnedItems?: CommandPaletteItem[];
1776
+ /** Group label rendered above pinned items. Defaults to "Actions". */
1777
+ pinnedCategoryLabel?: string;
1778
+ /**
1779
+ * Items shown only when the query is empty and there are no provider
1780
+ * items to display. Use for recent searches / recently opened records.
1781
+ */
1782
+ recentItems?: CommandPaletteItem[];
1783
+ /** Group label rendered above recent items. Defaults to "Recent". */
1784
+ recentCategoryLabel?: string;
1763
1785
  /** Custom empty state content */
1764
1786
  emptyState?: React__default.ReactNode;
1765
1787
  /** Custom render function for items */
@@ -1773,8 +1795,14 @@ interface CommandPaletteProps {
1773
1795
  className?: string;
1774
1796
  /** Test ID for testing */
1775
1797
  'data-testid'?: string;
1798
+ /**
1799
+ * Skip the built-in client-side query filter. Use when `items` are
1800
+ * already filtered server-side (e.g. semantic search) and labels
1801
+ * won't necessarily contain the query as a substring.
1802
+ */
1803
+ serverFiltered?: boolean;
1776
1804
  }
1777
- declare function CommandPalette({ placeholder, isLoading, onSelect, emptyState, renderItem, footer, className, 'data-testid': testId, }: CommandPaletteProps): React__default.JSX.Element | null;
1805
+ declare function CommandPalette({ placeholder, isLoading, onSelect, onQueryChange, pinnedItems, pinnedCategoryLabel, recentItems, recentCategoryLabel, emptyState, renderItem, footer, className, 'data-testid': testId, serverFiltered, }: CommandPaletteProps): React__default.JSX.Element | null;
1778
1806
  interface CommandPaletteTriggerProps {
1779
1807
  /** Button content (default shows search icon and keyboard hint) */
1780
1808
  children?: React__default.ReactNode;
@@ -2410,6 +2438,15 @@ interface DateRangePickerProps {
2410
2438
  placeholder?: string;
2411
2439
  /** Custom className */
2412
2440
  className?: string;
2441
+ /**
2442
+ * Horizontal alignment of the desktop popup relative to the trigger.
2443
+ * - `'start'` (default historical behavior): popup left edge aligns with trigger left edge.
2444
+ * - `'end'`: popup right edge aligns with trigger right edge.
2445
+ * - `'auto'`: starts as `'start'`, then automatically flips to `'end'` if the popup
2446
+ * would overflow the right edge of the viewport. Recommended for triggers placed
2447
+ * in the right side of a layout (e.g. page header action slots).
2448
+ */
2449
+ align?: 'start' | 'end' | 'auto';
2413
2450
  /** Whether to show the preset sidebar in the calendar popup (default: true) */
2414
2451
  showPresets?: boolean;
2415
2452
  /** Display variant: desktop (default), mobile (bottom sheet), or responsive (auto-adapts at md breakpoint) */
@@ -2471,7 +2508,7 @@ declare function calculateDateRange(presetKey: string): DateRange$1;
2471
2508
  * />
2472
2509
  * ```
2473
2510
  */
2474
- declare function DateRangePicker({ value, onChange, presets, activePreset, placeholder, className, showPresets, variant, labels, }: DateRangePickerProps): react_jsx_runtime.JSX.Element;
2511
+ declare function DateRangePicker({ value, onChange, presets, activePreset, placeholder, className, align, showPresets, variant, labels, }: DateRangePickerProps): react_jsx_runtime.JSX.Element;
2475
2512
  interface DateRangeFilterProps {
2476
2513
  /** Current date range value */
2477
2514
  value?: DateRange$1;
package/dist/index.js CHANGED
@@ -3876,7 +3876,7 @@ var MessageBubble = React48.forwardRef(
3876
3876
  "div",
3877
3877
  {
3878
3878
  className: cn(
3879
- "flex flex-1 flex-col min-w-0",
3879
+ "flex min-w-0 flex-1 flex-col",
3880
3880
  isOutgoing ? "items-end" : "items-start"
3881
3881
  ),
3882
3882
  children: [
@@ -8655,11 +8655,17 @@ function CommandPalette({
8655
8655
  placeholder = "Search...",
8656
8656
  isLoading = false,
8657
8657
  onSelect,
8658
+ onQueryChange,
8659
+ pinnedItems,
8660
+ pinnedCategoryLabel = "Actions",
8661
+ recentItems,
8662
+ recentCategoryLabel = "Recent",
8658
8663
  emptyState,
8659
8664
  renderItem,
8660
8665
  footer,
8661
8666
  className,
8662
- "data-testid": testId = "command-palette"
8667
+ "data-testid": testId = "command-palette",
8668
+ serverFiltered = false
8663
8669
  }) {
8664
8670
  const {
8665
8671
  isOpen,
@@ -8676,29 +8682,47 @@ function CommandPalette({
8676
8682
  const inputRef = useRef(null);
8677
8683
  const containerRef = useRef(null);
8678
8684
  const listRef = useRef(null);
8685
+ useEffect(() => {
8686
+ if (!isOpen) return;
8687
+ onQueryChange?.(query);
8688
+ }, [query, isOpen, onQueryChange]);
8689
+ const PINNED_CATEGORY_ID = "__palette_pinned__";
8690
+ const RECENT_CATEGORY_ID = "__palette_recent__";
8679
8691
  const filteredItems = useMemo(() => {
8680
8692
  let result = items;
8681
8693
  if (activeCategory) {
8682
8694
  result = result.filter((item) => item.category === activeCategory);
8683
8695
  }
8684
- if (query.trim()) {
8696
+ if (query.trim() && !serverFiltered) {
8685
8697
  const lowerQuery = query.toLowerCase();
8686
8698
  result = result.filter(
8687
8699
  (item) => item.label.toLowerCase().includes(lowerQuery) || item.subtitle?.toLowerCase().includes(lowerQuery) || item.description?.toLowerCase().includes(lowerQuery)
8688
8700
  );
8689
8701
  }
8690
8702
  return result;
8691
- }, [items, query, activeCategory]);
8703
+ }, [items, query, activeCategory, serverFiltered]);
8704
+ const effectiveItems = useMemo(() => {
8705
+ const pinned = !activeCategory && pinnedItems?.length ? pinnedItems.map((it) => ({
8706
+ ...it,
8707
+ category: it.category ?? PINNED_CATEGORY_ID
8708
+ })) : [];
8709
+ const showRecents = !activeCategory && !query.trim() && filteredItems.length === 0 && !!recentItems?.length;
8710
+ const recents = showRecents ? recentItems.map((it) => ({
8711
+ ...it,
8712
+ category: it.category ?? RECENT_CATEGORY_ID
8713
+ })) : [];
8714
+ return [...pinned, ...filteredItems, ...recents];
8715
+ }, [pinnedItems, recentItems, filteredItems, activeCategory, query]);
8692
8716
  const groupedItems = useMemo(() => {
8693
8717
  const groups = /* @__PURE__ */ new Map();
8694
- filteredItems.forEach((item) => {
8718
+ effectiveItems.forEach((item) => {
8695
8719
  const category = item.category ?? "Other";
8696
8720
  const group = groups.get(category) ?? [];
8697
8721
  group.push(item);
8698
8722
  groups.set(category, group);
8699
8723
  });
8700
8724
  return groups;
8701
- }, [filteredItems]);
8725
+ }, [effectiveItems]);
8702
8726
  useEscapeKey(close, isOpen);
8703
8727
  useClickOutside(containerRef, close);
8704
8728
  useEffect(() => {
@@ -8707,8 +8731,8 @@ function CommandPalette({
8707
8731
  }
8708
8732
  }, [isOpen]);
8709
8733
  useEffect(() => {
8710
- setSelectedIndex(filteredItems.length > 0 ? 0 : -1);
8711
- }, [filteredItems.length, setSelectedIndex]);
8734
+ setSelectedIndex(effectiveItems.length > 0 ? 0 : -1);
8735
+ }, [effectiveItems.length, setSelectedIndex]);
8712
8736
  useEffect(() => {
8713
8737
  if (selectedIndex >= 0 && listRef.current) {
8714
8738
  const selectedElement = listRef.current.querySelector(
@@ -8723,7 +8747,7 @@ function CommandPalette({
8723
8747
  case "ArrowDown":
8724
8748
  e.preventDefault();
8725
8749
  setSelectedIndex(
8726
- Math.min(selectedIndex + 1, filteredItems.length - 1)
8750
+ Math.min(selectedIndex + 1, effectiveItems.length - 1)
8727
8751
  );
8728
8752
  break;
8729
8753
  case "ArrowUp":
@@ -8732,8 +8756,8 @@ function CommandPalette({
8732
8756
  break;
8733
8757
  case "Enter":
8734
8758
  e.preventDefault();
8735
- if (selectedIndex >= 0 && filteredItems[selectedIndex]) {
8736
- const item = filteredItems[selectedIndex];
8759
+ if (selectedIndex >= 0 && effectiveItems[selectedIndex]) {
8760
+ const item = effectiveItems[selectedIndex];
8737
8761
  if (!item.disabled) {
8738
8762
  onSelect?.(item);
8739
8763
  close();
@@ -8751,7 +8775,7 @@ function CommandPalette({
8751
8775
  }
8752
8776
  },
8753
8777
  [
8754
- filteredItems,
8778
+ effectiveItems,
8755
8779
  selectedIndex,
8756
8780
  setSelectedIndex,
8757
8781
  onSelect,
@@ -8772,9 +8796,15 @@ function CommandPalette({
8772
8796
  );
8773
8797
  const getCategoryInfo = useCallback(
8774
8798
  (categoryId) => {
8799
+ if (categoryId === PINNED_CATEGORY_ID) {
8800
+ return { id: PINNED_CATEGORY_ID, label: pinnedCategoryLabel };
8801
+ }
8802
+ if (categoryId === RECENT_CATEGORY_ID) {
8803
+ return { id: RECENT_CATEGORY_ID, label: recentCategoryLabel };
8804
+ }
8775
8805
  return categories.find((c) => c.id === categoryId);
8776
8806
  },
8777
- [categories]
8807
+ [categories, pinnedCategoryLabel, recentCategoryLabel]
8778
8808
  );
8779
8809
  if (!isOpen) return null;
8780
8810
  let globalIndex = -1;
@@ -8880,14 +8910,14 @@ function CommandPalette({
8880
8910
  ref: listRef,
8881
8911
  "data-slot": "command-palette-results",
8882
8912
  className: "max-h-[60vh] overflow-y-auto",
8883
- children: filteredItems.length === 0 ? /* @__PURE__ */ jsx(
8913
+ children: effectiveItems.length === 0 ? /* @__PURE__ */ jsx(
8884
8914
  "div",
8885
8915
  {
8886
8916
  "data-slot": "command-palette-empty",
8887
8917
  className: "text-muted-foreground p-8 text-center",
8888
8918
  children: emptyState ?? /* @__PURE__ */ jsxs(Fragment, { children: [
8889
8919
  /* @__PURE__ */ jsx("div", { className: "mx-auto mb-2 h-8 w-8 opacity-50", children: /* @__PURE__ */ jsx(SearchIcon2, {}) }),
8890
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: query.trim() ? `No results for "${query}"` : "Start typing to search..." })
8920
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: isLoading && query.trim() ? `Searching for "${query}"\u2026` : query.trim() ? `No results for "${query}"` : "Start typing to search..." })
8891
8921
  ] })
8892
8922
  }
8893
8923
  ) : Array.from(groupedItems.entries()).map(
@@ -9020,7 +9050,7 @@ function CommandPalette({
9020
9050
  /* @__PURE__ */ jsx("span", { children: "close" })
9021
9051
  ] }),
9022
9052
  /* @__PURE__ */ jsxs("span", { children: [
9023
- filteredItems.length,
9053
+ effectiveItems.length,
9024
9054
  " results"
9025
9055
  ] })
9026
9056
  ]
@@ -11619,6 +11649,7 @@ function DateRangePicker({
11619
11649
  activePreset,
11620
11650
  placeholder = "Pick a date range",
11621
11651
  className,
11652
+ align = "auto",
11622
11653
  showPresets = true,
11623
11654
  variant = "desktop",
11624
11655
  labels = {}
@@ -11641,6 +11672,9 @@ function DateRangePicker({
11641
11672
  const [hoverDate, setHoverDate] = React48.useState(null);
11642
11673
  const calendarRef = React48.useRef(null);
11643
11674
  const triggerRef = React48.useRef(null);
11675
+ const [resolvedAlign, setResolvedAlign] = React48.useState(
11676
+ align === "end" ? "end" : "start"
11677
+ );
11644
11678
  const isMobileVariant = variant === "mobile";
11645
11679
  const isResponsive = variant === "responsive";
11646
11680
  const focusTrapRef = useFocusTrap(
@@ -11679,6 +11713,22 @@ function DateRangePicker({
11679
11713
  };
11680
11714
  }
11681
11715
  }, [isMobileVariant, isCalendarOpen]);
11716
+ React48.useLayoutEffect(() => {
11717
+ if (isMobileVariant || !isCalendarOpen) return;
11718
+ if (align === "start" || align === "end") {
11719
+ setResolvedAlign(align);
11720
+ return;
11721
+ }
11722
+ if (typeof window === "undefined") return;
11723
+ const trigger = triggerRef.current;
11724
+ if (!trigger) return;
11725
+ const rect = trigger.getBoundingClientRect();
11726
+ const estimatedPopupWidth = showPresets ? 840 : 640;
11727
+ const margin = 8;
11728
+ const overflowsRight = rect.left + estimatedPopupWidth > window.innerWidth - margin;
11729
+ const fitsLeftAligned = rect.right - estimatedPopupWidth >= margin;
11730
+ setResolvedAlign(overflowsRight && fitsLeftAligned ? "end" : "start");
11731
+ }, [align, isCalendarOpen, isMobileVariant, showPresets]);
11682
11732
  const handlePresetSelect = (presetKey) => {
11683
11733
  const range = calculateDateRange(presetKey);
11684
11734
  setRangeStart(range.start);
@@ -12018,7 +12068,8 @@ function DateRangePicker({
12018
12068
  {
12019
12069
  ref: calendarRef,
12020
12070
  className: cn(
12021
- "absolute top-full left-0 z-50 mt-1",
12071
+ "absolute top-full z-50 mt-1",
12072
+ resolvedAlign === "end" ? "right-0" : "left-0",
12022
12073
  "bg-background border-border rounded-lg border shadow-lg"
12023
12074
  ),
12024
12075
  role: "dialog",