@studiolxd/brand 49.1.0 → 49.2.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/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ El paquete sigue [semver](https://semver.org/lang/es/): **patch** para bug fixes
7
7
  regeneración de `dist`, **minor** para componentes/props/variantes/tokens nuevos, **major**
8
8
  para breaking changes.
9
9
 
10
+ ## [49.2.0] — 2026-09-16
11
+
12
+ > **Minor.** `CommandPalette` deja de ser dueña exclusiva de su consulta: `onQueryChange`
13
+ > avisa de cada cambio del texto del buscador (y de `""` al cerrarse) y `filter`
14
+ > (`'internal'` | `'none'`, global o por grupo con `CommandPaletteGroup.filter`) permite
15
+ > apagar el filtrado interno para un grupo que ya llega filtrado desde fuera.
16
+
17
+ ### El porqué
18
+
19
+ Un consumidor que busca en servidor (el `PublicCommandMenu` de la suite, resultados de una
20
+ API con *debounce*) no tenía forma de leer lo que el usuario tecleaba salvo envolviendo la
21
+ paleta en un contenedor con `onInput` y leyendo el valor por el árbol — una lectura por la
22
+ puerta de atrás. Ese mismo consumidor, además, tenía que meter la consulta en `keywords` de
23
+ cada resultado para que el filtrado interno no se los comiera, aunque los resultados ya
24
+ llegaran filtrados.
25
+
26
+ ### El arreglo
27
+
28
+ `onQueryChange?: (query: string) => void` se llama con el texto tal cual lo escribe quien
29
+ usa la paleta, y con `""` cuando la paleta se cierra, para que el consumidor no arrastre una
30
+ consulta vieja a la siguiente apertura. `query?: string` permite además controlarla desde
31
+ fuera. `filter?: 'internal' | 'none'` (por defecto `'internal'`, sin cambio de
32
+ comportamiento) apaga el filtrado por `label`/`keywords` cuando vale `'none'`; se puede fijar
33
+ también por grupo (`CommandPaletteGroup.filter`) para mezclar, en una misma paleta, un grupo
34
+ con filtrado interno y otro que ya llega filtrado de fuera. Story «Búsqueda en servidor» y
35
+ MDX § «Buscar fuera de la paleta».
36
+
10
37
  ## [49.1.0] — 2026-09-16
11
38
 
12
39
  > **Minor.** Regla visual nueva, sin cambios de API: **un enlace dentro de un `Alert` o de un
@@ -37,6 +37,13 @@ export interface CommandPaletteGroup {
37
37
  id: string;
38
38
  heading: string;
39
39
  items: CommandPaletteItem[];
40
+ /**
41
+ * Filtrado de este grupo. Anula al `filter` global de la paleta:
42
+ * - `'internal'` (por defecto, el del global): filtra por `label`/`keywords` como siempre.
43
+ * - `'none'`: el grupo enseña sus ítems tal cual, sin comparar con la consulta —
44
+ * para un grupo que ya llega filtrado desde fuera (búsqueda en servidor).
45
+ */
46
+ filter?: 'internal' | 'none';
40
47
  }
41
48
  export interface CommandPaletteProps {
42
49
  open: boolean;
@@ -81,6 +88,27 @@ export interface CommandPaletteProps {
81
88
  * Por defecto, el del entorno.
82
89
  */
83
90
  locale?: Intl.LocalesArgument;
91
+ /**
92
+ * Filtrado global de la paleta:
93
+ * - `'internal'` (por defecto): filtra los ítems por `label`/`keywords` contra la consulta.
94
+ * - `'none'`: la paleta no filtra nada — enseña los ítems de `groups` tal cual se los
95
+ * pasan. Es lo que necesita una búsqueda en servidor (con `onQueryChange` y su propio
96
+ * debounce), que ya trae los resultados filtrados.
97
+ *
98
+ * Un grupo concreto puede anular este valor con `CommandPaletteGroup.filter`.
99
+ */
100
+ filter?: 'internal' | 'none';
101
+ /**
102
+ * Consulta del buscador, para controlarla desde fuera (p. ej. resetearla al elegir un
103
+ * filtro externo). Sin ella, la paleta lleva su propio estado interno.
104
+ */
105
+ query?: string;
106
+ /**
107
+ * Se llama con cada cambio del texto del buscador (ya recortado por el filtrado interno
108
+ * cuando lo hay) y con `""` al cerrarse la paleta. Es la vía de lectura de la consulta
109
+ * para quien necesite buscar en servidor sin envolver la paleta ni leer el DOM.
110
+ */
111
+ onQueryChange?: (query: string) => void;
84
112
  className?: string;
85
113
  }
86
114
  /**
@@ -92,4 +120,4 @@ export interface CommandPaletteProps {
92
120
  * de Base UI en modo `inline` — sin popup propio, porque la superficie ya la
93
121
  * pone el `Modal`, que también se queda con Escape y el foco atrapado.
94
122
  */
95
- export declare function CommandPalette({ open, onOpenChange, groups, title, placeholder, emptyLabel, listLabel, closeLabel, shortcut, locale, className, }: CommandPaletteProps): import("react/jsx-runtime").JSX.Element;
123
+ export declare function CommandPalette({ open, onOpenChange, groups, title, placeholder, emptyLabel, listLabel, closeLabel, shortcut, locale, filter: filterMode, query, onQueryChange, className, }: CommandPaletteProps): import("react/jsx-runtime").JSX.Element;
@@ -2,63 +2,71 @@
2
2
  import './command-palette.css';
3
3
  import { n as e } from "./_shared/brandmessagescontext.js";
4
4
  import { Modal as t } from "./modal.js";
5
- import { useCallback as n, useEffect as r } from "react";
6
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
7
- import { Autocomplete as o } from "@base-ui/react/autocomplete";
5
+ import { useCallback as n, useEffect as r, useRef as i } from "react";
6
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
7
+ import { Autocomplete as s } from "@base-ui/react/autocomplete";
8
8
  //#region src/stories/molecules/CommandPalette/CommandPalette.tsx
9
- function s({ open: s, onOpenChange: c, groups: l, title: u, placeholder: d, emptyLabel: f, listLabel: p, closeLabel: m, shortcut: h = "k", locale: g, className: _ }) {
10
- let v = e("commandPalette");
9
+ function c({ open: c, onOpenChange: l, groups: u, title: d, placeholder: f, emptyLabel: p, listLabel: m, closeLabel: h, shortcut: g = "k", locale: _, filter: v = "internal", query: y, onQueryChange: b, className: x }) {
10
+ let S = e("commandPalette"), C = i(c);
11
11
  r(() => {
12
- if (h === !1) return;
12
+ C.current && !c && b?.(""), C.current = c;
13
+ }, [c, b]), r(() => {
14
+ if (g === !1) return;
13
15
  let e = (e) => {
14
- typeof e.key == "string" && e.key.toLowerCase() === h && (e.metaKey || e.ctrlKey) && (e.preventDefault(), c(!s));
16
+ typeof e.key == "string" && e.key.toLowerCase() === g && (e.metaKey || e.ctrlKey) && (e.preventDefault(), l(!c));
15
17
  };
16
18
  return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
17
19
  }, [
18
- h,
19
- s,
20
- c
20
+ g,
21
+ c,
22
+ l
21
23
  ]);
22
- let y = o.useFilter({
24
+ let w = s.useFilter({
23
25
  sensitivity: "base",
24
- locale: g
25
- }), b = n((e, t) => y.contains(e.label, t) || (e.keywords ?? []).some((e) => y.contains(e, t)), [y]);
26
- return /* @__PURE__ */ i(t, {
27
- open: s,
28
- onClose: () => c(!1),
29
- title: v("title", u),
30
- ...m ? { closeLabel: m } : {},
31
- children: /* @__PURE__ */ i(o.Root, {
26
+ locale: _
27
+ }), T = n((e, t) => (u.find((t) => t.items.includes(e))?.filter ?? v) === "none" ? !0 : w.contains(e.label, t) || (e.keywords ?? []).some((e) => w.contains(e, t)), [
28
+ w,
29
+ u,
30
+ v
31
+ ]);
32
+ return /* @__PURE__ */ a(t, {
33
+ open: c,
34
+ onClose: () => l(!1),
35
+ title: S("title", d),
36
+ ...h ? { closeLabel: h } : {},
37
+ children: /* @__PURE__ */ a(s.Root, {
32
38
  inline: !0,
33
39
  open: !0,
34
- items: l,
35
- filter: b,
40
+ items: u,
41
+ filter: T,
36
42
  autoHighlight: "always",
37
- children: /* @__PURE__ */ a("div", {
38
- className: ["command-palette", _].filter(Boolean).join(" "),
43
+ ...y === void 0 ? {} : { value: y },
44
+ onValueChange: (e) => b?.(e),
45
+ children: /* @__PURE__ */ o("div", {
46
+ className: ["command-palette", x].filter(Boolean).join(" "),
39
47
  children: [
40
- /* @__PURE__ */ i(o.Input, {
48
+ /* @__PURE__ */ a(s.Input, {
41
49
  className: "command-palette__input",
42
- placeholder: v("placeholder", d),
50
+ placeholder: S("placeholder", f),
43
51
  autoFocus: !0
44
52
  }),
45
- /* @__PURE__ */ i(o.List, {
53
+ /* @__PURE__ */ a(s.List, {
46
54
  className: "command-palette__list",
47
- "aria-label": v("list", p),
48
- children: (e) => /* @__PURE__ */ a(o.Group, {
55
+ "aria-label": S("list", m),
56
+ children: (e) => /* @__PURE__ */ o(s.Group, {
49
57
  items: e.items,
50
58
  className: "command-palette__group",
51
- children: [/* @__PURE__ */ i(o.GroupLabel, {
59
+ children: [/* @__PURE__ */ a(s.GroupLabel, {
52
60
  className: "command-palette__heading",
53
61
  children: e.heading
54
- }), /* @__PURE__ */ i(o.Collection, { children: (e) => /* @__PURE__ */ a(o.Item, {
62
+ }), /* @__PURE__ */ a(s.Collection, { children: (e) => /* @__PURE__ */ o(s.Item, {
55
63
  value: e,
56
64
  disabled: e.disabled,
57
65
  className: "command-palette__item",
58
66
  onClick: () => {
59
- c(!1), e.onSelect();
67
+ l(!1), e.onSelect();
60
68
  },
61
- children: [e.icon && /* @__PURE__ */ i("span", {
69
+ children: [e.icon && /* @__PURE__ */ a("span", {
62
70
  className: "command-palette__item-icon",
63
71
  "aria-hidden": "true",
64
72
  children: e.icon
@@ -66,9 +74,9 @@ function s({ open: s, onOpenChange: c, groups: l, title: u, placeholder: d, empt
66
74
  }, e.id) })]
67
75
  }, e.id)
68
76
  }),
69
- /* @__PURE__ */ i(o.Empty, {
77
+ /* @__PURE__ */ a(s.Empty, {
70
78
  className: "command-palette__empty",
71
- children: v("empty", f)
79
+ children: S("empty", p)
72
80
  })
73
81
  ]
74
82
  })
@@ -76,4 +84,4 @@ function s({ open: s, onOpenChange: c, groups: l, title: u, placeholder: d, empt
76
84
  });
77
85
  }
78
86
  //#endregion
79
- export { s as CommandPalette };
87
+ export { c as CommandPalette };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiolxd/brand",
3
- "version": "49.1.0",
3
+ "version": "49.2.0",
4
4
  "description": "El sistema de diseño de Studio LXD: componentes React, tokens y documentación de la suite SLXD.",
5
5
  "repository": {
6
6
  "type": "git",