beez-ui 0.8.1 → 0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +11 -9
  3. package/dist/components/animated-collapse.js +9 -3
  4. package/dist/components/animated-list-item.js +37 -34
  5. package/dist/components/filter-presets-bar.d.ts +71 -0
  6. package/dist/components/filter-presets-bar.js +508 -0
  7. package/dist/components/presence-swap.js +38 -51
  8. package/dist/components/reaction-button.d.ts +7 -2
  9. package/dist/components/reaction-button.js +59 -48
  10. package/dist/hooks.d.ts +10 -0
  11. package/dist/hooks.js +10 -0
  12. package/dist/index.d.ts +1 -23
  13. package/dist/index.js +1 -23
  14. package/dist/styles.css +1 -1
  15. package/package.json +11 -126
  16. package/dist/components/bouncing-dots-loader.d.ts +0 -19
  17. package/dist/components/bouncing-dots-loader.js +0 -47
  18. package/dist/components/confirm-delete-button.d.ts +0 -29
  19. package/dist/components/confirm-delete-button.js +0 -254
  20. package/dist/components/empty-state.d.ts +0 -24
  21. package/dist/components/empty-state.js +0 -37
  22. package/dist/components/error-state.d.ts +0 -20
  23. package/dist/components/error-state.js +0 -145
  24. package/dist/components/external-browser-handoff.d.ts +0 -27
  25. package/dist/components/external-browser-handoff.js +0 -306
  26. package/dist/components/info-popover.d.ts +0 -23
  27. package/dist/components/info-popover.js +0 -158
  28. package/dist/components/month-calendar-header.d.ts +0 -65
  29. package/dist/components/month-calendar-header.js +0 -337
  30. package/dist/components/month-grid.d.ts +0 -59
  31. package/dist/components/month-grid.js +0 -301
  32. package/dist/components/notification-bell.d.ts +0 -50
  33. package/dist/components/notification-bell.js +0 -338
  34. package/dist/components/notification-panel.d.ts +0 -56
  35. package/dist/components/notification-panel.js +0 -281
  36. package/dist/components/open-in-browser-cta.d.ts +0 -22
  37. package/dist/components/open-in-browser-cta.js +0 -61
  38. package/dist/components/pwa-update-control.d.ts +0 -18
  39. package/dist/components/pwa-update-control.js +0 -119
  40. package/dist/emoji-picker.d.ts +0 -27
  41. package/dist/emoji-picker.js +0 -283
  42. package/dist/hooks/use-minute-clock.d.ts +0 -23
  43. package/dist/hooks/use-minute-clock.js +0 -147
  44. package/dist/hooks/use-month-transition-direction.d.ts +0 -21
  45. package/dist/hooks/use-month-transition-direction.js +0 -110
  46. package/dist/lib/browser-navigation.d.ts +0 -21
  47. package/dist/lib/browser-navigation.js +0 -42
  48. package/dist/lib/in-app-browser.d.ts +0 -35
  49. package/dist/lib/in-app-browser.js +0 -80
  50. package/dist/lib/month-grid.d.ts +0 -42
  51. package/dist/lib/month-grid.js +0 -74
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ Todos los cambios relevantes de beez-ui se documentan en este archivo con el for
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.0] - 2026-09-27
8
+
9
+ ### Added
10
+
11
+ - `ReactionButton` acepta `activeColor` para elegir el color del ícono activo (por defecto, el color destructivo).
12
+ - Entrypoint `beez-ui/hooks` con los hooks de uso general: `useIsMobile`, `useIsHydrated`, `useViewerTimeZone`, `usePrefersReducedMotion`, `useHorizontalSwipe` y `resolveHorizontalSwipe`.
13
+ - `FilterPresetsBar` y `FilterPresetSaveButton` para guardar, aplicar, editar y borrar consultas con nombre de `FilterQueryBar`, con `parseFilterPresets` para validar las que la app restaura de su almacenamiento.
14
+
15
+ ### Changed
16
+
17
+ - `useIsHydrated`, `useViewerTimeZone`, `usePrefersReducedMotion`, `useHorizontalSwipe`, `resolveHorizontalSwipe` y `HORIZONTAL_SWIPE_DIRECTION` se importan desde `beez-ui/hooks` en lugar de la raíz; `useIsMobile` sigue también en la raíz.
18
+
19
+ ### Removed
20
+
21
+ - Se retiran `EmptyState`, `ErrorState`, `BouncingDotsLoader` e `InfoPopover`: son envoltorios simples que cada app mantiene con su propio diseño.
22
+ - `getNameInitials` deja de ser público.
23
+ - Se retiran componentes atados a un solo producto que llegaron en 0.8.0: `ExternalBrowserHandoff`, `OpenInBrowserCta`, `detectInAppBrowser`, `buildExternalBrowserUrl`, `NotificationBell`, `NotificationPanel`, `MonthGrid`, `MonthCalendarHeader`, `PwaUpdateControl` y `ConfirmDeleteButton`. Quien los use debe mantener su propia versión.
24
+ - Se retiran los helpers `createMonthGridDays`, `splitCalendarWeeks`, `groupItemsByDateKey`, `shiftMonthKey`, `useMonthTransitionDirection`, `useMinuteClock`, `advanceMinuteClockTo`, `navigateToUrl`, `reloadCurrentPage`, `replaceCurrentUrlSearchParams` y `replaceCurrentUrlSearchParamValues`.
25
+ - Se retira el entrypoint opcional `beez-ui/emoji-picker` y la dependencia opcional `emoji-picker-react`.
26
+
27
+ ## [0.8.2] - 2026-09-27
28
+
29
+ ### Fixed
30
+
31
+ - Con movimiento reducido, `PresenceSwap`, `AnimatedListItem` y `AnimatedCollapse` conservan un fundido breve en lugar de cambiar sin transición; `PresenceSwap` ya no queda trabado en el estado anterior cuando su contenido cambia varias veces seguidas (por ejemplo, error → cargando → cargado).
32
+
7
33
  ## [0.8.1] - 2026-09-27
