@sesionario/blocks 1.7.0 → 1.9.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/dist/index.cjs CHANGED
@@ -456,6 +456,68 @@ function ConfirmDestructiveDialog({
456
456
  ] })
457
457
  ] }) });
458
458
  }
459
+ var COLLAPSED_MAX_HEIGHT_REM = 11;
460
+ function ExpandableText({
461
+ children,
462
+ expandLabel,
463
+ collapseLabel,
464
+ className,
465
+ textClassName
466
+ }) {
467
+ const [expanded, setExpanded] = React6.useState(false);
468
+ const [overflowing, setOverflowing] = React6.useState(false);
469
+ const contentRef = React6.useRef(null);
470
+ React6.useEffect(() => {
471
+ setExpanded(false);
472
+ }, [children]);
473
+ React6.useLayoutEffect(() => {
474
+ const el = contentRef.current;
475
+ if (!el) return;
476
+ const measure = () => {
477
+ const rootFontSize = Number.parseFloat(
478
+ getComputedStyle(document.documentElement).fontSize
479
+ );
480
+ const maxPx = COLLAPSED_MAX_HEIGHT_REM * (rootFontSize || 16);
481
+ setOverflowing(el.scrollHeight > maxPx + 1);
482
+ };
483
+ measure();
484
+ const observer = new ResizeObserver(measure);
485
+ observer.observe(el);
486
+ return () => observer.disconnect();
487
+ }, [children]);
488
+ const collapsed = !expanded && overflowing;
489
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-1", className), children: [
490
+ /* @__PURE__ */ jsxRuntime.jsx(
491
+ "div",
492
+ {
493
+ className: cn(
494
+ collapsed && "max-h-[11rem] overflow-hidden scroll-fade-b"
495
+ ),
496
+ children: /* @__PURE__ */ jsxRuntime.jsx(
497
+ "p",
498
+ {
499
+ ref: contentRef,
500
+ className: cn(
501
+ "whitespace-pre-wrap text-sm leading-relaxed",
502
+ textClassName
503
+ ),
504
+ children
505
+ }
506
+ )
507
+ }
508
+ ),
509
+ overflowing ? /* @__PURE__ */ jsxRuntime.jsx(
510
+ "button",
511
+ {
512
+ type: "button",
513
+ className: "text-sm font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded-sm",
514
+ "aria-expanded": expanded,
515
+ onClick: () => setExpanded((prev) => !prev),
516
+ children: expanded ? collapseLabel : expandLabel
517
+ }
518
+ ) : null
519
+ ] });
520
+ }
459
521
  var MIN_SCALE = 1;
460
522
  var MAX_SCALE = 4;
461
523
  var DOUBLE_TAP_MS = 300;