8
34
 
9
35
  ### Changed
package/README.md CHANGED
@@ -173,19 +173,21 @@ Los componentes compartidos conservan el tema de LaTribu. Se incorporaron atribu
173
173
 
174
174
  ## Componentes de producto reutilizables
175
175
 
176
- Provienen de las carpetas de producto de LaTribu y agenda-mensual y quedaron desacoplados de su dominio: reciben datos ya resueltos y textos por props, con valores por defecto en español.
176
+ Provienen de las carpetas de producto de LaTribu y agenda-mensual y quedaron desacoplados de su dominio: reciben datos ya resueltos y textos por props, con valores por defecto en español. Solo se incorporan piezas genéricas que no fijan decisiones de un producto y que no son una composición trivial de primitivas existentes; los flujos propios de una app (notificaciones, calendarios de eventos, handoff de navegadores internos, actualización de PWA) y los envoltorios simples (estados vacío y de error, loaders, popovers de ayuda) se mantienen en esa app.
177
177
 
178
178
  - Movimiento: `PresenceSwap`, `AnimatedCount`, `AnimatedCollapse` y `AnimatedListItem`. Los tokens que usa la biblioteca se exportan desde la raíz y desde `beez-ui/motion-tokens`, así las apps animan sus superficies con las mismas curvas y springs en lugar de copiarlos.
179
- - Feedback: `BouncingDotsLoader`, `ProgressRing`, `EmptyState`, `ErrorState`, `InfoPopover`, `ConfirmDeleteButton` y `ReactionButton`. El loader y la entrada del título de `MonthCalendarHeader` usan keyframes CSS porque se renderizan en el servidor; el fallback de movimiento reducido los detiene.
180
- - Cuenta y sesión: `AccountMenu` (inicio de sesión mediante `signInHref` o `onSignIn`), `OpenInBrowserCta`, `ExternalBrowserHandoff` y los helpers `detectInAppBrowser` y `buildExternalBrowserUrl`.
179
+ - Indicadores: `ProgressRing` y `ReactionButton`. El color del ícono activo de la reacción se configura con `activeColor`.
180
+ - Cuenta: `AccountMenu`, con inicio de sesión mediante `signInHref` o `onSignIn`.
181
181
  - Contenido: `RichTextContent`, `RichMarkdownContent`, `RichLinkEditor` y `useRichLinkEditor`. Solo interpretan links en markdown, URLs sueltas, listas y negrita; el HTML del contenido nunca se renderiza como markup.
182
- - Notificaciones: `NotificationBell` y `NotificationPanel` reciben `NotificationPanelItem` (`id`, `title`, `detail`, `sentAtLabel`, `href`, `isUnread`); la app traduce sus notificaciones a ese formato. Con `surface="responsive"` se renderizan el popover y el sheet hasta hidratar, y después solo el que corresponde al viewport.
183
- - Calendario: `MonthGrid` y `MonthCalendarHeader` trabajan con claves `YYYY-MM` y `YYYY-MM-DD` sin zona horaria; la app decide en qué zona convierte cada instante en día. La navegación acepta `href` (link del router) o `onSelect`.
182
+ - Filtros guardados: `FilterPresetsBar` y `FilterPresetSaveButton` guardan, aplican, editan y borran consultas con nombre de `FilterQueryBar`. La app decide dónde persistirlas; `parseFilterPresets` valida lo que vuelve del almacenamiento.
184
183
  - Archivos: `FileUpload`, `FileUploadDropZone`, `FileUploadList` y `FileUploadItem` validan tipo y tamaño, pero no suben archivos: el progreso y los errores los informa la app.
185
- - PWA: `PwaUpdateControl` aparece solo cuando hay un service worker en espera y le envía `{ type: "SKIP_WAITING" }` (configurable con `skipWaitingMessage`).
186
- - Hooks y utilidades: `useIsHydrated`, `useMinuteClock`, `useViewerTimeZone`, `useHorizontalSwipe`, `usePrefersReducedMotion`, `formatFileSize`, `copyTextToClipboard`, `getNameInitials`, búsqueda difusa y helpers de URL.
184
+ - Utilidades: `formatFileSize`, `copyTextToClipboard` y búsqueda difusa.
187
185
 
188
- `EmojiPicker` vive en el entrypoint opcional `beez-ui/emoji-picker` para no sumar `emoji-picker-react` a quien no lo usa. Hay que instalarlo como dependencia de la app (`pnpm add emoji-picker-react`); la librería se carga recién al abrir el selector, nunca durante el render del servidor.
186
+ Los hooks de uso general se importan desde `beez-ui/hooks`: `useIsMobile`, `useIsHydrated`, `useViewerTimeZone`, `usePrefersReducedMotion` y `useHorizontalSwipe` (con `resolveHorizontalSwipe`). `useIsMobile` también sigue disponible desde la raíz.
187
+
188
+ ```tsx
189
+ import { useIsHydrated } from "beez-ui/hooks";
190
+ ```
189
191
 
190
192
  ## React Compiler
191
193
 
@@ -222,7 +224,7 @@ pnpm release:prepare
222
224
 
223
225
  `pnpm build` genera las declaraciones con TypeScript 7, JavaScript con Oxc/React Compiler y CSS con el CLI de Tailwind. `pnpm check` ejecuta ESLint 10, los typechecks separados de código y tests con TypeScript 7, y Vitest 5. `tests/tsconfig.json` incorpora los matchers de Testing Library y los tipos de Vite sin incluirlos en el código de producción. `test:browser` verifica una app React/Vite nativa y una app Next real en Chromium y WebKit, tanto en desktop como en móvil. Los tests unitarios importan los archivos compilados. Los consumidores de navegador no instalan plugins de Tailwind, y una prueba adicional sirve el tarball por HTTP sin procesadores CSS para verificar estilos computados y carga de fuentes. Una prueba adicional instala el tarball en un consumidor aislado sin Next y verifica render, filtrado y declaraciones públicas.