@@ -1349,7 +1411,7 @@ function LanguageSwitcher({
1349
1411
  const sizes = sizeClasses2[size];
1350
1412
  const shapeClass = shapeClasses2[shape];
1351
1413
  const reduceMotion = framerMotion.useReducedMotion();
1352
- const defaultLabels4 = {
1414
+ const defaultLabels5 = {
1353
1415
  language: labels.language ?? "Language"
1354
1416
  };
1355
1417
  if (variant === "mini") {
@@ -1359,7 +1421,7 @@ function LanguageSwitcher({
1359
1421
  {
1360
1422
  variant: "ghost",
1361
1423
  size: "icon",
1362
- "aria-label": defaultLabels4.language,
1424
+ "aria-label": defaultLabels5.language,
1363
1425
  className: cn(sizes.buttonMini, shapeClass, className),
1364
1426
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Languages, { className: sizes.iconMini })
1365
1427
  }
@@ -4286,6 +4348,18 @@ function useDebouncedValueStrict(value, delayMs) {
4286
4348
  }, [value, delayMs]);
4287
4349
  return debounced;
4288
4350
  }
4351
+ function useMediaQuery2(query) {
4352
+ const [matches, setMatches] = React6.useState(false);
4353
+ React6.useEffect(() => {
4354
+ if (typeof window === "undefined" || !window.matchMedia) return;
4355
+ const mql = window.matchMedia(query);
4356
+ const onChange = () => setMatches(mql.matches);
4357
+ onChange();
4358
+ mql.addEventListener("change", onChange);
4359
+ return () => mql.removeEventListener("change", onChange);
4360
+ }, [query]);
4361
+ return matches;
4362
+ }
4289
4363
  function useSearchHotkey(options = {}) {
4290
4364
  const { defaultOpen = false, disabled = false } = options;
4291
4365
  const [open, setOpen] = React6.useState(defaultOpen);
@@ -4303,6 +4377,22 @@ function useSearchHotkey(options = {}) {
4303
4377
  }, [disabled]);
4304
4378
  return { open, setOpen, toggle };
4305
4379
  }
4380
+
4381
+ // src/components/search/fuzzy.ts
4382
+ function normalize(value) {
4383
+ return value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4384
+ }
4385
+ function fuzzyMatch(query, target) {
4386
+ const q = normalize(query.trim());
4387
+ if (!q) return true;
4388
+ const t = normalize(target);
4389
+ if (t.includes(q)) return true;
4390
+ let qi = 0;
4391
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
4392
+ if (t[ti] === q[qi]) qi++;
4393
+ }
4394
+ return qi === q.length;
4395
+ }
4306
4396
  function Command({
4307
4397
  className,
4308
4398
  ...props
@@ -4420,6 +4510,19 @@ function CommandGroup({
4420
4510
  }
4421
4511
  );
4422
4512
  }
4513
+ function CommandSeparator({
4514
+ className,
4515
+ ...props
4516
+ }) {
4517
+ return /* @__PURE__ */ jsxRuntime.jsx(
4518
+ cmdk.Command.Separator,
4519
+ {
4520
+ "data-slot": "command-separator",
4521
+ className: cn("-mx-1 h-px bg-border", className),
4522
+ ...props
4523
+ }
4524
+ );
4525
+ }
4423
4526
  function CommandItem({
4424
4527
  className,
4425
4528
  ...props
@@ -4436,23 +4539,584 @@ function CommandItem({
4436
4539
  }
4437
4540
  );
4438
4541
  }
4439
-
4440
- // src/components/search/fuzzy.ts
4441
- function normalize(value) {
4442
- return value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4542
+ function OptionRow({
4543
+ entry,
4544
+ selected,
4545
+ onSelect,
4546
+ renderOption
4547
+ }) {
4548
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4549
+ CommandItem,
4550
+ {
4551
+ value: entry.value,
4552
+ keywords: [entry.label],
4553
+ onSelect,
4554
+ "data-checked": selected || void 0,
4555
+ children: [
4556
+ renderOption ? renderOption(entry.item, { selected }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.label }),
4557
+ /* @__PURE__ */ jsxRuntime.jsx(
4558
+ lucideReact.CheckIcon,
4559
+ {
4560
+ className: cn(
4561
+ "ml-auto size-4 shrink-0",
4562
+ selected ? "opacity-100" : "opacity-0"
4563
+ )
4564
+ }
4565
+ )
4566
+ ]
4567
+ },
4568
+ entry.value
4569
+ );
4443
4570
  }
4444
- function fuzzyMatch(query, target) {
4445
- const q = normalize(query.trim());
4446
- if (!q) return true;
4447
- const t = normalize(target);
4448
- if (t.includes(q)) return true;
4449
- let qi = 0;
4450
- for (let ti = 0; ti < t.length && qi < q.length; ti++) {
4451
- if (t[ti] === q[qi]) qi++;
4452
- }
4453
- return qi === q.length;
4571
+ function SearchablePickerList({
4572
+ query,
4573
+ onQueryChange,
4574
+ labels,
4575
+ isLoading,
4576
+ hasError,
4577
+ recommended,
4578
+ recent,
4579
+ results,
4580
+ selectedValues,
4581
+ mode,
4582
+ allowEmpty,
4583
+ emptyLabel,
4584
+ onSelectEmpty,
4585
+ onSelectItem,
4586
+ renderOption,
4587
+ createOption,
4588
+ onLoadMore,
4589
+ hasMore,
4590
+ isLoadingMore,
4591
+ listClassName
4592
+ }) {
4593
+ const hasPinned = recommended.length > 0 || recent.length > 0;
4594
+ const hasResults = results.length > 0;
4595
+ const showEmpty = !isLoading && !hasError && !hasPinned && !hasResults && !(allowEmpty && mode === "single");
4596
+ return /* @__PURE__ */ jsxRuntime.jsxs(Command, { shouldFilter: false, className: "flex h-full min-h-0 flex-col", children: [
4597
+ /* @__PURE__ */ jsxRuntime.jsx(
4598
+ CommandInput,
4599
+ {
4600
+ placeholder: labels.searchPlaceholder,
4601
+ value: query,
4602
+ onValueChange: onQueryChange
4603
+ }
4604
+ ),
4605
+ /* @__PURE__ */ jsxRuntime.jsxs(CommandList, { className: cn("min-h-0 flex-1", listClassName), children: [
4606
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(
4607
+ "div",
4608
+ {
4609
+ className: "flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground",
4610
+ role: "status",
4611
+ children: [
4612
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
4613
+ labels.loading
4614
+ ]
4615
+ }
4616
+ ) : null,
4617
+ hasError && !isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
4618
+ "div",
4619
+ {
4620
+ className: "py-6 text-center text-sm text-destructive",
4621
+ role: "alert",
4622
+ children: labels.error
4623
+ }
4624
+ ) : null,
4625
+ showEmpty ? /* @__PURE__ */ jsxRuntime.jsx(CommandEmpty, { children: labels.empty }) : null,
4626
+ allowEmpty && mode === "single" && !query.trim() ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { children: /* @__PURE__ */ jsxRuntime.jsxs(
4627
+ CommandItem,
4628
+ {
4629
+ value: "__empty__",
4630
+ onSelect: () => onSelectEmpty?.(),
4631
+ "data-checked": !selectedValues.size || void 0,
4632
+ children: [
4633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: emptyLabel }),
4634
+ /* @__PURE__ */ jsxRuntime.jsx(
4635
+ lucideReact.CheckIcon,
4636
+ {
4637
+ className: cn(
4638
+ "ml-auto size-4 shrink-0",
4639
+ selectedValues.size === 0 ? "opacity-100" : "opacity-0"
4640
+ )
4641
+ }
4642
+ )
4643
+ ]
4644
+ }
4645
+ ) }) : null,
4646
+ recommended.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: labels.recommended, children: recommended.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4647
+ OptionRow,
4648
+ {
4649
+ entry,
4650
+ selected: selectedValues.has(entry.value),
4651
+ onSelect: () => onSelectItem(entry.value),
4652
+ renderOption
4653
+ },
4654
+ `rec-${entry.value}`
4655
+ )) }) : null,
4656
+ recent.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: labels.recent, children: recent.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4657
+ OptionRow,
4658
+ {
4659
+ entry,
4660
+ selected: selectedValues.has(entry.value),
4661
+ onSelect: () => onSelectItem(entry.value),
4662
+ renderOption
4663
+ },
4664
+ `recent-${entry.value}`
4665
+ )) }) : null,
4666
+ hasPinned && hasResults ? /* @__PURE__ */ jsxRuntime.jsx(CommandSeparator, {}) : null,
4667
+ hasResults ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: hasPinned ? labels.results : void 0, children: results.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4668
+ OptionRow,
4669
+ {
4670
+ entry,
4671
+ selected: selectedValues.has(entry.value),
4672
+ onSelect: () => onSelectItem(entry.value),
4673
+ renderOption
4674
+ },
4675
+ `res-${entry.value}`
4676
+ )) }) : null,
4677
+ hasMore && onLoadMore ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
4678
+ Button,
4679
+ {
4680
+ type: "button",
4681
+ variant: "ghost",
4682
+ size: "sm",
4683
+ className: "w-full",
4684
+ disabled: isLoadingMore,
4685
+ onClick: () => void onLoadMore(),
4686
+ children: isLoadingMore ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4687
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
4688
+ labels.loading
4689
+ ] }) : labels.loadMore
4690
+ }
4691
+ ) }) : null,
4692
+ createOption ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4693
+ /* @__PURE__ */ jsxRuntime.jsx(CommandSeparator, {}),
4694
+ /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { children: /* @__PURE__ */ jsxRuntime.jsx(
4695
+ CommandItem,
4696
+ {
4697
+ value: "__create__",
4698
+ onSelect: () => createOption.onSelect(),
4699
+ children: createOption.label
4700
+ }
4701
+ ) })
4702
+ ] }) : null
4703
+ ] })
4704
+ ] });
4454
4705
  }