224
226
 
225
- Los tests resuelven `beez-ui`, `beez-ui/next`, `beez-ui/tanstack` y `beez-ui/emoji-picker` mediante rutas explícitas a las declaraciones compiladas en `tests/tsconfig.json`. Ejecutar `pnpm build` después de clonar o si falta `dist`; los comandos de validación completos ya lo hacen. Si el editor conserva diagnósticos anteriores después del build, reiniciar su servidor de TypeScript.
227
+ Los tests resuelven `beez-ui`, `beez-ui/next`, `beez-ui/tanstack` y `beez-ui/hooks` mediante rutas explícitas a las declaraciones compiladas en `tests/tsconfig.json`. Ejecutar `pnpm build` después de clonar o si falta `dist`; los comandos de validación completos ya lo hacen. Si el editor conserva diagnósticos anteriores después del build, reiniciar su servidor de TypeScript.
226
228
 
227
229
  El compilador `tsc` es TypeScript 7. Para `typescript-eslint`, se mantiene la [API de compatibilidad oficial de TypeScript 6](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) mediante un alias; no reemplaza el compilador de los typechecks.
228
230
 
@@ -17,8 +17,14 @@ const COLLAPSE_TRANSITION = {
17
17
  ease: MOTION_EASE
18
18
  }
19
19
  };
20
- /** Reduced motion keeps the open/closed contract but applies it instantly. */
21
- const INSTANT_TRANSITION = { duration: 0 };
20
+ /** Reduced motion applies the height at once but keeps the short fade, so exits always settle. */
21
+ const REDUCED_MOTION_COLLAPSE_TRANSITION = {
22
+ height: { duration: 0 },
23
+ opacity: {
24
+ duration: MOTION_TIMING.panel,
25
+ ease: MOTION_EASE
26
+ }
27
+ };
22
28
  /**
23
29
  * Renders `children` only while `isOpen`, animating height and opacity.
24
30
  * Keep padding inside `children`: the animated wrapper collapses to zero height.
@@ -50,7 +56,7 @@ export function AnimatedCollapse(t0) {
50
56
  height: 0,
51
57
  opacity: 0
52
58
  },
53
- transition: shouldReduceMotion ? INSTANT_TRANSITION : COLLAPSE_TRANSITION,
59
+ transition: shouldReduceMotion ? REDUCED_MOTION_COLLAPSE_TRANSITION : COLLAPSE_TRANSITION,
54
60
  onAnimationStart: () => setIsSettled(false),
55
61
  onAnimationComplete: () => setIsSettled(true),
56
62
  children
@@ -19,8 +19,6 @@ const LIST_ITEM_EXIT_TRANSITION = {
19
19
  duration: MOTION_TIMING.exit,
20
20
  ease: MOTION_EASE
21
21
  };
22
- /** Reduced motion keeps enter/exit semantics (inert while leaving) but without travel or delay. */
23
- const INSTANT_TRANSITION = { duration: 0 };
24
22
  /**
25
23
  * Wrap the list in `<AnimatePresence initial={false}>` so only items added after the
26
24
  * first render animate in, and removed items animate out before leaving the DOM.
@@ -30,7 +28,7 @@ const INSTANT_TRANSITION = { duration: 0 };
30
28
  * @returns The animated item.
31
29
  */