4455
4706
  var defaultLabels3 = {
4707
+ placeholder: "Seleccionar\u2026",
4708
+ searchPlaceholder: "Buscar\u2026",
4709
+ empty: "No se encontraron resultados",
4710
+ loading: "Cargando\u2026",
4711
+ error: "No se pudo cargar la lista",
4712
+ recommended: "Recomendados",
4713
+ recent: "Recientes",
4714
+ results: "Resultados",
4715
+ done: "Listo",
4716
+ cancel: "Cancelar",
4717
+ clear: "Limpiar",
4718
+ selectedCount: (count) => count === 1 ? "1 seleccionado" : `${count} seleccionados`,
4719
+ emptyOption: "Ninguno",
4720
+ loadMore: "Cargar m\xE1s",
4721
+ dialogTitle: "Seleccionar"
4722
+ };
4723
+ function toListItems(items, getOptionValue, getOptionLabel) {
4724
+ if (!items?.length) return [];
4725
+ return items.map((item) => ({
4726
+ item,
4727
+ value: getOptionValue(item),
4728
+ label: getOptionLabel(item)
4729
+ }));
4730
+ }
4731
+ function dedupeByValue(items, exclude = /* @__PURE__ */ new Set()) {
4732
+ const seen = new Set(exclude);
4733
+ const out = [];
4734
+ for (const entry of items) {
4735
+ if (seen.has(entry.value)) continue;
4736
+ seen.add(entry.value);
4737
+ out.push(entry);
4738
+ }
4739
+ return out;
4740
+ }
4741
+ function matchesQuery(entry, query, getOptionKeywords) {
4742
+ if (!query.trim()) return true;
4743
+ if (fuzzyMatch(query, entry.label)) return true;
4744
+ return getOptionKeywords?.(entry.item)?.some(
4745
+ (keyword) => fuzzyMatch(query, keyword)
4746
+ ) ?? false;
4747
+ }
4748
+ function SearchablePicker(props) {
4749
+ const {
4750
+ options,
4751
+ fetchOptions,
4752
+ getOptionValue,
4753
+ getOptionLabel,
4754
+ getOptionKeywords,
4755
+ renderOption,
4756
+ renderValue,
4757
+ pinned,
4758
+ labels: userLabels,
4759
+ minQueryLength = 0,
4760
+ debounceMs = 200,
4761
+ disabled,
4762
+ open: openProp,
4763
+ onOpenChange,
4764
+ clearable,
4765
+ allowEmpty,
4766
+ emptyLabel,
4767
+ keepOpenOnSelect,
4768
+ maxSelected,
4769
+ createOption,
4770
+ onLoadMore,
4771
+ hasMore,
4772
+ className,
4773
+ triggerClassName: triggerClassName2,
4774
+ id,
4775
+ "aria-labelledby": ariaLabelledBy,
4776
+ "aria-label": ariaLabel
4777
+ } = props;
4778
+ const mode = props.mode ?? "single";
4779
+ const labels = { ...defaultLabels3, ...userLabels };
4780
+ const resolvedEmptyLabel = emptyLabel ?? labels.emptyOption;
4781
+ const isMobile = useMediaQuery2("(max-width: 767px)");
4782
+ const generatedId = React6.useId();
4783
+ const triggerId = id ?? generatedId;
4784
+ const [uncontrolledOpen, setUncontrolledOpen] = React6.useState(false);
4785
+ const open = openProp ?? uncontrolledOpen;
4786
+ const setOpen = React6.useCallback(
4787
+ (next) => {
4788
+ onOpenChange?.(next);
4789
+ if (openProp === void 0) setUncontrolledOpen(next);
4790
+ },
4791
+ [onOpenChange, openProp]
4792
+ );
4793
+ const [query, setQuery] = React6.useState("");
4794
+ const [remoteItems, setRemoteItems] = React6.useState([]);
4795
+ const [isLoading, setIsLoading] = React6.useState(false);
4796
+ const [hasError, setHasError] = React6.useState(false);
4797
+ const [isLoadingMore, setIsLoadingMore] = React6.useState(false);
4798
+ const debouncedQuery = useDebouncedValue(query, debounceMs);
4799
+ const requestIdRef = React6.useRef(0);
4800
+ const knownItemsRef = React6.useRef(/* @__PURE__ */ new Map());
4801
+ const rememberItems = React6.useCallback(
4802
+ (items) => {
4803
+ for (const item of items) {
4804
+ knownItemsRef.current.set(getOptionValue(item), item);
4805
+ }
4806
+ },
4807
+ [getOptionValue]
4808
+ );
4809
+ React6.useEffect(() => {
4810
+ if (options) rememberItems(options);
4811
+ if (pinned?.recommended) rememberItems(pinned.recommended);
4812
+ if (pinned?.recent) rememberItems(pinned.recent);
4813
+ }, [options, pinned, rememberItems]);
4814
+ React6.useEffect(() => {
4815
+ if (!open) {
4816
+ setQuery("");
4817
+ setRemoteItems([]);
4818
+ setHasError(false);
4819
+ setIsLoading(false);
4820
+ }
4821
+ }, [open]);
4822
+ const trimmedDebounced = debouncedQuery.trim();
4823
+ const shouldFetch = Boolean(fetchOptions) && open && trimmedDebounced.length >= minQueryLength && (trimmedDebounced.length > 0 || !options);
4824
+ React6.useEffect(() => {
4825
+ if (!shouldFetch || !fetchOptions) {
4826
+ if (!trimmedDebounced) {
4827
+ setRemoteItems([]);
4828
+ setHasError(false);
4829
+ setIsLoading(false);
4830
+ }
4831
+ return;
4832
+ }
4833
+ const requestId = ++requestIdRef.current;
4834
+ setIsLoading(true);
4835
+ setHasError(false);
4836
+ fetchOptions(trimmedDebounced).then((result) => {
4837
+ if (requestIdRef.current !== requestId) return;
4838
+ setRemoteItems(result);
4839
+ rememberItems(result);
4840
+ }).catch(() => {
4841
+ if (requestIdRef.current !== requestId) return;
4842
+ setRemoteItems([]);
4843
+ setHasError(true);
4844
+ }).finally(() => {
4845
+ if (requestIdRef.current !== requestId) return;
4846
+ setIsLoading(false);
4847
+ });
4848
+ }, [shouldFetch, fetchOptions, trimmedDebounced, rememberItems]);
4849
+ const selectedValues = React6.useMemo(() => {
4850
+ if (mode === "multiple") {
4851
+ const multiValue = props.value ?? [];
4852
+ return new Set(multiValue);
4853
+ }
4854
+ const singleValue = props.value;
4855
+ return singleValue ? /* @__PURE__ */ new Set([singleValue]) : /* @__PURE__ */ new Set();
4856
+ }, [mode, props]);
4857
+ const selectedValueKey = mode === "multiple" ? (props.value ?? []).join("\0") : props.value ?? "";
4858
+ const selectedItems = React6.useMemo(() => {
4859
+ const values = mode === "multiple" ? selectedValueKey ? selectedValueKey.split("\0") : [] : selectedValueKey ? [selectedValueKey] : [];
4860
+ return values.map((value) => knownItemsRef.current.get(value)).filter((item) => item !== void 0);
4861
+ }, [mode, selectedValueKey, options, pinned, remoteItems]);
4862
+ const queryForFilter = query.trim();
4863
+ const recommended = React6.useMemo(() => {
4864
+ if (queryForFilter) return [];
4865
+ const pinnedValues = /* @__PURE__ */ new Set();
4866
+ const items = dedupeByValue(
4867
+ toListItems(pinned?.recommended, getOptionValue, getOptionLabel)
4868
+ );
4869
+ for (const entry of items) pinnedValues.add(entry.value);
4870
+ return items;
4871
+ }, [queryForFilter, pinned?.recommended, getOptionValue, getOptionLabel]);
4872
+ const recent = React6.useMemo(() => {
4873
+ if (queryForFilter) return [];
4874
+ const exclude = new Set(recommended.map((e) => e.value));
4875
+ return dedupeByValue(
4876
+ toListItems(pinned?.recent, getOptionValue, getOptionLabel),
4877
+ exclude
4878
+ );
4879
+ }, [
4880
+ queryForFilter,
4881
+ pinned?.recent,
4882
+ recommended,
4883
+ getOptionValue,
4884
+ getOptionLabel
4885
+ ]);
4886
+ const pinnedValueSet = React6.useMemo(() => {
4887
+ const set = /* @__PURE__ */ new Set();
4888
+ for (const entry of recommended) set.add(entry.value);
4889
+ for (const entry of recent) set.add(entry.value);
4890
+ return set;
4891
+ }, [recommended, recent]);
4892
+ const results = React6.useMemo(() => {
4893
+ const usingRemote = Boolean(fetchOptions) && trimmedDebounced.length >= minQueryLength && (trimmedDebounced.length > 0 || !options);
4894
+ if (usingRemote) {
4895
+ return dedupeByValue(
4896
+ toListItems(remoteItems, getOptionValue, getOptionLabel),
4897
+ queryForFilter ? /* @__PURE__ */ new Set() : pinnedValueSet
4898
+ );
4899
+ }
4900
+ const staticItems = toListItems(options, getOptionValue, getOptionLabel);
4901
+ const filtered = staticItems.filter(
4902
+ (entry) => matchesQuery(entry, queryForFilter, getOptionKeywords)
4903
+ );
4904
+ return dedupeByValue(filtered, queryForFilter ? /* @__PURE__ */ new Set() : pinnedValueSet);
4905
+ }, [
4906
+ fetchOptions,
4907
+ trimmedDebounced,
4908
+ minQueryLength,
4909
+ options,
4910
+ remoteItems,
4911
+ getOptionValue,
4912
+ getOptionLabel,
4913
+ getOptionKeywords,
4914
+ queryForFilter,
4915
+ pinnedValueSet
4916
+ ]);
4917
+ const closeOnSelect = keepOpenOnSelect !== void 0 ? !keepOpenOnSelect : mode === "single";
4918
+ const handleSelectEmpty = React6.useCallback(() => {
4919
+ if (mode === "single") {
4920
+ props.onValueChange?.(
4921
+ null
4922
+ );
4923
+ }
4924
+ if (closeOnSelect) setOpen(false);
4925
+ }, [mode, props, closeOnSelect, setOpen]);
4926
+ const handleSelectItem = React6.useCallback(
4927
+ (value) => {
4928
+ if (mode === "multiple") {
4929
+ const current = props.value ?? [];
4930
+ const onChange = props.onValueChange;
4931
+ const exists = current.includes(value);
4932
+ if (exists) {
4933
+ onChange?.(current.filter((v) => v !== value));
4934
+ } else if (maxSelected !== void 0 && current.length >= maxSelected) {
4935
+ return;
4936
+ } else {
4937
+ onChange?.([...current, value]);
4938
+ }
4939
+ if (closeOnSelect) setOpen(false);
4940
+ return;
4941
+ }
4942
+ props.onValueChange?.(
4943
+ value
4944
+ );
4945
+ if (closeOnSelect) setOpen(false);
4946
+ },
4947
+ [mode, props, maxSelected, closeOnSelect, setOpen]
4948
+ );
4949
+ const handleClear = React6.useCallback(
4950
+ (event) => {
4951
+ event.preventDefault();
4952
+ event.stopPropagation();
4953
+ if (mode === "multiple") {
4954
+ props.onValueChange?.(
4955
+ []
4956
+ );
4957
+ } else {
4958
+ props.onValueChange?.(null);
4959
+ }
4960
+ },
4961
+ [mode, props]
4962
+ );
4963
+ const handleLoadMore = React6.useCallback(async () => {
4964
+ if (!onLoadMore || isLoadingMore) return;
4965
+ setIsLoadingMore(true);
4966
+ try {
4967
+ await onLoadMore();
4968
+ } finally {
4969
+ setIsLoadingMore(false);
4970
+ }
4971
+ }, [onLoadMore, isLoadingMore]);
4972
+ const triggerLabel = React6.useMemo(() => {
4973
+ if (renderValue) {
4974
+ if (mode === "multiple") {
4975
+ return renderValue(selectedItems);
4976
+ }
4977
+ return renderValue(selectedItems[0] ?? null);
4978
+ }
4979
+ if (mode === "multiple") {
4980
+ const count = selectedValues.size;
4981
+ if (count === 0) {
4982
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: labels.placeholder });
4983
+ }
4984
+ if (selectedItems.length === 1) {
4985
+ return getOptionLabel(selectedItems[0]);
4986
+ }
4987
+ return labels.selectedCount(count);
4988
+ }
4989
+ if (selectedValues.size === 0) {
4990
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: allowEmpty ? resolvedEmptyLabel : labels.placeholder });
4991
+ }
4992
+ if (selectedItems[0]) {
4993
+ return getOptionLabel(selectedItems[0]);
4994
+ }
4995
+ const only = [...selectedValues][0];
4996
+ return only ?? labels.placeholder;
4997
+ }, [
4998
+ renderValue,
4999
+ mode,
5000
+ selectedItems,
5001
+ selectedValues,
5002
+ labels,
5003
+ getOptionLabel,
5004
+ allowEmpty,
5005
+ resolvedEmptyLabel
5006
+ ]);
5007
+ const showClear = clearable && !disabled && (mode === "multiple" ? selectedValues.size > 0 : selectedValues.size > 0);
5008
+ const list = /* @__PURE__ */ jsxRuntime.jsx(
5009
+ SearchablePickerList,
5010
+ {
5011
+ query,
5012
+ onQueryChange: setQuery,
5013
+ labels,
5014
+ isLoading,
5015
+ hasError,
5016
+ recommended,
5017
+ recent,
5018
+ results,
5019
+ selectedValues,
5020
+ mode,
5021
+ allowEmpty,
5022
+ emptyLabel: resolvedEmptyLabel,
5023
+ onSelectEmpty: handleSelectEmpty,
5024
+ onSelectItem: handleSelectItem,
5025
+ renderOption,
5026
+ createOption,
5027
+ onLoadMore: onLoadMore ? handleLoadMore : void 0,
5028
+ hasMore,
5029
+ isLoadingMore,
5030
+ listClassName: isMobile ? "max-h-[min(50vh,360px)]" : "max-h-72"
5031
+ }
5032
+ );
5033
+ const triggerButton = /* @__PURE__ */ jsxRuntime.jsxs(
5034
+ Button,
5035
+ {
5036
+ type: "button",
5037
+ variant: "outline",
5038
+ role: "combobox",
5039
+ id: triggerId,
5040
+ "aria-expanded": open,
5041
+ "aria-labelledby": ariaLabelledBy,
5042
+ "aria-label": ariaLabel ?? labels.placeholder,
5043
+ disabled,
5044
+ className: cn("w-full justify-between font-normal", triggerClassName2),
5045
+ children: [
5046
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: triggerLabel }),
5047
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50", "aria-hidden": true })
5048
+ ]
5049
+ }
5050
+ );
5051
+ const clearButton = showClear ? /* @__PURE__ */ jsxRuntime.jsx(
5052
+ Button,
5053
+ {
5054
+ type: "button",
5055
+ variant: "ghost",
5056
+ size: "icon-sm",
5057
+ "aria-label": labels.clear,
5058
+ disabled,
5059
+ className: "shrink-0",
5060
+ onClick: handleClear,
5061
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
5062
+ }
5063
+ ) : null;
5064
+ if (isMobile) {
5065
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full items-center gap-1", className), children: [
5066
+ /* @__PURE__ */ jsxRuntime.jsx(
5067
+ "div",
5068
+ {
5069
+ className: "min-w-0 flex-1",
5070
+ onClick: () => {
5071
+ if (!disabled) setOpen(true);
5072
+ },
5073
+ children: triggerButton
5074
+ }
5075
+ ),
5076
+ clearButton,
5077
+ /* @__PURE__ */ jsxRuntime.jsx(Drawer, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(DrawerContent, { className: "max-h-[90vh]", children: [
5078
+ /* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "text-left", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: labels.dialogTitle }) }),
5079
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-hidden px-2 pb-2", children: list }),
5080
+ mode === "multiple" ? /* @__PURE__ */ jsxRuntime.jsxs(DrawerFooter, { className: "flex-row gap-2", children: [
5081
+ /* @__PURE__ */ jsxRuntime.jsx(
5082
+ Button,
5083
+ {
5084
+ type: "button",
5085
+ variant: "outline",
5086
+ className: "flex-1",
5087
+ onClick: () => setOpen(false),
5088
+ children: labels.cancel
5089
+ }
5090
+ ),
5091
+ /* @__PURE__ */ jsxRuntime.jsx(
5092
+ Button,
5093
+ {
5094
+ type: "button",
5095
+ className: "flex-1",
5096
+ onClick: () => setOpen(false),
5097
+ children: labels.done
5098
+ }
5099
+ )
5100
+ ] }) : null
5101
+ ] }) })
5102
+ ] });
5103
+ }
5104
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full items-center gap-1", className), children: [
5105
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, modal: true, children: [
5106
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: triggerButton }),
5107
+ /* @__PURE__ */ jsxRuntime.jsx(
5108
+ PopoverContent,
5109
+ {
5110
+ className: "w-[var(--radix-popover-trigger-width)] min-w-[240px] p-0",
5111
+ align: "start",
5112
+ children: list
5113
+ }
5114
+ )
5115
+ ] }) }),
5116
+ clearButton
5117
+ ] });
5118
+ }
5119
+ var defaultLabels4 = {
4456
5120
  placeholder: "Buscar en toda la plataforma\u2026",
4457
5121
  dialogTitle: "Buscar",
4458
5122
  dialogDescription: "Busca funcionalidades, eventos, documentos y m\xE1s",
@@ -4486,7 +5150,7 @@ function SearchCommand({
4486
5150
  debounceMs = 200,
4487
5151
  className
4488
5152
  }) {
4489
- const labels = { ...defaultLabels3, ...userLabels };
5153
+ const labels = { ...defaultLabels4, ...userLabels };
4490
5154
  const [query, setQuery] = React6.useState("");
4491
5155
  const [hits, setHits] = React6.useState([]);
4492
5156
  const [isLoading, setIsLoading] = React6.useState(false);
@@ -5367,18 +6031,6 @@ function ChatMessageList({
5367
6031
  /* @__PURE__ */ jsxRuntime.jsx(MessageScrollerButton, {})
5368
6032
  ] }) });
5369
6033
  }
5370
- function useMediaQuery2(query) {
5371
- const [matches, setMatches] = React6.useState(false);
5372
- React6.useEffect(() => {
5373
- if (typeof window === "undefined" || !window.matchMedia) return;
5374
- const mql = window.matchMedia(query);
5375
- const onChange = () => setMatches(mql.matches);
5376
- onChange();
5377
- mql.addEventListener("change", onChange);
5378
- return () => mql.removeEventListener("change", onChange);
5379
- }, [query]);
5380
- return matches;
5381
- }
5382
6034
  var RECONNECT_DELAY_MS = 2e3;
5383
6035
  function useSupportConversation({
5384
6036
  client,
@@ -6029,6 +6681,7 @@ exports.EmptyMedia = EmptyMedia;
6029
6681
  exports.EmptyState = EmptyState;
6030
6682
  exports.EmptyTitle = EmptyTitle;
6031
6683
  exports.ErrorState = ErrorState;
6684
+ exports.ExpandableText = ExpandableText;
6032
6685
  exports.ImageLightbox = ImageLightbox;
6033
6686
  exports.LanguageContext = LanguageContext;
6034
6687
  exports.LanguageSwitcher = LanguageSwitcher;
@@ -6054,6 +6707,7 @@ exports.MexicoMap = MexicoMap;
6054
6707
  exports.OrgSwitcher = OrgSwitcher;
6055
6708
  exports.PhoneInput = PhoneInput;
6056
6709
  exports.SearchCommand = SearchCommand;
6710
+ exports.SearchablePicker = SearchablePicker;
6057
6711
  exports.SupportMessengerProvider = SupportMessengerProvider;
6058
6712
  exports.ThemeSwitcher = ThemeSwitcher;
6059
6713
  exports.VerifiedProfessionalBadge = VerifiedProfessionalBadge;
@@ -6077,6 +6731,7 @@ exports.useDebouncedValue = useDebouncedValue;
6077
6731
  exports.useDebouncedValueStrict = useDebouncedValueStrict;
6078
6732
  exports.useHlsPlayback = useHlsPlayback;
6079
6733
  exports.useLanguageContext = useLanguageContext;
6734
+ exports.useMediaQuery = useMediaQuery2;
6080
6735
  exports.usePlaybackStats = usePlaybackStats;
6081
6736
  exports.useSearchHotkey = useSearchHotkey;
6082
6737
  exports.useSupportConversation = useSupportConversation;