32
30
  export function AnimatedListItem(t0) {
33
- const $ = _c(22);
31
+ const $ = _c(24);
34
32
  let children;
35
33
  let className;
36
34
  let elementProps;
@@ -56,6 +54,8 @@ export function AnimatedListItem(t0) {
56
54
  const MotionElement = ANIMATED_LIST_ITEM_ELEMENTS[as];
57
55
  const isPresent = useIsPresent();
58
56
  const shouldReduceMotion = usePrefersReducedMotion();
57
+ const distance = shouldReduceMotion ? 0 : MOTION_LIST_ITEM_DISTANCE;
58
+ const enterScale = shouldReduceMotion ? 1 : MOTION_ITEM_ENTER_SCALE;
59
59
  const t3 = isPresent ? elementProps["aria-hidden"] : true;
60
60
  const t4 = !isPresent || undefined;
61
61
  let t5;
@@ -68,41 +68,44 @@ export function AnimatedListItem(t0) {
68
68
  }
69
69
  const t6 = layout && !shouldReduceMotion ? "position" : false;
70
70
  let t7;
71
- let t8;
72
- if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
71
+ if ($[8] !== distance || $[9] !== enterScale) {
73
72
  t7 = {
74
73
  opacity: 0,
75
- y: MOTION_LIST_ITEM_DISTANCE,
76
- scale: MOTION_ITEM_ENTER_SCALE
74
+ y: distance,
75
+ scale: enterScale
77
76
  };
77
+ $[8] = distance;
78
+ $[9] = enterScale;
79
+ $[10] = t7;
80
+ } else {
81
+ t7 = $[10];
82
+ }
83
+ let t8;
84
+ if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
78
85
  t8 = {
79
86
  opacity: 1,
80
87
  y: 0,
81
88
  scale: 1
82
89
  };
83
- $[8] = t7;
84
- $[9] = t8;
90
+ $[11] = t8;
85
91
  } else {
86
- t7 = $[8];
87
- t8 = $[9];
92
+ t8 = $[11];
88
93
  }
89
- const t9 = shouldReduceMotion ? INSTANT_TRANSITION : LIST_ITEM_EXIT_TRANSITION;
90
- let t10;
91
- if ($[10] !== t9) {
92
- t10 = {
94
+ let t9;
95
+ if ($[12] !== enterScale) {
96
+ t9 = {
93
97
  opacity: 0,
94
- scale: MOTION_ITEM_ENTER_SCALE,
95
- transition: t9
98
+ scale: enterScale,
99
+ transition: LIST_ITEM_EXIT_TRANSITION
96
100
  };
97
- $[10] = t9;
98
- $[11] = t10;
101
+ $[12] = enterScale;
102
+ $[13] = t9;
99
103
  } else {
100
- t10 = $[11];
104
+ t9 = $[13];
101
105
  }
102
- const t11 = shouldReduceMotion ? INSTANT_TRANSITION : LIST_ITEM_TRANSITION;
103
- let t12;
104
- if ($[12] !== MotionElement || $[13] !== children || $[14] !== elementProps || $[15] !== t10 || $[16] !== t11 || $[17] !== t3 || $[18] !== t4 || $[19] !== t5 || $[20] !== t6) {
105
- t12 = /* @__PURE__ */ _jsx(MotionElement, {
106
+ let t10;
107
+ if ($[14] !== MotionElement || $[15] !== children || $[16] !== elementProps || $[17] !== t3 || $[18] !== t4 || $[19] !== t5 || $[20] !== t6 || $[21] !== t7 || $[22] !== t9) {
108
+ t10 = /* @__PURE__ */ _jsx(MotionElement, {
106
109
  "data-slot": "animated-list-item",
107
110
  ...elementProps,
108
111
  "aria-hidden": t3,
@@ -111,22 +114,22 @@ export function AnimatedListItem(t0) {
111
114
  layout: t6,
112
115
  initial: t7,
113
116
  animate: t8,
114
- exit: t10,
115
- transition: t11,
117
+ exit: t9,
118
+ transition: LIST_ITEM_TRANSITION,
116
119
  children
117
120
  });
118
- $[12] = MotionElement;
119
- $[13] = children;
120
- $[14] = elementProps;
121
- $[15] = t10;
122
- $[16] = t11;
121
+ $[14] = MotionElement;
122
+ $[15] = children;
123
+ $[16] = elementProps;
123
124
  $[17] = t3;
124
125
  $[18] = t4;
125
126
  $[19] = t5;
126
127
  $[20] = t6;
127
- $[21] = t12;
128
+ $[21] = t7;
129
+ $[22] = t9;
130
+ $[23] = t10;
128
131
  } else {
129
- t12 = $[21];
132
+ t10 = $[23];
130
133
  }
131
- return t12;
134
+ return t10;
132
135
  }
@@ -0,0 +1,71 @@
1
+ import type { FilterQualifierConfig } from "./filter-query-grammar.js";
2
+ /** A named, reusable query of the filter bar. */
3
+ export interface FilterPreset {
4
+ name: string;
5
+ query: string;
6
+ }
7
+ /**
8
+ * Validates presets restored from untrusted storage, trimming names and queries and dropping
9
+ * malformed or empty entries.
10
+ * @param value - Parsed stored value, such as the result of `JSON.parse`.
11
+ * @returns The valid presets, or an empty list when the value is not a list.
12
+ */
13
+ export declare function parseFilterPresets(value: unknown): FilterPreset[];
14
+ export interface FilterPresetLabels {
15
+ saveAction: string;
16
+ saveConfirm: string;
17
+ saveConfirmAriaLabel: string;
18
+ presetName: string;
19
+ presetNamePlaceholder: string;
20
+ presetQuery: string;
21
+ presetQueryPlaceholder: string;
22
+ savedPresets: string;
23
+ editConfirmAriaLabel: string;
24
+ cancel: string;
25
+ delete: string;
26
+ emptyNameError: string;
27
+ emptyCurrentQueryError: string;
28
+ emptyPresetQueryError: string;
29
+ duplicateNameError: string;
30
+ applyPresetAriaLabel: (presetName: string) => string;
31
+ editPresetAriaLabel: (presetName: string) => string;
32
+ deletePresetAriaLabel: (presetName: string) => string;
33
+ deleteConfirmation: (presetName: string) => string;
34
+ }
35
+ export declare const FILTER_PRESET_DEFAULT_LABELS: FilterPresetLabels;
36
+ export interface FilterPresetSaveButtonProps {
37
+ /** Whether the filter bar currently has a query worth saving. */
38
+ canSaveCurrentQuery: boolean;
39
+ /** Saves the current query under the given name. Returns `false` when there is no query to save. */
40
+ onSaveCurrentQuery: (presetName: string) => boolean;
41
+ labels?: Partial<FilterPresetLabels>;
42
+ }
43
+ /**
44
+ * Compact icon button that saves the current query as a named preset. Fits the action slot of
45
+ * the filter bar input.
46
+ * @param props - Save availability, save callback and optional labels.
47
+ * @returns The button and its naming popover.
48
+ */
49
+ export declare function FilterPresetSaveButton({ canSaveCurrentQuery, onSaveCurrentQuery, labels }: FilterPresetSaveButtonProps): import("react").JSX.Element;
50
+ export interface FilterPresetsBarProps {
51
+ presets: readonly FilterPreset[];
52
+ /** Qualifiers of the filter bar, offered as suggestions while editing a preset query. */
53
+ queryFilterConfigs: FilterQualifierConfig[];
54
+ onApplyPreset: (preset: FilterPreset) => void;
55
+ onDeletePreset: (presetName: string) => void;
56
+ /** Replaces the preset named `originalName` with the given name and query. */
57
+ onUpdatePreset: (update: {
58
+ name: string;
59
+ originalName: string;
60
+ query: string;
61
+ }) => void;
62
+ labels?: Partial<FilterPresetLabels>;
63
+ className?: string;
64
+ }
65
+ /**
66
+ * Renders one chip per saved preset: its name applies it, the pencil edits its name and query,
67
+ * and the cross deletes it after a confirmation. Renders nothing without presets.
68
+ * @param props - Presets, filter configs, callbacks and optional labels.
69
+ * @returns The presets row, or nothing.
70
+ */
71
+ export declare function FilterPresetsBar({ presets, queryFilterConfigs, onApplyPreset, onDeletePreset, onUpdatePreset, labels, className, }: FilterPresetsBarProps): import("react").JSX.Element | null;