@beweco/aurora-ui 1.0.0-beta.138 → 1.0.0-beta.140
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/assets/css/styles.css +23 -1
- package/dist/index.cjs.js +2757 -481
- package/dist/index.esm.js +2789 -498
- package/dist/types/__smoke__/v2-api-surface.d.ts +5 -2
- package/dist/types/__smoke__/v2-api-surface.d.ts.map +1 -1
- package/dist/types/compat/heroui-v2-shims.d.ts +212 -34
- package/dist/types/compat/heroui-v2-shims.d.ts.map +1 -1
- package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
- package/dist/types/components/autocomplete/AutoComplete.types.d.ts +36 -2
- package/dist/types/components/autocomplete/AutoComplete.types.d.ts.map +1 -1
- package/dist/types/components/autocomplete/index.d.ts +1 -1
- package/dist/types/components/autocomplete/index.d.ts.map +1 -1
- package/dist/types/components/button/Button.d.ts +3 -0
- package/dist/types/components/button/Button.d.ts.map +1 -1
- package/dist/types/components/button/Button.types.d.ts +24 -1
- package/dist/types/components/button/Button.types.d.ts.map +1 -1
- package/dist/types/components/button/_internal/press-event.d.ts +16 -0
- package/dist/types/components/button/_internal/press-event.d.ts.map +1 -0
- package/dist/types/components/button/index.d.ts +1 -1
- package/dist/types/components/button/index.d.ts.map +1 -1
- package/dist/types/components/card/Card.d.ts +13 -1
- package/dist/types/components/card/Card.d.ts.map +1 -1
- package/dist/types/components/card/Card.types.d.ts +53 -1
- package/dist/types/components/card/Card.types.d.ts.map +1 -1
- package/dist/types/components/card/index.d.ts +1 -1
- package/dist/types/components/card/index.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.d.ts +1 -1
- package/dist/types/components/chip/Chip.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.types.d.ts +28 -6
- package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.types.d.ts +89 -1
- package/dist/types/components/datepicker/DatePicker.types.d.ts.map +1 -1
- package/dist/types/components/datepicker/calendar-slots.d.ts +17 -0
- package/dist/types/components/datepicker/calendar-slots.d.ts.map +1 -0
- package/dist/types/components/datepicker/index.d.ts +1 -1
- package/dist/types/components/datepicker/index.d.ts.map +1 -1
- package/dist/types/components/daterangepicker/DateRangePicker.d.ts +1 -1
- package/dist/types/components/daterangepicker/DateRangePicker.d.ts.map +1 -1
- package/dist/types/components/daterangepicker/DateRangePicker.types.d.ts +43 -1
- package/dist/types/components/daterangepicker/DateRangePicker.types.d.ts.map +1 -1
- package/dist/types/components/daterangepicker/index.d.ts +1 -1
- package/dist/types/components/daterangepicker/index.d.ts.map +1 -1
- package/dist/types/components/drawer/Drawer.d.ts +58 -0
- package/dist/types/components/drawer/Drawer.d.ts.map +1 -0
- package/dist/types/components/drawer/Drawer.types.d.ts +75 -0
- package/dist/types/components/drawer/Drawer.types.d.ts.map +1 -0
- package/dist/types/components/drawer/DrawerContext.d.ts +15 -0
- package/dist/types/components/drawer/DrawerContext.d.ts.map +1 -0
- package/dist/types/components/drawer/_internal/DrawerBody.d.ts +7 -0
- package/dist/types/components/drawer/_internal/DrawerBody.d.ts.map +1 -0
- package/dist/types/components/drawer/_internal/DrawerContent.d.ts +14 -0
- package/dist/types/components/drawer/_internal/DrawerContent.d.ts.map +1 -0
- package/dist/types/components/drawer/_internal/DrawerFooter.d.ts +13 -0
- package/dist/types/components/drawer/_internal/DrawerFooter.d.ts.map +1 -0
- package/dist/types/components/drawer/_internal/DrawerHeader.d.ts +20 -0
- package/dist/types/components/drawer/_internal/DrawerHeader.d.ts.map +1 -0
- package/dist/types/components/drawer/_internal/index.d.ts +5 -0
- package/dist/types/components/drawer/_internal/index.d.ts.map +1 -0
- package/dist/types/components/drawer/index.d.ts +4 -0
- package/dist/types/components/drawer/index.d.ts.map +1 -0
- package/dist/types/components/dropdown/Dropdown.d.ts +4 -2
- package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
- package/dist/types/components/dropdown/Dropdown.types.d.ts +61 -5
- package/dist/types/components/dropdown/Dropdown.types.d.ts.map +1 -1
- package/dist/types/components/dropdown/_internal/renderTrigger.d.ts +47 -0
- package/dist/types/components/dropdown/_internal/renderTrigger.d.ts.map +1 -0
- package/dist/types/components/dropdown/index.d.ts +1 -1
- package/dist/types/components/dropdown/index.d.ts.map +1 -1
- package/dist/types/components/input/Input.d.ts +4 -0
- package/dist/types/components/input/Input.d.ts.map +1 -1
- package/dist/types/components/input/Input.types.d.ts +37 -1
- package/dist/types/components/input/Input.types.d.ts.map +1 -1
- package/dist/types/components/modal/Modal.d.ts +4 -3
- package/dist/types/components/modal/Modal.d.ts.map +1 -1
- package/dist/types/components/modal/Modal.types.d.ts +59 -3
- package/dist/types/components/modal/Modal.types.d.ts.map +1 -1
- package/dist/types/components/modal/ModalFacadeContext.d.ts +27 -0
- package/dist/types/components/modal/ModalFacadeContext.d.ts.map +1 -0
- package/dist/types/components/modal/_internal/ModalBody.d.ts +1 -1
- package/dist/types/components/modal/_internal/ModalBody.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalContent.d.ts +10 -5
- package/dist/types/components/modal/_internal/ModalContent.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalFooter.d.ts +1 -1
- package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalHeader.d.ts +4 -2
- package/dist/types/components/modal/_internal/ModalHeader.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.d.ts +10 -0
- package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.types.d.ts +21 -0
- package/dist/types/components/pagination/Pagination.types.d.ts.map +1 -1
- package/dist/types/components/select/Select.d.ts.map +1 -1
- package/dist/types/components/select/Select.types.d.ts +64 -0
- package/dist/types/components/select/Select.types.d.ts.map +1 -1
- package/dist/types/components/select/_internal/v2-collection.d.ts +64 -0
- package/dist/types/components/select/_internal/v2-collection.d.ts.map +1 -0
- package/dist/types/components/select/index.d.ts +1 -1
- package/dist/types/components/select/index.d.ts.map +1 -1
- package/dist/types/components/table/AuraTable.types.d.ts +5 -3
- package/dist/types/components/table/AuraTable.types.d.ts.map +1 -1
- package/dist/types/components/table/Table.d.ts +15 -5
- package/dist/types/components/table/Table.d.ts.map +1 -1
- package/dist/types/components/table/TableContent.d.ts +32 -0
- package/dist/types/components/table/TableContent.d.ts.map +1 -0
- package/dist/types/components/table/_internal/v2-compat.d.ts +94 -0
- package/dist/types/components/table/_internal/v2-compat.d.ts.map +1 -0
- package/dist/types/components/tabs/Tabs.d.ts +5 -4
- package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
- package/dist/types/components/tabs/Tabs.types.d.ts +90 -13
- package/dist/types/components/tabs/Tabs.types.d.ts.map +1 -1
- package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
- package/dist/types/components/textarea/Textarea.types.d.ts +13 -0
- package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
- package/dist/types/components/textarea/useTextareaAutosize.d.ts +13 -0
- package/dist/types/components/textarea/useTextareaAutosize.d.ts.map +1 -0
- package/dist/types/components/time-input/TimeInput.d.ts +12 -8
- package/dist/types/components/time-input/TimeInput.d.ts.map +1 -1
- package/dist/types/components/time-input/TimeInput.types.d.ts +29 -3
- package/dist/types/components/time-input/TimeInput.types.d.ts.map +1 -1
- package/dist/types/components/time-input/index.d.ts +2 -2
- package/dist/types/components/time-input/index.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.types.d.ts +31 -1
- package/dist/types/components/tooltip/Tooltip.types.d.ts.map +1 -1
- package/dist/types/components/tooltip/_internal/renderTrigger.d.ts +3 -0
- package/dist/types/components/tooltip/_internal/renderTrigger.d.ts.map +1 -0
- package/dist/types/components/tooltip/index.d.ts +1 -1
- package/dist/types/components/tooltip/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -7
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -19,47 +19,159 @@ const CardBody = react.Card.Content;
|
|
|
19
19
|
// Breadcrumbs: el `Breadcrumbs` raíz sobrevive igual; su hijo suelto se renombra Y cambia grafía
|
|
20
20
|
// (`BreadcrumbItem` v2 → `BreadcrumbsItem`/`Breadcrumbs.Item` v3, con "s").
|
|
21
21
|
const BreadcrumbItem = react.Breadcrumbs.Item;
|
|
22
|
-
// Autocomplete / Select: los ítems v2 (`AutocompleteItem`, `SelectItem`) y sus secciones
|
|
23
|
-
// (`SelectSection`) no sobreviven planos en v3 — se resuelven con `ListBox.Item`/
|
|
24
|
-
// `ListBox.Section`, la misma pieza que usan tanto el `ComboBox` (ver `AuraAutocomplete`) como el
|
|
25
|
-
// `Select` compound.
|
|
26
|
-
//
|
|
27
|
-
// ⚠️ Limitación conocida (ver decisiones-aceptadas-f2.md, D-F2-2): v2 identificaba cada item por
|
|
28
|
-
// el `key` de React; v3 usa `id` explícito. El código v2 existente (que solo pasa `key`/
|
|
29
|
-
// `textValue`) sigue compilando y seleccionando bien porque react-aria-components cae al `key` de
|
|
30
|
-
// React cuando no hay `id` en colecciones estáticas (verificado en Storybook) — pero si algún
|
|
31
|
-
// consumidor lee la selección por otra vía, debe migrar sus items a `id` explícito.
|
|
32
22
|
const AutocompleteItem = react.ListBoxItem;
|
|
33
23
|
const SelectItem = react.ListBoxItem;
|
|
34
24
|
const SelectSection = react.ListBoxSection;
|
|
35
25
|
// ---------------------------------------------------------------------------
|
|
36
26
|
// Renombrados simples (el nombre v2 desaparece del todo, 1:1 con un nombre v3)
|
|
37
27
|
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
const Progress = react.ProgressBar;
|
|
28
|
+
// Progress: ver el bloque «Progress (v2) sobre ProgressBar (v3)» más abajo.
|
|
40
29
|
/** Divider → Separator. El uso INTERNO de la librería ya migró a `Separator` directo
|
|
41
30
|
* (`TagsFilter.tsx`); este shim es solo para las apps, que siguen importando `Divider`. */
|
|
42
31
|
const Divider = react.Separator;
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Eliminados
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
32
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
33
|
+
* El relleno de v3 se pinta con `var(--progress-bar-fill)` (`progress-bar.css`), que cada variante
|
|
34
|
+
* de color fija en la capa `components`. v3 tiene 5 colores y v2 seis, y dos NO coinciden:
|
|
35
|
+
*
|
|
36
|
+
* | v2 | literal v2 (`indicator`) | v3 con el mismo nombre | aquí |
|
|
37
|
+
* |---|---|---|---|
|
|
38
|
+
* | `primary` (default) | `bg-primary` | — (no existe) | `accent`, que en este repo ES `--color-primary` (`theme-packs.generated.css`: `--accent: var(--color-primary)`) |
|
|
39
|
+
* | `default` | `bg-default-400` (gris medio) | `--default-foreground` (casi negro) | falso amigo: se repone el gris de v2 |
|
|
40
|
+
* | `secondary` | `bg-secondary` | — | el `--color-secondary` del pack |
|
|
41
|
+
*
|
|
42
|
+
* La variable se pisa con una utilidad (capa `utilities` > `components`), así que no depende del
|
|
43
|
+
* orden del CSS. `success`/`warning`/`danger` son el mismo nombre y el mismo token en las dos.
|
|
44
|
+
*/
|
|
45
|
+
const PROGRESS_FILL_OVERRIDE = {
|
|
46
|
+
default: "[--progress-bar-fill:var(--color-default-400)]",
|
|
47
|
+
secondary: "[--progress-bar-fill:var(--color-secondary)]",
|
|
48
|
+
};
|
|
49
|
+
const PROGRESS_V3_COLOR = {
|
|
50
|
+
default: undefined,
|
|
51
|
+
primary: "accent",
|
|
52
|
+
secondary: undefined,
|
|
53
|
+
success: "success",
|
|
54
|
+
warning: "warning",
|
|
55
|
+
danger: "danger",
|
|
56
|
+
accent: "accent",
|
|
57
|
+
};
|
|
58
|
+
/** Los hijos que el consumidor ya trae, aplanando Fragments (`Children.toArray` NO los aplana). */
|
|
59
|
+
function flattenChildren(children) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const child of $8WNJc$react.Children.toArray(children)) {
|
|
62
|
+
if (!$8WNJc$react.isValidElement(child)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (child.type === $8WNJc$react.Fragment) {
|
|
66
|
+
out.push(...flattenChildren(child.props.children));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
out.push(child);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `Progress` de v2 (TECH-2459, hallazgo beweossmbs 23-sep: «es un alias del ProgressBar crudo y
|
|
76
|
+
* SIN HIJOS NO PINTA»).
|
|
77
|
+
*
|
|
78
|
+
* EL DEFECTO. Hasta aquí `Progress` era `ProgressBar` a secas, y el `ProgressBar` de v3 es un
|
|
79
|
+
* compound: la raíz solo publica el estado, la barra la pintan `ProgressBar.Track` +
|
|
80
|
+
* `ProgressBar.Fill`, y la etiqueta y el valor, `Label` + `ProgressBar.Output`. El JSX v2 de las
|
|
81
|
+
* apps (`<Progress value={40} color="primary" showValueLabel />`, 10 usos en beweossmbs) no trae
|
|
82
|
+
* ninguna de esas piezas, así que salía un `<div role="progressbar">` VACÍO: ni pista, ni relleno,
|
|
83
|
+
* ni texto. Además `color="primary"` no es un color de v3 y `showValueLabel`/`label`/`classNames`
|
|
84
|
+
* se descartaban.
|
|
85
|
+
*
|
|
86
|
+
* LO QUE HACE. Acepta la API v2 y monta por dentro la anatomía de v3. Es **idempotente por
|
|
87
|
+
* identidad de `child.type`**: si la app ya pasa `ProgressBar.Track` (o `Progress.Track`),
|
|
88
|
+
* `ProgressBar.Output` o `Label`, esa pieza no se añade otra vez (mismo criterio que la fachada
|
|
89
|
+
* `Tabs` con su indicador). Children como función se respetan tal cual.
|
|
90
|
+
*
|
|
91
|
+
* LO QUE NO COPIA DE v2, por §9.1 (verse como v3): las alturas de pista (v2 `h-1`/`h-3`/`h-5`,
|
|
92
|
+
* v3 `h-1`/`h-2`/`h-3`), el radio completo y el fondo `default-300/50` de la pista, y el tamaño de
|
|
93
|
+
* letra por `size` de la etiqueta. `size` elige el tamaño de v3 del mismo nombre.
|
|
94
|
+
*/
|
|
95
|
+
function ProgressFacade({ label, showValueLabel = false, size = "md", color = "primary", classNames, className, children, ...props }) {
|
|
96
|
+
if (typeof children === "function") {
|
|
97
|
+
return (jsxRuntime.jsx(react.ProgressBar, { ...props, size: size, color: PROGRESS_V3_COLOR[color], className: react.cn(PROGRESS_FILL_OVERRIDE[color], classNames?.base, className), children: children }));
|
|
98
|
+
}
|
|
99
|
+
const own = flattenChildren(children);
|
|
100
|
+
const hasTrack = own.some((c) => c.type === react.ProgressBar.Track || c.type === react.ProgressBar.Fill);
|
|
101
|
+
const hasOutput = own.some((c) => c.type === react.ProgressBar.Output);
|
|
102
|
+
const hasLabel = own.some((c) => c.type === react.Label);
|
|
103
|
+
return (jsxRuntime.jsxs(react.ProgressBar, { ...props, size: size, color: PROGRESS_V3_COLOR[color],
|
|
104
|
+
// v2: `cn(classNames?.base, className)` en la raíz (`use-progress.mjs`). La variable del
|
|
105
|
+
// color va primero para que un `[--progress-bar-fill:…]` del consumidor la sustituya.
|
|
106
|
+
className: react.cn(PROGRESS_FILL_OVERRIDE[color], classNames?.base, className), children: [label != null && label !== false && !hasLabel && (jsxRuntime.jsx(react.Label, { className: classNames?.label, children: label })), showValueLabel && !hasOutput && (jsxRuntime.jsx(react.ProgressBar.Output, { className: classNames?.value })), children, !hasTrack && (jsxRuntime.jsx(react.ProgressBar.Track, { className: classNames?.track, children: jsxRuntime.jsx(react.ProgressBar.Fill, { className: classNames?.indicator }) }))] }));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* `Progress` de v2 con la anatomía de v3 dentro, y con los estáticos del compound colgados
|
|
110
|
+
* (`Progress.Track`, `Progress.Fill`, `Progress.Output`, `Progress.Root`) para que el marcado v3
|
|
111
|
+
* siga compilando: son los MISMOS objetos que `ProgressBar.*`, así que la detección por identidad
|
|
112
|
+
* los reconoce vengan por un nombre o por otro.
|
|
113
|
+
*/
|
|
114
|
+
const Progress = Object.assign(ProgressFacade, {
|
|
115
|
+
Root: react.ProgressBar.Root,
|
|
116
|
+
Output: react.ProgressBar.Output,
|
|
117
|
+
Track: react.ProgressBar.Track,
|
|
118
|
+
Fill: react.ProgressBar.Fill,
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* `HeroUIProvider` ya no existe en v3 (los componentes no necesitan Provider para pintarse). Pero
|
|
122
|
+
* el de v2 hacía dos cosas que react-aria sí necesita y que las apps no pueden montar por su
|
|
123
|
+
* cuenta (todo pasa por aurora-ui): `locale` → `I18nProvider` y `navigate`/`useHref` →
|
|
124
|
+
* `RouterProvider`. Este shim las repone, cada una SOLO cuando se pasa; sin props pasa los
|
|
125
|
+
* `children` tal cual, como el no-op que era. El resto de props v2 se aceptan y se ignoran (ver
|
|
126
|
+
* `HeroUIProviderProps`). Orden de anidado idéntico al de v2: `I18nProvider` por fuera,
|
|
127
|
+
* `RouterProvider` por dentro.
|
|
50
128
|
*/
|
|
51
|
-
function HeroUIProvider({ children }) {
|
|
52
|
-
|
|
129
|
+
function HeroUIProvider({ children, locale, navigate, useHref, }) {
|
|
130
|
+
let contents = children;
|
|
131
|
+
if (navigate) {
|
|
132
|
+
contents = (jsxRuntime.jsx(react.RouterProvider, { navigate: navigate, useHref: useHref, children: contents }));
|
|
133
|
+
}
|
|
134
|
+
if (locale) {
|
|
135
|
+
contents = jsxRuntime.jsx(react.I18nProvider, { locale: locale, children: contents });
|
|
136
|
+
}
|
|
137
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: contents });
|
|
53
138
|
}
|
|
139
|
+
/*
|
|
140
|
+
* Literales del `tv` de v2, escritos enteros para que Tailwind 4 los genere (solo emite lo que
|
|
141
|
+
* aparece en el fuente). v2 decía `rounded-small`/`rounded-large` —clases del plugin `heroui()`,
|
|
142
|
+
* muertas sin él y que `cn` BORRARÍA— con los valores de su tema: small 8px, large 14px (los
|
|
143
|
+
* mismos que usan `RADIUS_CLASS` de `Select`/`Input` para `sm`/`lg`). `md` era `rounded-md` a secas
|
|
144
|
+
* en el `tv` de v2 (el de Tailwind, 6px), no el `medium` del tema: se copia tal cual.
|
|
145
|
+
*/
|
|
146
|
+
const IMAGE_RADIUS_CLASS = {
|
|
147
|
+
none: "rounded-none",
|
|
148
|
+
sm: "rounded-[8px]",
|
|
149
|
+
md: "rounded-md",
|
|
150
|
+
lg: "rounded-[14px]",
|
|
151
|
+
full: "rounded-full",
|
|
152
|
+
};
|
|
54
153
|
/**
|
|
55
154
|
* `Image` se elimina en v3 (usar `<img>` nativo, mismo criterio aplicado en
|
|
56
|
-
* `SocialMediaPreview`/`WhatsAppPreview`). Shim:
|
|
57
|
-
*
|
|
58
|
-
*
|
|
155
|
+
* `SocialMediaPreview`/`WhatsAppPreview`). Shim: un `<img>` nativo con la API de v2 que las apps
|
|
156
|
+
* escriben (TECH-2459, hallazgo beweossmbs 23-sep: 12 `<Image>` en la app; `radius` en 3,
|
|
157
|
+
* `classNames` en 3, `removeWrapper` en 2; 0 usos de `isZoomed`/`isBlurred`/`fallbackSrc`/`shadow`).
|
|
158
|
+
*
|
|
159
|
+
* - `radius`: con el default de v2 (`lg`). Sin él, las 9 imágenes que NO pasan `radius` salían con
|
|
160
|
+
* esquinas rectas en vez de las redondeadas de v2. Va primero en el `cn`: la clase de la app gana.
|
|
161
|
+
* - `className` + `classNames.img`: en v2 las dos iban al `<img>` (`use-image.mjs`:
|
|
162
|
+
* `cn(className, classNames?.img)`), y así aquí.
|
|
163
|
+
* - `classNames.wrapper`/`zoomedWrapper`/`blurredImg` y `removeWrapper` se ACEPTAN y no pintan: este
|
|
164
|
+
* shim no monta envoltorio. En v2 el `<div>` existía para el skeleton, el zoom y el desenfoque
|
|
165
|
+
* (`image.mjs`), y le ponía `max-width: fit-content` en línea — que es exactamente lo que los dos
|
|
166
|
+
* `classNames.wrapper` medidos deshacen («El wrapper de HeroUI Image trae `max-w-fit`», con
|
|
167
|
+
* `w-full !max-w-full`). Sin envoltorio, el `<img>` con su `w-full` ya ocupa el ancho: el efecto
|
|
168
|
+
* buscado sale sin la pieza.
|
|
169
|
+
* - NO reproduce `isZoomed`/`isBlurred`/`fallbackSrc`/`shadow`/skeleton (0 usos medidos).
|
|
59
170
|
*/
|
|
60
|
-
function Image$1(props) {
|
|
171
|
+
function Image$1({ radius = "lg", classNames, removeWrapper: _removeWrapper, className, ...props }) {
|
|
172
|
+
return (
|
|
61
173
|
// biome-ignore lint/a11y/useAltText: passthrough genérico, `alt` lo exige ComponentProps<'img'>
|
|
62
|
-
|
|
174
|
+
jsxRuntime.jsx("img", { ...props, className: react.cn(IMAGE_RADIUS_CLASS[radius], className, classNames?.img) }));
|
|
63
175
|
}
|
|
64
176
|
// ---------------------------------------------------------------------------
|
|
65
177
|
// Hooks
|
|
@@ -84,6 +196,42 @@ function useDisclosure(props) {
|
|
|
84
196
|
};
|
|
85
197
|
}
|
|
86
198
|
|
|
199
|
+
/**
|
|
200
|
+
* `click` nativo → un `PressEvent` con la forma del de react-aria.
|
|
201
|
+
*
|
|
202
|
+
* Lo usan las piezas de esta librería que NO pasan por un componente de react-aria y aun así
|
|
203
|
+
* exponen el `onPress` de v2: la `Card` pulsable (`<button>` nativo) y el `Button` con `href`
|
|
204
|
+
* (`<a>` nativo). No se importa `usePress` de react-aria porque no es `external` en
|
|
205
|
+
* `rollup.config.mjs`: se metería una segunda copia dentro del bundle.
|
|
206
|
+
*
|
|
207
|
+
* Un `click` nativo ya cubre ratón, táctil y teclado (Enter/Espacio en un botón, Enter en un
|
|
208
|
+
* enlace). `detail === 0` es un click sintético —teclado o `.click()`—, y como react-aria, se
|
|
209
|
+
* sitúa entonces el punto en el centro del elemento (lo necesita la onda expansiva).
|
|
210
|
+
*/
|
|
211
|
+
function pressEventFromClick(event) {
|
|
212
|
+
const target = event.currentTarget;
|
|
213
|
+
const rect = target.getBoundingClientRect();
|
|
214
|
+
const fromKeyboard = event.detail === 0;
|
|
215
|
+
const native = event.nativeEvent;
|
|
216
|
+
const pointerType = fromKeyboard
|
|
217
|
+
? "keyboard"
|
|
218
|
+
: native.pointerType === "touch" || native.pointerType === "pen"
|
|
219
|
+
? native.pointerType
|
|
220
|
+
: "mouse";
|
|
221
|
+
return {
|
|
222
|
+
type: "press",
|
|
223
|
+
pointerType,
|
|
224
|
+
target,
|
|
225
|
+
shiftKey: event.shiftKey,
|
|
226
|
+
ctrlKey: event.ctrlKey,
|
|
227
|
+
metaKey: event.metaKey,
|
|
228
|
+
altKey: event.altKey,
|
|
229
|
+
x: fromKeyboard ? rect.width / 2 : event.clientX - rect.left,
|
|
230
|
+
y: fromKeyboard ? rect.height / 2 : event.clientY - rect.top,
|
|
231
|
+
continuePropagation: () => undefined,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
87
235
|
/** `clamp(0.01 * size, 0.2, size > 100 ? 0.75 : 0.5)` de v2, en ms. */
|
|
88
236
|
function duracionDeOnda(size) {
|
|
89
237
|
const maximo = size > 100 ? 750 : 500;
|
|
@@ -376,6 +524,77 @@ const SHAPE_CLASS = {
|
|
|
376
524
|
outline: "border-2",
|
|
377
525
|
ghost: "",
|
|
378
526
|
};
|
|
527
|
+
// ── Props DOM que el `Button` de react-aria-components descarta ────────────────────────────
|
|
528
|
+
//
|
|
529
|
+
// `title` (tooltip nativo, 3 usos en beweossmbs: los «Cancelar importación»), `tabIndex` (2),
|
|
530
|
+
// `aria-busy`/`aria-live`… no llegaban al `<button>`: el `Button` de react-aria-components pasa
|
|
531
|
+
// solo lo que deja su `filterDOMProps({ global: true })` más lo que genera `useButton` (ver
|
|
532
|
+
// `ButtonDOMProps` en `Button.types.ts`). v2 los pasaba todos. Se reponen con la prop `render` de
|
|
533
|
+
// react-aria (el mismo `<button>`, con el `ref` ya conectado en `domProps`). Van DESPUÉS de lo
|
|
534
|
+
// que genera react-aria, como en v2 (`mergeProps(ariaButtonProps, …, filterDOMProps(otherProps))`
|
|
535
|
+
// → gana el consumidor): medido, react-aria escribe `tabIndex="0"` en el botón, y el
|
|
536
|
+
// `tabIndex={-1}` que pasa `client-table-row` para sacarlo del orden de tabulación se perdía.
|
|
537
|
+
const DOM_ATTRS_DESCARTADOS = new Set([
|
|
538
|
+
"title",
|
|
539
|
+
"tabIndex",
|
|
540
|
+
"role",
|
|
541
|
+
"accessKey",
|
|
542
|
+
"draggable",
|
|
543
|
+
"spellCheck",
|
|
544
|
+
]);
|
|
545
|
+
function atributosDescartados(props) {
|
|
546
|
+
const out = {};
|
|
547
|
+
for (const [key, value] of Object.entries(props)) {
|
|
548
|
+
if (value !== undefined &&
|
|
549
|
+
(DOM_ATTRS_DESCARTADOS.has(key) || key.startsWith("aria-"))) {
|
|
550
|
+
out[key] = value;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
// Props de react-aria que NO son atributos DOM: en el camino de enlace (un `<a>` nativo, que no
|
|
556
|
+
// pasa por react-aria) no se reenvían, o React avisaría de atributos desconocidos.
|
|
557
|
+
const PROPS_SOLO_REACT_ARIA = new Set([
|
|
558
|
+
"onPressStart",
|
|
559
|
+
"onPressEnd",
|
|
560
|
+
"onPressChange",
|
|
561
|
+
"onPressUp",
|
|
562
|
+
"onHoverStart",
|
|
563
|
+
"onHoverEnd",
|
|
564
|
+
"onHoverChange",
|
|
565
|
+
"onFocusChange",
|
|
566
|
+
"excludeFromTabOrder",
|
|
567
|
+
"preventFocusOnPress",
|
|
568
|
+
"slot",
|
|
569
|
+
"fullWidth",
|
|
570
|
+
"render",
|
|
571
|
+
"ref",
|
|
572
|
+
"type",
|
|
573
|
+
"form",
|
|
574
|
+
"formAction",
|
|
575
|
+
"formEncType",
|
|
576
|
+
"formMethod",
|
|
577
|
+
"formNoValidate",
|
|
578
|
+
"formTarget",
|
|
579
|
+
"name",
|
|
580
|
+
"value",
|
|
581
|
+
]);
|
|
582
|
+
function propsDeEnlace(props) {
|
|
583
|
+
const out = {};
|
|
584
|
+
for (const [key, value] of Object.entries(props)) {
|
|
585
|
+
if (value === undefined || PROPS_SOLO_REACT_ARIA.has(key)) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (DOM_ATTRS_DESCARTADOS.has(key) ||
|
|
589
|
+
key === "id" ||
|
|
590
|
+
key.startsWith("aria-") ||
|
|
591
|
+
key.startsWith("data-") ||
|
|
592
|
+
/^on[A-Z]/.test(key)) {
|
|
593
|
+
out[key] = value;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return out;
|
|
597
|
+
}
|
|
379
598
|
/**
|
|
380
599
|
* Botón genérico basado en HeroUI v3 siguiendo las reglas de diseño BeweOS (TECH-2459 · F3.0).
|
|
381
600
|
*
|
|
@@ -391,8 +610,15 @@ const SHAPE_CLASS = {
|
|
|
391
610
|
* - `disableRipple`: v3 no tiene ripple — reimplementado en `_internal/ripple.tsx`, así que la
|
|
392
611
|
* prop vuelve a apagar algo.
|
|
393
612
|
* - `ai`: aura de Linda para las acciones que hace la IA (ver `Button.types.ts`).
|
|
613
|
+
* - Props DOM (`title`, `tabIndex`, `aria-*`…): llegan al `<button>` aunque react-aria las
|
|
614
|
+
* descarte (ver `DOM_ATTRS_DESCARTADOS`). `disabled` nativo equivale a `isDisabled`.
|
|
615
|
+
* - `as="a"` / `href` (API v2): renderiza un `<a>` con las mismas clases y colores del botón.
|
|
394
616
|
*/
|
|
395
|
-
const Button = ({ color = "primary", size = "md", variant = "solid", radius = "sm", startContent, endContent, isLoading = false, spinner, spinnerPlacement = "start", isIconOnly = false, isDisabled, ai, className, style, children, disableRipple = false, disableAnimation = false, onPress, ...rest }) => {
|
|
617
|
+
const Button = ({ color = "primary", size = "md", variant = "solid", radius = "sm", startContent, endContent, isLoading = false, spinner, spinnerPlacement = "start", isIconOnly = false, isDisabled, ai, className, style, children, disableRipple = false, disableAnimation = false, onPress, disabled, as, href, target, rel, download, hrefLang, referrerPolicy, ...rest }) => {
|
|
618
|
+
// v2 reenviaba `disabled` tal cual al `<button>` nativo, que así quedaba deshabilitado; en v3
|
|
619
|
+
// se descartaba y el botón seguía pulsable. Se trata como `isDisabled`.
|
|
620
|
+
const deshabilitado = Boolean(isDisabled || disabled || isLoading);
|
|
621
|
+
const esEnlace = href !== undefined || as === "a";
|
|
396
622
|
const shape = SHAPE_BY_VARIANT$1[variant];
|
|
397
623
|
const v3Variant = BASE_V3_VARIANT[shape];
|
|
398
624
|
const spinnerNode = spinner ?? jsxRuntime.jsx(react.Spinner, { size: "sm", color: "current" });
|
|
@@ -430,20 +656,62 @@ const Button = ({ color = "primary", size = "md", variant = "solid", radius = "s
|
|
|
430
656
|
* `.linda-aura > *` ya los alcanza.
|
|
431
657
|
*/
|
|
432
658
|
const contenido = ai ? (jsxRuntime.jsx("span", { className: "linda-aura-content", children: children })) : (children);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
659
|
+
const claseForma = [
|
|
660
|
+
SHAPE_CLASS[shape],
|
|
661
|
+
RADIUS_CLASS$a[radius],
|
|
662
|
+
ai ? CLASE_AURA[ai] : "",
|
|
663
|
+
// P-05: v2 apagaba transición + hundido con esta prop; v3 no tiene la prop, así
|
|
664
|
+
// que el interruptor es una clase propia (`Button.css`).
|
|
665
|
+
disableAnimation ? "button--no-animation" : "",
|
|
666
|
+
// P-06b: hover atenuado de v2 en vez del cambio de fondo de v3 (ver
|
|
667
|
+
// `HOVER_ATENUADO` arriba y `neutralize-v3.css`).
|
|
668
|
+
HOVER_ATENUADO[shape] ? "button--hover-dim" : "",
|
|
669
|
+
className,
|
|
670
|
+
]
|
|
671
|
+
.filter(Boolean)
|
|
672
|
+
.join(" ");
|
|
673
|
+
const estilo = { ...colorStyle$1(color, shape), ...style };
|
|
674
|
+
const hijos = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isLoading && spinnerPlacement === "start" && spinnerNode, !isLoading && startContent, contenido, !isLoading && endContent, isLoading && spinnerPlacement === "end" && spinnerNode, ondas.length > 0 && jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar })] }));
|
|
675
|
+
/*
|
|
676
|
+
* `as="a"` / `href` — el botón-enlace de v2 (3 usos en beweossmbs: «abrir contacto en
|
|
677
|
+
* pestaña nueva», «ver publicación», el enlace de agenda de la demo de referidos).
|
|
678
|
+
*
|
|
679
|
+
* v3 no acepta `href` en `Button` (trampa 5 de la skill `auraui-components`) y el `href`
|
|
680
|
+
* caía sin más: el botón no navegaba. Se renderiza un `<a>` NATIVO con las clases del
|
|
681
|
+
* botón (`buttonVariants` de v3, las mismas que pone `Button`) + las de la fachada.
|
|
682
|
+
*
|
|
683
|
+
* Por qué no las otras dos vías: `render` del `Button` de react-aria devolviendo un `<a>`
|
|
684
|
+
* provoca un `console.warn` en desarrollo (espera el MISMO elemento); y el `Link` de v3
|
|
685
|
+
* añade la clase `.link`, y `.link.button { gap: 0 }` se come el hueco del icono. El `:hover`
|
|
686
|
+
* de `.button`/`.button--hover-dim` funciona igual en un `<a>` (el CSS escucha `:hover` además
|
|
687
|
+
* de `data-hovered`).
|
|
688
|
+
*/
|
|
689
|
+
if (esEnlace) {
|
|
690
|
+
const alPulsarEnlace = (evento) => {
|
|
691
|
+
if (deshabilitado) {
|
|
692
|
+
evento.preventDefault();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
rest.onClick?.(evento);
|
|
696
|
+
alPulsar(pressEventFromClick(evento));
|
|
697
|
+
};
|
|
698
|
+
return (jsxRuntime.jsx("a", { ...propsDeEnlace(rest), "data-slot": "button", "data-disabled": deshabilitado || undefined, "aria-disabled": deshabilitado || undefined,
|
|
699
|
+
// Deshabilitado, sin `href`: un `<a>` no tiene `disabled` y seguiría navegando.
|
|
700
|
+
href: deshabilitado ? undefined : href, target: target, rel: rel, download: download, hrefLang: hrefLang, referrerPolicy: referrerPolicy, className: react.cn(react.buttonVariants({
|
|
701
|
+
variant: v3Variant,
|
|
702
|
+
size,
|
|
703
|
+
isIconOnly,
|
|
704
|
+
fullWidth: rest.fullWidth,
|
|
705
|
+
}), claseForma), style: estilo, onClick: alPulsarEnlace, children: hijos }));
|
|
706
|
+
}
|
|
707
|
+
const extras = atributosDescartados(rest);
|
|
708
|
+
const renderConExtras = Object.keys(extras).length > 0
|
|
709
|
+
? (domProps) => {
|
|
710
|
+
const merged = { ...domProps, ...extras };
|
|
711
|
+
return (jsxRuntime.jsx("button", { ...merged }));
|
|
712
|
+
}
|
|
713
|
+
: undefined;
|
|
714
|
+
return (jsxRuntime.jsx(react.Button, { ...rest, ...(renderConExtras ? { render: renderConExtras } : {}), variant: v3Variant, size: size, isIconOnly: isIconOnly, isPending: isLoading, isDisabled: deshabilitado, onPress: alPulsar, className: claseForma, style: estilo, children: hijos }));
|
|
447
715
|
};
|
|
448
716
|
|
|
449
717
|
// TECH-2459 · F3.0 — Matriz de traducción v2 → v3 (mismo mecanismo que Button, documentado en
|
|
@@ -489,6 +757,21 @@ const SHAPE_BY_VARIANT = {
|
|
|
489
757
|
flat: "soft",
|
|
490
758
|
bordered: "outline",
|
|
491
759
|
faded: "outline",
|
|
760
|
+
// `shadow` de v2 es `solid` + sombra teñida (ver `SHADOW_CLASS`).
|
|
761
|
+
shadow: "solid",
|
|
762
|
+
};
|
|
763
|
+
// `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
|
|
764
|
+
// `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
|
|
765
|
+
// (`default` usa `/50`). El relleno y el texto son los de `solid`; aquí solo se añade la sombra.
|
|
766
|
+
// Clases literales (no plantillas) para que Tailwind las genere al escanear `src/`. Los 5 colores
|
|
767
|
+
// fijos de Aurora (red/purple/…) no existían en v2 con `shadow`: reciben la sombra neutra.
|
|
768
|
+
const SHADOW_CLASS$1 = {
|
|
769
|
+
default: "shadow-lg shadow-default/50",
|
|
770
|
+
primary: "shadow-lg shadow-primary/40",
|
|
771
|
+
secondary: "shadow-lg shadow-secondary/40",
|
|
772
|
+
success: "shadow-lg shadow-success/40",
|
|
773
|
+
warning: "shadow-lg shadow-warning/40",
|
|
774
|
+
danger: "shadow-lg shadow-danger/40",
|
|
492
775
|
};
|
|
493
776
|
// TECH-2459 · F5 · P-08c — Tamaños de v2 (`chip.ts`): la altura era FIJA y el texto más grande que
|
|
494
777
|
// en v3, y el sangrado horizontal salía de dos slots (`base` + `content`):
|
|
@@ -726,7 +1009,7 @@ function colorStyle(color, shape) {
|
|
|
726
1009
|
* y el hueco que queda tras el botón de cierre sean distintos, como en v2 (ver `SIZE_CLASS` y
|
|
727
1010
|
* `CONTENT_PADDING`).
|
|
728
1011
|
*/
|
|
729
|
-
const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children,
|
|
1012
|
+
const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, as, style: consumerStyle, ...domProps }) => {
|
|
730
1013
|
const isStandard = STANDARD_COLORS.has(color);
|
|
731
1014
|
const shape = SHAPE_BY_VARIANT[variant];
|
|
732
1015
|
// Literal de v2 (`@heroui/chip@2.2.18`, `use-chip.js:63`):
|
|
@@ -750,12 +1033,27 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
750
1033
|
const flatDarkFgClass = isStandard && shape === "soft"
|
|
751
1034
|
? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
|
|
752
1035
|
: "";
|
|
753
|
-
const
|
|
1036
|
+
const baseStyle = vars
|
|
754
1037
|
? shape === "outline"
|
|
755
1038
|
? { ...vars, borderColor: vars["--chip-fg"] }
|
|
756
1039
|
: vars
|
|
757
1040
|
: undefined;
|
|
758
|
-
|
|
1041
|
+
// El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
|
|
1042
|
+
// beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
|
|
1043
|
+
const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
|
|
1044
|
+
const shadowClass = variant === "shadow"
|
|
1045
|
+
? isStandard
|
|
1046
|
+
? SHADOW_CLASS$1[color]
|
|
1047
|
+
: "shadow-lg"
|
|
1048
|
+
: "";
|
|
1049
|
+
// `as` de v2 (`Component = as || "div"`). v3 no tiene `as`, pero su raíz acepta `render`, que
|
|
1050
|
+
// recibe las props DOM ya compuestas (clases, `data-slot`, `ref`…) y devuelve el elemento.
|
|
1051
|
+
const render = as
|
|
1052
|
+
? (props) => $8WNJc$react.createElement(as, props)
|
|
1053
|
+
: undefined;
|
|
1054
|
+
return (jsxRuntime.jsxs(react.Chip
|
|
1055
|
+
// `aria-disabled` ANTES del spread: si el consumidor manda el suyo, gana el suyo.
|
|
1056
|
+
, { "aria-disabled": isDisabled || undefined, ...domProps, render: render, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
|
|
759
1057
|
// `cn` (tailwind-merge) y no una concatenación de texto: el consumidor tiene que poder
|
|
760
1058
|
// pisar tamaño y radio (`className="h-5 text-[10px]"` es un uso real en beweossmbs).
|
|
761
1059
|
// Con una cadena suelta ambas clases llegan al DOM y gana la que Tailwind emita más
|
|
@@ -764,18 +1062,20 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
764
1062
|
className: react.cn(SIZE_CLASS$6[size],
|
|
765
1063
|
// Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
|
|
766
1064
|
// respecto al tamaño normal y tiene que ganar en tailwind-merge.
|
|
767
|
-
isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style,
|
|
1065
|
+
isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
|
|
768
1066
|
? CONTENT_PADDING[size].leftWithContent
|
|
769
1067
|
: CONTENT_PADDING[size].left, hasEndContent
|
|
770
1068
|
? CONTENT_PADDING[size].rightWithContent
|
|
771
1069
|
: CONTENT_PADDING[size].right,
|
|
772
1070
|
// `content: "px-0 flex-none"` del compound de un carácter. Va después de los
|
|
773
1071
|
// `pl-*`/`pr-*` para que tailwind-merge los borre (un `px` posterior sí gana).
|
|
774
|
-
isSquare ? "px-0 flex-none" : "", classNames?.label
|
|
1072
|
+
isSquare ? "px-0 flex-none" : "", classNames?.label,
|
|
1073
|
+
// `content` es el nombre v2 del slot (16 usos en beweossmbs); va el último.
|
|
1074
|
+
classNames?.content), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
|
|
775
1075
|
// `.close-button` de v3 impone caja de 24px, `bg-default` y `text-muted`; v2 no
|
|
776
1076
|
// pintaba fondo ni caja: el glifo ERA el botón. Se anulan con utilidades (capa
|
|
777
1077
|
// `utilities` gana a `components`, incluido el `& svg { size-4 -mx-0.5 my-0.5 }`).
|
|
778
|
-
className: "h-auto w-auto min-h-0 rounded-full bg-transparent p-0 text-current opacity-70 transition-opacity hover:bg-transparent hover:opacity-100", onPress: onClose, isDisabled: isDisabled, children: jsxRuntime.jsx("svg", { "aria-hidden": "true", focusable: "false", role: "presentation", viewBox: "0 0 24 24", className: `m-0 shrink-0 ${CLOSE_ICON_SIZE_CLASS[size]}`, children: jsxRuntime.jsx("path", { d: CLOSE_FILLED_PATH, fill: "currentColor" }) }) }))] }));
|
|
1078
|
+
className: react.cn("h-auto w-auto min-h-0 rounded-full bg-transparent p-0 text-current opacity-70 transition-opacity hover:bg-transparent hover:opacity-100", classNames?.closeButton), onPress: onClose, isDisabled: isDisabled, children: jsxRuntime.jsx("svg", { "aria-hidden": "true", focusable: "false", role: "presentation", viewBox: "0 0 24 24", className: `m-0 shrink-0 ${CLOSE_ICON_SIZE_CLASS[size]}`, children: jsxRuntime.jsx("path", { d: CLOSE_FILLED_PATH, fill: "currentColor" }) }) }))] }));
|
|
779
1079
|
};
|
|
780
1080
|
|
|
781
1081
|
const sizeMap = {
|
|
@@ -816,10 +1116,10 @@ const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFli
|
|
|
816
1116
|
// (`bg-default-200` / `bg-default-300` en variant `flat`) se reponen también por clase por el
|
|
817
1117
|
// mismo motivo.
|
|
818
1118
|
//
|
|
819
|
-
// El RADIO no entra aquí a propósito: se compone por pieza más abajo.
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
// `rounded-[12px]` y la celda activa salía a escuadra.
|
|
1119
|
+
// El RADIO no entra aquí a propósito: se compone por pieza más abajo (`cellRadius`). Las piezas se
|
|
1120
|
+
// fusionan con `cn` (tailwind-merge), que resuelve dos clases del mismo grupo por su POSICIÓN en
|
|
1121
|
+
// la cadena. Antes se concatenaban como texto y decidía el orden en que Tailwind las emite:
|
|
1122
|
+
// `rounded-none` ganaba a `rounded-[12px]` y la celda activa salía a escuadra.
|
|
823
1123
|
// ⚠️ TECH-2459 · F5 · R2 — `shadow-xs`, no `shadow-sm`: **Tailwind 4 corrió la escala de sombras
|
|
824
1124
|
// un paso**. El `shadow-sm` de Tailwind 3 (que es el que emitía v2) es el `shadow-xs` de Tailwind
|
|
825
1125
|
// 4. Literal de `node_modules/tailwindcss/theme.css:406-407`:
|
|
@@ -844,11 +1144,38 @@ const CELL_CLASS = "size-9 md:size-9 min-w-9 bg-default-100 text-sm text-default
|
|
|
844
1144
|
* `w-fit` reproduce el `max-w-fit` del wrapper de v2.
|
|
845
1145
|
*/
|
|
846
1146
|
const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1147
|
+
/**
|
|
1148
|
+
* `isCompact={false}`: celdas sueltas. Literales del `tv` de v2 sin el bloque `isCompact`: el
|
|
1149
|
+
* `wrapper` vuelve a `gap-1` (4px) sin sombra común, y cada celda lleva su propio radio
|
|
1150
|
+
* (`radius: md` → `rounded-medium`, 12px).
|
|
1151
|
+
*/
|
|
1152
|
+
const STRIP_LOOSE_CLASS = "w-fit gap-1";
|
|
1153
|
+
/** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
|
|
1154
|
+
const NAV_CLASS = "w-9 px-0";
|
|
1155
|
+
/**
|
|
1156
|
+
* Radio de cada pieza según su posición en la tira.
|
|
1157
|
+
*
|
|
1158
|
+
* Compacta (el `isCompact` de v2): celdas interiores a escuadra
|
|
1159
|
+
* (`[&:not(:first-of-type):not(:last-of-type)]:rounded-none`) y solo el lado EXTERIOR de los
|
|
1160
|
+
* extremos redondeado (`first-of-type:rounded-e-none` / `last-of-type:rounded-s-none`). Los
|
|
1161
|
+
* extremos son las flechas cuando hay `showControls`, y las propias páginas cuando no: por eso
|
|
1162
|
+
* se decide por posición y no por tipo de pieza.
|
|
1163
|
+
*/
|
|
1164
|
+
function cellRadius(isCompact, position) {
|
|
1165
|
+
if (!isCompact) {
|
|
1166
|
+
return "rounded-[12px]";
|
|
1167
|
+
}
|
|
1168
|
+
if (position.isFirst && position.isLast) {
|
|
1169
|
+
return "rounded-[12px]";
|
|
1170
|
+
}
|
|
1171
|
+
if (position.isFirst) {
|
|
1172
|
+
return "rounded-s-[12px] rounded-e-none";
|
|
1173
|
+
}
|
|
1174
|
+
if (position.isLast) {
|
|
1175
|
+
return "rounded-e-[12px] rounded-s-none";
|
|
1176
|
+
}
|
|
1177
|
+
return "rounded-none";
|
|
1178
|
+
}
|
|
852
1179
|
/**
|
|
853
1180
|
* Página activa. v2 la pintaba con un "cursor" absoluto de `bg-primary text-primary-foreground` y
|
|
854
1181
|
* `rounded-medium` por encima de la celda; aquí se pinta la celda misma con los mismos tokens.
|
|
@@ -856,7 +1183,39 @@ const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
|
|
|
856
1183
|
* Sin esto v3 la pinta con `--pagination-link-bg: var(--default)`, el gris CRUDO de v3 y no el
|
|
857
1184
|
* `--color-default` de Aurora (falso amigo, decisiones-aceptadas-f1.md D-F1-3).
|
|
858
1185
|
*/
|
|
859
|
-
const ACTIVE_LINK_CLASS = "rounded-[12px]
|
|
1186
|
+
const ACTIVE_LINK_CLASS = "rounded-[12px]";
|
|
1187
|
+
/**
|
|
1188
|
+
* `color` de v2 → la página activa. Literal de `colorVariants.solid` (`@heroui/theme@2.4.15`,
|
|
1189
|
+
* `dist/components/pagination.js`): `bg-<color> text-<color>-foreground`. Los estados hover y
|
|
1190
|
+
* pulsado se fijan al mismo fondo porque la celda base los cambia (`CELL_CLASS`) y en v2 el
|
|
1191
|
+
* cursor activo no reaccionaba al ratón. Cadenas literales completas: Tailwind solo emite lo que
|
|
1192
|
+
* encuentra escrito entero.
|
|
1193
|
+
*
|
|
1194
|
+
* `default` es el gris de Aurora (`--color-default`), no el `--default` crudo de v3 (falso
|
|
1195
|
+
* amigo, decisiones-aceptadas-f1.md D-F1-3).
|
|
1196
|
+
*/
|
|
1197
|
+
const ACTIVE_COLOR_CLASS = {
|
|
1198
|
+
default: "bg-default text-default-foreground hover:bg-default active:bg-default",
|
|
1199
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary active:bg-primary",
|
|
1200
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary active:bg-secondary",
|
|
1201
|
+
success: "bg-success text-success-foreground hover:bg-success active:bg-success",
|
|
1202
|
+
warning: "bg-warning text-warning-foreground hover:bg-warning active:bg-warning",
|
|
1203
|
+
danger: "bg-danger text-danger-foreground hover:bg-danger active:bg-danger",
|
|
1204
|
+
};
|
|
1205
|
+
/**
|
|
1206
|
+
* `showShadow` de v2 → sombra de color bajo la página activa. Literal del compound
|
|
1207
|
+
* `{ showShadow: true, color }` de v2: `cursor: [colorVariants.shadow[color], "shadow-md"]`, que
|
|
1208
|
+
* tras tailwind-merge queda `shadow-md shadow-<color>/40` (`/50` en `default`). `shadow-md` no
|
|
1209
|
+
* cambió de valor entre Tailwind 3 y 4 (el corrimiento de la escala solo afectó a `sm`/`DEFAULT`).
|
|
1210
|
+
*/
|
|
1211
|
+
const ACTIVE_SHADOW_CLASS = {
|
|
1212
|
+
default: "shadow-md shadow-default/50",
|
|
1213
|
+
primary: "shadow-md shadow-primary/40",
|
|
1214
|
+
secondary: "shadow-md shadow-secondary/40",
|
|
1215
|
+
success: "shadow-md shadow-success/40",
|
|
1216
|
+
warning: "shadow-md shadow-warning/40",
|
|
1217
|
+
danger: "shadow-md shadow-danger/40",
|
|
1218
|
+
};
|
|
860
1219
|
/**
|
|
861
1220
|
* Traducciones por defecto en español. Todas son `aria-label`: este componente no tiene más
|
|
862
1221
|
* texto visible que los números de página.
|
|
@@ -968,12 +1327,22 @@ function getPaginationRange(total, current) {
|
|
|
968
1327
|
* `aria-label` los dos botones llegaban vacíos al árbol de accesibilidad.
|
|
969
1328
|
*
|
|
970
1329
|
|
|
1330
|
+
* API v2 que las apps siguen escribiendo (medido en beweossmbs `origin/staging`: 17 usos, todos
|
|
1331
|
+
* con `showControls`, 14 con `isCompact`, 12 con `color="primary"`, 11 con `showShadow`):
|
|
1332
|
+
* - `showControls` (default `true`): pinta anterior/siguiente; con `false` desaparecen.
|
|
1333
|
+
* - `isCompact` (default `true`): tira compacta; con `false`, celdas sueltas (`gap-1`).
|
|
1334
|
+
* - `color` (default `"primary"`): color de la página activa.
|
|
1335
|
+
* - `showShadow` (default `false`): sombra de color bajo la página activa.
|
|
1336
|
+
* Los defaults `true` de las dos primeras son los que forzaba la fachada v2 de Aurora
|
|
1337
|
+
* (`<HeroUIPagination showControls isCompact {...props} />`), así que quien no las pasa se ve
|
|
1338
|
+
* exactamente igual que hasta ahora.
|
|
1339
|
+
*
|
|
971
1340
|
* ⚠️ `size` se acepta pero no se aplica: mismo bug preexistente que v2 (destructurado, nunca
|
|
972
1341
|
* reenviado a HeroUI) — fuera de scope de esta migración, reportado aparte (ver
|
|
973
1342
|
* findings/decisiones-aceptadas-g2.md y f5-L7-sueltos.md · P-17). v3 SÍ tiene la prop (emite
|
|
974
1343
|
* `.pagination--sm|--md|--lg`), así que arreglarlo es un cambio de tamaño visible: ticket aparte.
|
|
975
1344
|
*/
|
|
976
|
-
const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, translations = {}, }) => {
|
|
1345
|
+
const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
|
|
977
1346
|
const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
|
|
978
1347
|
const currentPage = page ?? internalPage;
|
|
979
1348
|
const t = { ...DEFAULT_TRANSLATIONS$g, ...translations };
|
|
@@ -990,18 +1359,26 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
990
1359
|
// `dist/pagination.js:464`). Si `page` viene fuera de rango, `indexOf` da -1 y todas las
|
|
991
1360
|
// elipsis avanzan — también igual que v2, que no protegía ese caso.
|
|
992
1361
|
const activeIndex = items.indexOf(currentPage);
|
|
1362
|
+
// Posición de cada pieza en la tira, para el radio compacto: con controles, los extremos
|
|
1363
|
+
// son las flechas y ninguna página toca el borde.
|
|
1364
|
+
const lastItemIndex = items.length - 1;
|
|
1365
|
+
const itemPosition = (index) => ({
|
|
1366
|
+
isFirst: !showControls && index === 0,
|
|
1367
|
+
isLast: !showControls && index === lastItemIndex,
|
|
1368
|
+
});
|
|
1369
|
+
const activeClass = react.cn(ACTIVE_COLOR_CLASS[color], showShadow ? ACTIVE_SHADOW_CLASS[color] : "", ACTIVE_LINK_CLASS);
|
|
993
1370
|
return (
|
|
994
1371
|
// `w-fit` reproduce el `max-w-fit` del wrapper de v2: `.pagination` de v3 es `w-full` +
|
|
995
1372
|
// `justify-between`, así que dentro de un `flex justify-center` (como el de AccordionList) la
|
|
996
1373
|
// tira se quedaba pegada a la izquierda en vez de centrarse.
|
|
997
|
-
jsxRuntime.jsx(react.Pagination, { className:
|
|
1374
|
+
jsxRuntime.jsx(react.Pagination, { className: react.cn("w-fit", className), children: jsxRuntime.jsxs(react.Pagination.Content, { className: isCompact ? STRIP_CLASS : STRIP_LOOSE_CLASS, children: [showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Previous
|
|
998
1375
|
// v3 mete el galón en un `<span aria-hidden="true">`
|
|
999
1376
|
// (`pagination.js:165`), así que sin esto el botón se queda SIN nombre
|
|
1000
1377
|
// accesible: un lector de pantalla lo anuncia como "botón" a secas. v2 sí
|
|
1001
1378
|
// lo nombraba ("previous page button", `use-pagination.js:213`), pero en
|
|
1002
1379
|
// inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
|
|
1003
1380
|
// i18n del repo, skill `auraui-i18n`).
|
|
1004
|
-
, { "aria-label": t.previousPageAriaLabel, className:
|
|
1381
|
+
, { "aria-label": t.previousPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: true, isLast: false })), isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) })), items.map((item, index) => {
|
|
1005
1382
|
if (item === "ellipsis") {
|
|
1006
1383
|
const isBefore = index < activeIndex;
|
|
1007
1384
|
const label = isBefore
|
|
@@ -1009,14 +1386,14 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
1009
1386
|
: t.jumpForwardAriaLabel;
|
|
1010
1387
|
return (
|
|
1011
1388
|
// biome-ignore lint/suspicious/noArrayIndexKey: solo hay como máximo 2 elipsis, posición estable
|
|
1012
|
-
jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsxs(react.Pagination.Link, { "aria-label": label.replace("{count}", String(DOTS_JUMP)), className:
|
|
1389
|
+
jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsxs(react.Pagination.Link, { "aria-label": label.replace("{count}", String(DOTS_JUMP)), className: react.cn("group", CELL_CLASS, cellRadius(isCompact, itemPosition(index))), isDisabled: isDisabled, onPress: () => goTo(isBefore
|
|
1013
1390
|
? currentPage - DOTS_JUMP
|
|
1014
1391
|
: currentPage + DOTS_JUMP), children: [jsxRuntime.jsx(EllipsisDotsIcon, {}), jsxRuntime.jsx(JumpIcon, { isBefore: isBefore })] }) }, `ellipsis-${index}`));
|
|
1015
1392
|
}
|
|
1016
|
-
return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className:
|
|
1017
|
-
}), jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
|
|
1393
|
+
return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className: react.cn(CELL_CLASS, cellRadius(isCompact, itemPosition(index)), item === currentPage ? activeClass : ""), isActive: item === currentPage, isDisabled: isDisabled, onPress: () => goTo(item), children: item }) }, item));
|
|
1394
|
+
}), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
|
|
1018
1395
|
/* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
|
|
1019
|
-
, { "aria-label": t.nextPageAriaLabel, className:
|
|
1396
|
+
, { "aria-label": t.nextPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: false, isLast: true })), isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) }))] }) }));
|
|
1020
1397
|
};
|
|
1021
1398
|
|
|
1022
1399
|
// TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
|
|
@@ -1185,6 +1562,33 @@ exports.HolidayType = void 0;
|
|
|
1185
1562
|
HolidayType["DateRange"] = "dateRange";
|
|
1186
1563
|
})(exports.HolidayType || (exports.HolidayType = {}));
|
|
1187
1564
|
|
|
1565
|
+
// Piezas compartidas por `DatePicker` y `DateRangePicker` para reponer `classNames` y
|
|
1566
|
+
// `calendarWidth` de v2 (TECH-2459). Interno: no se exporta desde el índice del paquete.
|
|
1567
|
+
/**
|
|
1568
|
+
* Fusiona las clases de la fachada con las del consumidor (`classNames.<slot>`), las del consumidor
|
|
1569
|
+
* DETRÁS para que ganen en tailwind-merge — como el `slots.x({ class })` de v2, cuyo `tv` llevaba
|
|
1570
|
+
* `twMerge: true`. Sin clase del consumidor la cadena de la fachada sale TAL CUAL (unida con
|
|
1571
|
+
* espacios, como antes de existir `classNames`): pasar la fachada por `cn` sin necesidad resolvería
|
|
1572
|
+
* sus conflictos internos (p. ej. `rounded-none` de `underlined` contra el radio) de otra forma que
|
|
1573
|
+
* la cascada de hoy, y cambiaría el píxel de quien no usa la prop.
|
|
1574
|
+
*/
|
|
1575
|
+
function withConsumerClass(own, consumer) {
|
|
1576
|
+
const base = own.filter(Boolean).join(" ");
|
|
1577
|
+
return consumer ? react.cn(base, consumer) : base;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* `calendarWidth` de v2 → estilo en línea del `Calendar`. v3 fija `.calendar { w-63 max-w-63 }`
|
|
1581
|
+
* (`@heroui/styles/dist/components/calendar.css:11-13`), así que el `max-width` tiene que acompañar
|
|
1582
|
+
* al `width`: con solo `width: 300px` el tope de 252px lo seguiría recortando.
|
|
1583
|
+
*/
|
|
1584
|
+
function calendarWidthStyle(calendarWidth, style) {
|
|
1585
|
+
if (calendarWidth === undefined) {
|
|
1586
|
+
return style;
|
|
1587
|
+
}
|
|
1588
|
+
const width = typeof calendarWidth === "number" ? `${calendarWidth}px` : calendarWidth;
|
|
1589
|
+
return { ...style, width, maxWidth: width };
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1188
1592
|
// Mismo mecanismo que Input/Select (findings/campo-texto-responsive-sm-trap.md):
|
|
1189
1593
|
// `.date-input-group__input` fija su propio `text-base sm:text-sm` — se repite la clase de
|
|
1190
1594
|
// tamaño de TEXTO directo en `DateField.Input`, no solo en el `DateField.Group` contenedor.
|
|
@@ -1233,6 +1637,44 @@ const VARIANT_CLASS$5 = {
|
|
|
1233
1637
|
* En reposo no cambia nada (el anillo de v3 ya vale 0 ahí), así que la referencia v2 se mantiene.
|
|
1234
1638
|
*/
|
|
1235
1639
|
const FIELD_FOCUS_CLASS$5 = "ring-0 ring-offset-0 outline-0";
|
|
1640
|
+
/*
|
|
1641
|
+
* TECH-2459 · hallazgo beweossmbs 23-sep — `labelPlacement` de v2, repuesta. Misma solución que
|
|
1642
|
+
* `TimeInput` en la .139: una tabla de clases por colocación. La barra de la agenda
|
|
1643
|
+
* (`agenda-toolbar.component.tsx`) pide `"outside-left"` y v1 no tenía la prop: la etiqueta caía
|
|
1644
|
+
* encima y la barra crecía una línea.
|
|
1645
|
+
*
|
|
1646
|
+
* `outside` es EXACTAMENTE lo que la fachada pintaba hasta ahora (la raíz `flex flex-col gap-1`,
|
|
1647
|
+
* sin clases de colocación en el grupo ni en la ayuda), así que quien no pasa la prop no cambia un
|
|
1648
|
+
* píxel. `outside-left` son los literales del `tv` de v2 (`@heroui/theme@2.4.15` →
|
|
1649
|
+
* `dist/components/date-input.js`, `labelPlacement["outside-left"]`, que es el `tv` del que tira el
|
|
1650
|
+
* `DatePicker` de v2):
|
|
1651
|
+
*
|
|
1652
|
+
* base → "flex-row items-center gap-x-2 flex-nowrap"
|
|
1653
|
+
* + `data-[has-helper=true]:pb-[calc(theme(fontSize.tiny)_+_8px)]`
|
|
1654
|
+
* inputWrapper → "relative flex-1"
|
|
1655
|
+
* helperWrapper→ "absolute top-[calc(100%_+_2px)] start-0"
|
|
1656
|
+
*
|
|
1657
|
+
* La ayuda y el error no pueden ser un tercer hijo de la fila (quedarían a la derecha del campo):
|
|
1658
|
+
* v2 los sacaba del flujo bajo el campo y reservaba su alto con el `pb` de la raíz. Aquí igual;
|
|
1659
|
+
* `theme(fontSize.tiny)` de v2 (12px) es `--text-xs` en Tailwind 4, y la raíz lleva `relative`
|
|
1660
|
+
* para que el `absolute` se ancle a ella.
|
|
1661
|
+
*/
|
|
1662
|
+
const ROOT_PLACEMENT_CLASS = {
|
|
1663
|
+
outside: "flex flex-col gap-1",
|
|
1664
|
+
"outside-left": "relative flex flex-row items-center gap-x-2 flex-nowrap",
|
|
1665
|
+
};
|
|
1666
|
+
const ROOT_HELPER_SPACE_CLASS = {
|
|
1667
|
+
outside: undefined,
|
|
1668
|
+
"outside-left": "pb-[calc(var(--text-xs)+8px)]",
|
|
1669
|
+
};
|
|
1670
|
+
const GROUP_PLACEMENT_CLASS = {
|
|
1671
|
+
outside: undefined,
|
|
1672
|
+
"outside-left": "relative flex-1",
|
|
1673
|
+
};
|
|
1674
|
+
const HELPER_PLACEMENT_CLASS = {
|
|
1675
|
+
outside: undefined,
|
|
1676
|
+
"outside-left": "absolute top-[calc(100%+2px)] start-0",
|
|
1677
|
+
};
|
|
1236
1678
|
// TECH-2459 · F5 · P-10 — El "círculo" del calendario. v2 (`@heroui/theme@2.4.15`,
|
|
1237
1679
|
// `calendar.ts`) solo tenía UN círculo y significaba "día seleccionado":
|
|
1238
1680
|
// cellButton: "w-8 h-8 … rounded-full" + color primary: "data-[selected=true]:bg-primary
|
|
@@ -1259,20 +1701,32 @@ const CALENDAR_CELL_CLASS$1 = "size-8 place-self-center rounded-full " +
|
|
|
1259
1701
|
* (grid) para que la API pública siga siendo un único componente con props planas, igual que en
|
|
1260
1702
|
* v2 (label, description, errorMessage, endContent, variant, size, radius).
|
|
1261
1703
|
*/
|
|
1262
|
-
function DatePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent, className, "aria-label": ariaLabel, ...props }) {
|
|
1704
|
+
function DatePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", labelPlacement = "outside", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent: calendarBottomContentProp, CalendarBottomContent, calendarTopContent: calendarTopContentProp, CalendarTopContent, calendarWidth, calendarProps, classNames, className, "aria-label": ariaLabel, ...props }) {
|
|
1263
1705
|
const generatedId = $8WNJc$react.useId();
|
|
1264
1706
|
const autoId = id || generatedId;
|
|
1265
|
-
const calendarLabel = (typeof label === "string" ? label : undefined) ??
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1707
|
+
const calendarLabel = (typeof label === "string" ? label : undefined) ??
|
|
1708
|
+
ariaLabel ??
|
|
1709
|
+
"Calendario";
|
|
1710
|
+
const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
|
|
1711
|
+
const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
|
|
1712
|
+
const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
|
|
1713
|
+
const { className: calendarClassName, style: calendarStyle, ...restCalendarProps } = calendarProps ?? {};
|
|
1714
|
+
return (jsxRuntime.jsxs(react.DatePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass([
|
|
1715
|
+
ROOT_PLACEMENT_CLASS[labelPlacement],
|
|
1716
|
+
fullWidth && "w-full",
|
|
1717
|
+
hasHelper && ROOT_HELPER_SPACE_CLASS[labelPlacement],
|
|
1718
|
+
classNames?.base,
|
|
1719
|
+
], className), children: [label && (jsxRuntime.jsx(react.Label, { className: withConsumerClass(["text-default-600 text-xs font-medium"], classNames?.label), children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: withConsumerClass([
|
|
1269
1720
|
SIZE_CLASS$5[size],
|
|
1270
1721
|
RADIUS_CLASS$8[radius],
|
|
1271
1722
|
VARIANT_CLASS$5[variant],
|
|
1272
1723
|
FIELD_FOCUS_CLASS$5,
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1724
|
+
GROUP_PLACEMENT_CLASS[labelPlacement],
|
|
1725
|
+
], classNames?.inputWrapper), children: [jsxRuntime.jsx(react.DateField.Input, { className: withConsumerClass([SIZE_TEXT_CLASS$4[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DatePicker.Trigger, { className: classNames?.selectorButton, children: endContent !== undefined ? (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon, children: endContent })) : (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon })) }) })] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.description) || undefined, children: description })), jsxRuntime.jsxs(react.DatePicker.Popover, { className: classNames?.popoverContent, children: [calendarTopContent, jsxRuntime.jsxs(react.Calendar, { "aria-label": calendarLabel, minValue: props.minValue, maxValue: props.maxValue, ...restCalendarProps, className: withConsumerClass([
|
|
1726
|
+
typeof calendarClassName === "string"
|
|
1727
|
+
? calendarClassName
|
|
1728
|
+
: undefined,
|
|
1729
|
+
], classNames?.calendar) || undefined, style: calendarWidthStyle(calendarWidth, typeof calendarStyle === "object" ? calendarStyle : undefined), children: [jsxRuntime.jsxs(react.Calendar.Header, { children: [jsxRuntime.jsxs(react.Calendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.Calendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.Calendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.Calendar.Grid, { children: [jsxRuntime.jsx(react.Calendar.GridHeader, { children: (day) => jsxRuntime.jsx(react.Calendar.HeaderCell, { children: day }) }), jsxRuntime.jsx(react.Calendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.Calendar.Cell, { date: date, className: CALENDAR_CELL_CLASS$1 })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.errorMessage) || undefined, children: errorMessage }))] }));
|
|
1276
1730
|
}
|
|
1277
1731
|
|
|
1278
1732
|
// Mismo mecanismo que DatePicker.tsx / Input.tsx / Select.tsx (findings/
|
|
@@ -1351,20 +1805,25 @@ const CALENDAR_CELL_CLASS = "data-[today=true]:not-data-[selected=true]:[&_[data
|
|
|
1351
1805
|
* `DatePicker`, migrado en tándem. Compone `DateRangePicker.Root/Trigger/TriggerIndicator/
|
|
1352
1806
|
* RangeSeparator/Popover` + `DateField` (dos segmentos, start/end) + `RangeCalendar`.
|
|
1353
1807
|
*/
|
|
1354
|
-
function DateRangePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent, className, "aria-label": ariaLabel, ...props }) {
|
|
1808
|
+
function DateRangePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent: calendarBottomContentProp, CalendarBottomContent, calendarTopContent: calendarTopContentProp, CalendarTopContent, calendarWidth, calendarProps, classNames, className, "aria-label": ariaLabel, ...props }) {
|
|
1355
1809
|
const generatedId = $8WNJc$react.useId();
|
|
1356
1810
|
const autoId = id || generatedId;
|
|
1357
|
-
const calendarLabel = (typeof label === "string" ? label : undefined) ??
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1811
|
+
const calendarLabel = (typeof label === "string" ? label : undefined) ??
|
|
1812
|
+
ariaLabel ??
|
|
1813
|
+
"Rango de fechas";
|
|
1814
|
+
const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
|
|
1815
|
+
const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
|
|
1816
|
+
const { className: calendarClassName, style: calendarStyle, ...restCalendarProps } = calendarProps ?? {};
|
|
1817
|
+
return (jsxRuntime.jsxs(react.DateRangePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass(["flex flex-col gap-1", fullWidth && "w-full", classNames?.base], className), children: [label && (jsxRuntime.jsx(react.Label, { className: withConsumerClass(["text-default-600 text-xs font-medium"], classNames?.label), children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: withConsumerClass([
|
|
1361
1818
|
SIZE_CLASS$4[size],
|
|
1362
1819
|
RADIUS_CLASS$7[radius],
|
|
1363
1820
|
VARIANT_CLASS$4[variant],
|
|
1364
1821
|
FIELD_FOCUS_CLASS$4,
|
|
1365
|
-
]
|
|
1366
|
-
|
|
1367
|
-
|
|
1822
|
+
], classNames?.inputWrapper), children: [jsxRuntime.jsx(react.DateField.Input, { slot: "start", className: withConsumerClass([SIZE_TEXT_CLASS$3[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateRangePicker.RangeSeparator, { className: classNames?.separator }), jsxRuntime.jsx(react.DateField.Input, { slot: "end", className: withConsumerClass([SIZE_TEXT_CLASS$3[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DateRangePicker.Trigger, { className: classNames?.selectorButton, children: endContent !== undefined ? (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, { className: classNames?.selectorIcon, children: endContent })) : (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, { className: classNames?.selectorIcon })) }) })] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: classNames?.description, children: description })), jsxRuntime.jsxs(react.DateRangePicker.Popover, { className: classNames?.popoverContent, children: [calendarTopContent, jsxRuntime.jsxs(react.RangeCalendar, { "aria-label": calendarLabel, minValue: props.minValue, maxValue: props.maxValue, ...restCalendarProps, className: withConsumerClass([
|
|
1823
|
+
typeof calendarClassName === "string"
|
|
1824
|
+
? calendarClassName
|
|
1825
|
+
: undefined,
|
|
1826
|
+
], classNames?.calendar) || undefined, style: calendarWidthStyle(calendarWidth, typeof calendarStyle === "object" ? calendarStyle : undefined), children: [jsxRuntime.jsxs(react.RangeCalendar.Header, { children: [jsxRuntime.jsxs(react.RangeCalendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.RangeCalendar.Grid, { children: [jsxRuntime.jsx(react.RangeCalendar.GridHeader, { children: (day) => (jsxRuntime.jsx(react.RangeCalendar.HeaderCell, { children: day })) }), jsxRuntime.jsx(react.RangeCalendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.RangeCalendar.Cell, { date: date, className: CALENDAR_CELL_CLASS })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: classNames?.errorMessage, children: errorMessage }))] }));
|
|
1368
1827
|
}
|
|
1369
1828
|
|
|
1370
1829
|
// TECH-2459 · F3.0 — v3 divide el `Input` monolítico de v2 en `TextField` (label/description/
|
|
@@ -1678,6 +2137,10 @@ const INVALID_TEXT_CLASS$2 = "text-danger";
|
|
|
1678
2137
|
* - Radius configurable (v3 no tiene la prop).
|
|
1679
2138
|
* - `startContent`/`endContent` → `InputGroup.Prefix`/`InputGroup.Suffix`.
|
|
1680
2139
|
* - `isClearable` → botón de limpiar propio (`CloseButton`); solo en modo controlado (`value`).
|
|
2140
|
+
* - `onClear` (API v2) → basta para mostrar la X; se llama al limpiar (X o Escape) y el foco
|
|
2141
|
+
* vuelve al campo, como en v2.
|
|
2142
|
+
* - `classNames`: los 10 slots de v2, cada uno en su nodo equivalente (tabla en `Input.types.ts`).
|
|
2143
|
+
* - `labelPlacement` (API v2): aceptado y sin efecto — el label siempre va fuera.
|
|
1681
2144
|
*
|
|
1682
2145
|
* `ref` apunta al `<input>` REAL (`HTMLInputElement`), no al `InputGroup` ni al `TextField` que lo
|
|
1683
2146
|
* envuelven: el consumidor que necesita el nodo —insertar texto en la posición del cursor con
|
|
@@ -1697,7 +2160,10 @@ const Input = $8WNJc$react.forwardRef(({ label, description, errorMessage, id,
|
|
|
1697
2160
|
// `lg` que había aquí venía del `argTypes.defaultValue.summary` de la story, que YA estaba mal
|
|
1698
2161
|
// en v2. Con "lg" todos los campos de las 4 apps que no pasan `size` salían 8px más altos, con
|
|
1699
2162
|
// 2px más de padding lateral y el texto a 16px en vez de 14.
|
|
1700
|
-
size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable
|
|
2163
|
+
size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable: isClearableProp = false, onClear,
|
|
2164
|
+
// Sin efecto (ver `InputProps.labelPlacement`): se saca del spread para que no llegue
|
|
2165
|
+
// al `<input>` como atributo desconocido.
|
|
2166
|
+
labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
1701
2167
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
1702
2168
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
1703
2169
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -1705,7 +2171,21 @@ size = "md", variant = "bordered", radius = "md", placeholder, startContent, end
|
|
|
1705
2171
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
1706
2172
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
1707
2173
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
1708
|
-
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", ...rest }, ref) => {
|
|
2174
|
+
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown, ...rest }, ref) => {
|
|
2175
|
+
// El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
|
|
2176
|
+
// (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
|
|
2177
|
+
const inputRef = $8WNJc$react.useRef(null);
|
|
2178
|
+
const setInputRef = $8WNJc$react.useCallback((node) => {
|
|
2179
|
+
inputRef.current = node;
|
|
2180
|
+
if (typeof ref === "function") {
|
|
2181
|
+
ref(node);
|
|
2182
|
+
}
|
|
2183
|
+
else if (ref) {
|
|
2184
|
+
ref.current = node;
|
|
2185
|
+
}
|
|
2186
|
+
}, [ref]);
|
|
2187
|
+
// v2: `isClearable = !!onClear || originalProps.isClearable` (`use-input.ts`).
|
|
2188
|
+
const isClearable = isClearableProp || Boolean(onClear);
|
|
1709
2189
|
const generatedId = $8WNJc$react.useId();
|
|
1710
2190
|
const inputId = id || generatedId;
|
|
1711
2191
|
// ⚠️ `isRequired` (prop de HeroUI, asterisco visual) y `required` (atributo HTML nativo, solo
|
|
@@ -1713,7 +2193,24 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1713
2193
|
// Detectado por VRT (`FormExample`, que solo pasa `required`): tratarlos como lo mismo aquí
|
|
1714
2194
|
// introducía un asterisco que v2 no tenía. `isRequired` maneja el asterisco (vía TextField);
|
|
1715
2195
|
// `required` se reenvía tal cual al `<input>` nativo, sin efecto visual.
|
|
1716
|
-
|
|
2196
|
+
// ── El valor lo guarda el `TextField`, no el `<input>` ───────────────────────────────────
|
|
2197
|
+
//
|
|
2198
|
+
// En react-aria el dueño del valor es el `TextField`: `useTextField` lo guarda con
|
|
2199
|
+
// `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<input>` como `value`
|
|
2200
|
+
// por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la fachada pasaba
|
|
2201
|
+
// `value`/`defaultValue` al `InputGroup.Input` y ninguno al `TextField`, así que en modo NO
|
|
2202
|
+
// controlado el `TextField` arrancaba en "" y el `<input>` recibía a la vez `value=""` (del
|
|
2203
|
+
// contexto) y `defaultValue`: medido en jsdom, `<Input defaultValue="hola" />` salía VACÍO
|
|
2204
|
+
// ya en el primer render, con el aviso de React «both value and defaultValue». Mismo arreglo
|
|
2205
|
+
// que en `Textarea.tsx`.
|
|
2206
|
+
//
|
|
2207
|
+
// Aquí además la fachada lleva el valor no controlado en su propio estado (en vez de
|
|
2208
|
+
// dejárselo al `TextField`), porque limpiar tiene que poder vaciarlo: `isClearable`/`onClear`
|
|
2209
|
+
// en modo no controlado no tenían manera de escribir en el estado interno del `TextField`.
|
|
2210
|
+
const isControlled = value !== undefined;
|
|
2211
|
+
const [uncontrolledValue, setUncontrolledValue] = $8WNJc$react.useState(defaultValue ?? "");
|
|
2212
|
+
const currentValue = isControlled ? value : uncontrolledValue;
|
|
2213
|
+
const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
|
|
1717
2214
|
// Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
|
|
1718
2215
|
// arriba desde el primer render (ver el bloque de arriba).
|
|
1719
2216
|
const labelFlotante = Boolean(label) && !placeholder;
|
|
@@ -1721,7 +2218,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1721
2218
|
onChange?.(event);
|
|
1722
2219
|
onValueChange?.(event.target.value);
|
|
1723
2220
|
};
|
|
1724
|
-
|
|
2221
|
+
// Orden de v2 (`handleClear` de `use-input.ts`): primero el valor a "" —que en v2 disparaba
|
|
2222
|
+
// `onValueChange`—, luego `onClear`, y el foco vuelve al campo.
|
|
2223
|
+
const clearValue = () => {
|
|
2224
|
+
if (!isControlled) {
|
|
2225
|
+
setUncontrolledValue("");
|
|
2226
|
+
}
|
|
1725
2227
|
onValueChange?.("");
|
|
1726
2228
|
if (onChange) {
|
|
1727
2229
|
const fakeEvent = {
|
|
@@ -1730,15 +2232,38 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1730
2232
|
};
|
|
1731
2233
|
onChange(fakeEvent);
|
|
1732
2234
|
}
|
|
2235
|
+
onClear?.();
|
|
2236
|
+
};
|
|
2237
|
+
const handleClear = () => {
|
|
2238
|
+
clearValue();
|
|
2239
|
+
inputRef.current?.focus();
|
|
2240
|
+
};
|
|
2241
|
+
// Escape limpia, igual que v2 (`handleKeyDown` de `use-input.ts`: con valor, limpiable y
|
|
2242
|
+
// no de solo lectura). El `onKeyDown` del consumidor se llama primero.
|
|
2243
|
+
const handleKeyDown = (event) => {
|
|
2244
|
+
onKeyDown?.(event);
|
|
2245
|
+
if (event.key === "Escape" && showClear) {
|
|
2246
|
+
clearValue();
|
|
2247
|
+
}
|
|
1733
2248
|
};
|
|
1734
|
-
return (jsxRuntime.jsxs(react.TextField, { id: inputId,
|
|
2249
|
+
return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
|
|
2250
|
+
// Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
|
|
2251
|
+
// (que se entera por `onChange`/`onValueChange` del `<input>`).
|
|
2252
|
+
onChange: isControlled ? undefined : setUncontrolledValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS$3[size], fullWidth ? "w-full" : "",
|
|
2253
|
+
// `mainWrapper` de v2 no tiene nodo propio aquí (ver tabla en `Input.types.ts`):
|
|
2254
|
+
// se fusiona en la raíz antes que `base`, que es el slot más externo.
|
|
2255
|
+
classNames?.mainWrapper,
|
|
2256
|
+
// `base` de v2 = la raíz. v2 los juntaba así: `clsx(classNames.base, className)`.
|
|
2257
|
+
classNames?.base,
|
|
1735
2258
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
1736
2259
|
// v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
|
|
1737
2260
|
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$4, LABEL_LEFT_CLASS[variant], variant === "underlined" ? "top-0" : LABEL_OFFSET_CLASS$1[size], isInvalid ? INVALID_LABEL_CLASS$2 : "", labelFlotante ? LABEL_INSIDE_CLASS[size] : "", labelFlotante ? LABEL_FLOAT_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.InputGroup, { isInvalid: isInvalid, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(SIZE_CLASS$3[size], radius === "full" ? PADDING_FULL_CLASS$1[size] : PADDING_CLASS$2[size], variant === "underlined" ? UNDERLINED_INNER_CLASS$1[size] : "", RADIUS_CLASS$6[radius], VARIANT_CLASS$3[variant], FIELD_FOCUS_CLASS$3, FIELD_SHADOW_CLASS$2,
|
|
1738
2261
|
// Incondicional, como el `inputWrapper` de v2: la caja del campo llena
|
|
1739
2262
|
// siempre su raíz, y es la raíz la que responde a `fullWidth` y al ancho que
|
|
1740
2263
|
// pase el consumidor.
|
|
1741
|
-
"w-full",
|
|
2264
|
+
"w-full",
|
|
2265
|
+
// v3 fusiona `innerWrapper` e `inputWrapper` de v2 en este único elemento.
|
|
2266
|
+
classNames?.innerWrapper, classNames?.inputWrapper), children: [startContent && (jsxRuntime.jsx(react.InputGroup.Prefix, { className: "border-none bg-transparent px-0", children: startContent })), jsxRuntime.jsx(react.InputGroup.Input, { ...rest, ref: setInputRef, onKeyDown: handleKeyDown, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, onChange: handleChange, className: react.cn("bg-transparent p-0", SIZE_TEXT_CLASS$2[size], isInvalid ? INVALID_TEXT_CLASS$2 : "", startContent ? "ps-1.5" : "", endContent || showClear ? "pe-1.5" : "", classNames?.input) }), showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn("h-4 w-4 min-h-0 p-0.5 mr-1", classNames?.clearButton), onPress: handleClear })), endContent && (jsxRuntime.jsx(react.InputGroup.Suffix, { className: "border-none bg-transparent px-0", children: endContent }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.errorMessage), children: errorMessage }))] }));
|
|
1742
2267
|
});
|
|
1743
2268
|
Input.displayName = "Input";
|
|
1744
2269
|
|
|
@@ -1845,12 +2370,36 @@ const RADIUS_CLASS$5 = {
|
|
|
1845
2370
|
// v3 no tiene prop `shadow`: su Card siempre usa `shadow-surface`. Los valores de v2 se
|
|
1846
2371
|
// reproducen con los tokens propios `--aurora-shadow-*` (emitidos en F1, mismo valor exacto que
|
|
1847
2372
|
// generaba el plugin `heroui()` de v2 — ver findings/tabla-mapeo-tokens.md §2).
|
|
2373
|
+
//
|
|
2374
|
+
// ⚠️ El prefijo `shadow:` dentro del corchete NO es decorativo. Sin él, tailwind-merge 3 clasifica
|
|
2375
|
+
// `shadow-[var(--x)]` como COLOR de sombra (grupo `shadow-color`), no como sombra: medido con el
|
|
2376
|
+
// `cn` real, `cn("shadow-[var(--aurora-shadow-small)]", "shadow-2xl")` conserva LAS DOS, y en el
|
|
2377
|
+
// CSS de Tailwind 4 la arbitraria se emite después de `shadow-2xl` (orden de candidato), así que
|
|
2378
|
+
// ganaba la nuestra — el `shadow-2xl` que pide `whatsapp-setup-modal` no se veía. Con la etiqueta
|
|
2379
|
+
// `shadow:` tailwind-merge la reconoce como sombra y la retira cuando la app pasa la suya; el CSS
|
|
2380
|
+
// que emite Tailwind 4 es idéntico (`--tw-shadow: var(--aurora-shadow-small)`, comprobado con
|
|
2381
|
+
// `@tailwindcss/node`).
|
|
1848
2382
|
const SHADOW_CLASS = {
|
|
1849
2383
|
none: "shadow-none",
|
|
1850
|
-
sm: "shadow-[var(--aurora-shadow-small)]",
|
|
1851
|
-
md: "shadow-[var(--aurora-shadow-medium)]",
|
|
1852
|
-
lg: "shadow-[var(--aurora-shadow-large)]",
|
|
1853
|
-
};
|
|
2384
|
+
sm: "shadow-[shadow:var(--aurora-shadow-small)]",
|
|
2385
|
+
md: "shadow-[shadow:var(--aurora-shadow-medium)]",
|
|
2386
|
+
lg: "shadow-[shadow:var(--aurora-shadow-large)]",
|
|
2387
|
+
};
|
|
2388
|
+
// Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
|
|
2389
|
+
// traducidos a los que existen en Tailwind 4 + Aurora:
|
|
2390
|
+
// isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
|
|
2391
|
+
// tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
|
|
2392
|
+
// marcaba `data-pressed` en un botón nativo)
|
|
2393
|
+
// disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
|
|
2394
|
+
// foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
|
|
2395
|
+
// isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
|
|
2396
|
+
// `data-hover` — trampa 3 de la skill `auraui-components`)
|
|
2397
|
+
// isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
|
|
2398
|
+
// fullWidth → `w-full`
|
|
2399
|
+
const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-focus focus-visible:outline-offset-2 [-webkit-tap-highlight-color:transparent]";
|
|
2400
|
+
const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
|
|
2401
|
+
const HOVERABLE_CLASS = "hover:bg-content2";
|
|
2402
|
+
const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
1854
2403
|
/**
|
|
1855
2404
|
* Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
|
|
1856
2405
|
*
|
|
@@ -1864,18 +2413,50 @@ const SHADOW_CLASS = {
|
|
|
1864
2413
|
* - Shadow: none, sm, md, lg (configurable, reimplementado vía className — v3 no tiene la prop)
|
|
1865
2414
|
* - Radius: sm (por defecto, configurable, reimplementado vía className — v3 no tiene la prop)
|
|
1866
2415
|
* - Padding configurable (sm, md, lg)
|
|
2416
|
+
* - `className` de la app AL FINAL y fusionado con `cn` (tailwind-merge), como el `slots.base({
|
|
2417
|
+
* class })` de v2: un `p-0`/`rounded-xl`/`shadow-2xl` de la app retira el default de la fachada
|
|
2418
|
+
* en vez de competir con él por orden de CSS.
|
|
2419
|
+
* - `ref` y TODAS las props DOM (`style`, `onMouse*`, `aria-*`, `data-*`…) llegan al nodo raíz,
|
|
2420
|
+
* como en v2 (`HTMLHeroUIProps<"div">`).
|
|
2421
|
+
* - API v2 de card pulsable: `isPressable`/`onPress`/`disableAnimation`/`disableRipple`, más
|
|
2422
|
+
* `isHoverable`/`isDisabled`/`fullWidth`.
|
|
2423
|
+
*
|
|
2424
|
+
* `ref` se reenvía con `forwardRef` (patrón de las fachadas de campo del repo, ver
|
|
2425
|
+
* `Input.tsx`) y apunta al nodo raíz: el `<div>` o, con `isPressable`, el `<button>`.
|
|
1867
2426
|
*/
|
|
1868
|
-
const Card = ({ children, className
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
2427
|
+
const Card = $8WNJc$react.forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
|
|
2428
|
+
// Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
|
|
2429
|
+
const sinRipple = disableRipple || disableAnimation;
|
|
2430
|
+
const { ondas, lanzar, quitar } = useRipple();
|
|
2431
|
+
const handleClick = $8WNJc$react.useCallback((event) => {
|
|
2432
|
+
onClick?.(event);
|
|
2433
|
+
if (!isPressable || isDisabled) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const pressEvent = pressEventFromClick(event);
|
|
2437
|
+
onPress?.(pressEvent);
|
|
2438
|
+
if (!sinRipple) {
|
|
2439
|
+
lanzar(pressEvent);
|
|
2440
|
+
}
|
|
2441
|
+
}, [onClick, isPressable, isDisabled, onPress, sinRipple, lanzar]);
|
|
2442
|
+
const combinedClassName = react.cn(paddingClasses[padding], RADIUS_CLASS$5[radius], SHADOW_CLASS[shadow], fullWidth ? "w-full" : "", isPressable ? PRESSABLE_CLASS : "", isPressable && !disableAnimation ? PRESS_ANIMATION_CLASS : "", isHoverable ? HOVERABLE_CLASS : "", isDisabled ? DISABLED_CLASS : "",
|
|
2443
|
+
// El `className` de la app, último: gana por fusión (ver JSDoc).
|
|
2444
|
+
className);
|
|
2445
|
+
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable` (`Component = as || (isPressable
|
|
2446
|
+
// ? "button" : "div")`). v3 lo permite con `render` (DOMRenderProps: basta con conectar
|
|
2447
|
+
// el `ref`, que ya viene en `domProps`), así que se conserva el `CardContext` del
|
|
2448
|
+
// compound y los `Card.Header`/`CardBody` de dentro siguen funcionando.
|
|
2449
|
+
const pressableRender = isPressable
|
|
2450
|
+
? (domProps) => (jsxRuntime.jsx("button", { ...domProps, type: "button",
|
|
2451
|
+
// v2 (`useCard`): `{ ...buttonProps, ...focusProps, role: "button" }` y
|
|
2452
|
+
// `tabIndex: isPressable ? 0 : -1`. En un `<button>` nativo el rol ya es
|
|
2453
|
+
// `button` implícito (biome marca el explícito como redundante); el
|
|
2454
|
+
// `tabIndex` se escribe igual que v2.
|
|
2455
|
+
tabIndex: isDisabled ? -1 : 0, disabled: isDisabled }))
|
|
2456
|
+
: render;
|
|
2457
|
+
return (jsxRuntime.jsxs(react.Card, { ...props, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: pressableRender, children: [children, isPressable && ondas.length > 0 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
|
|
2458
|
+
});
|
|
2459
|
+
Card.displayName = "Card";
|
|
1879
2460
|
|
|
1880
2461
|
const H2 = ({ children, className, ...props }) => {
|
|
1881
2462
|
return (jsxRuntime.jsx("h2", { className: react.cn("text-lg font-semibold", className), ...props, children: children }));
|
|
@@ -2113,10 +2694,221 @@ function withCollectionItemIds(children) {
|
|
|
2113
2694
|
return Object.keys(patch).length > 0 ? $8WNJc$react.cloneElement(element, patch) : element;
|
|
2114
2695
|
}
|
|
2115
2696
|
|
|
2697
|
+
/**
|
|
2698
|
+
* Repone en los items y secciones de una colección de v3 la API de CONTENIDO de v2 que las apps
|
|
2699
|
+
* siguen escribiendo en `SelectItem`/`AutocompleteItem`/`SelectSection` (TECH-2459, hallazgo
|
|
2700
|
+
* beweossmbs 23-sep).
|
|
2701
|
+
*
|
|
2702
|
+
* QUÉ HACÍA v2 (`@heroui/listbox@2.3.31`, `dist/chunk-2TBDFBA3.mjs`, leído del tarball):
|
|
2703
|
+
*
|
|
2704
|
+
* <li …getItemProps()>
|
|
2705
|
+
* {startContent && <span data-slot="startContent">…}
|
|
2706
|
+
* {description
|
|
2707
|
+
* ? <div wrapper><span title>{rendered}</span><span description>{description}</span></div>
|
|
2708
|
+
* : <span title>{rendered}</span>}
|
|
2709
|
+
* {selectedIcon}
|
|
2710
|
+
* {endContent && <span data-slot="endContent">…}
|
|
2711
|
+
* </li>
|
|
2712
|
+
*
|
|
2713
|
+
* con los slots del `tv` (`@heroui/theme@2.4.15`, `dist/components/listbox.js:100-139`):
|
|
2714
|
+
* `wrapper: "w-full flex flex-col items-start justify-center"`, `title: "flex-1 text-small
|
|
2715
|
+
* font-normal"`, `description: "w-full text-tiny text-foreground-500 …"`. La sección
|
|
2716
|
+
* (`chunk-Q2EVG7SO.mjs`) pintaba la cabecera `heading: "pl-1 text-tiny text-foreground-500"` y,
|
|
2717
|
+
* con `showDivider` (default `false`), un `Divider` con `mt-2` al final.
|
|
2718
|
+
*
|
|
2719
|
+
* QUÉ HACE v3. `ListBox.Item` no tiene NINGUNA de esas props (la guía oficial: «Place icons
|
|
2720
|
+
* manually in item content», «Use `Description` component»; y `title` de la sección → `Header`).
|
|
2721
|
+
* Pasadas tal cual se descartan en silencio: el `filterDOMProps` de react-aria no las deja llegar
|
|
2722
|
+
* al DOM y el item pinta solo su texto.
|
|
2723
|
+
*
|
|
2724
|
+
* POR QUÉ AQUÍ Y NO EN EL SHIM. `SelectItem`/`AutocompleteItem`/`SelectSection` tienen que seguir
|
|
2725
|
+
* siendo el MISMO objeto que `ListBoxItem`/`ListBoxSection`: el normalizador de ids
|
|
2726
|
+
* (`utils/collection-items.ts`) y el indicador (`with-item-indicator.tsx`) reconocen los items por
|
|
2727
|
+
* identidad de `element.type`, y un envoltorio los volvería invisibles (los items dentro de una
|
|
2728
|
+
* sección perderían su `id` derivado de la `key`, que es el defecto nº 1 de la migración). Así que
|
|
2729
|
+
* el shim solo TIPA las props v2 y esta función, llamada desde la fachada que tiene los items en
|
|
2730
|
+
* la mano, las traduce a la anatomía de v3.
|
|
2731
|
+
*
|
|
2732
|
+
* NO-OP cuando no hay nada que traducir: un item sin `description`/`startContent`/`endContent`/
|
|
2733
|
+
* `classNames` se devuelve tal cual (mismo objeto), así que los 150+ items planos de las apps no
|
|
2734
|
+
* cambian ni un nodo.
|
|
2735
|
+
*/
|
|
2736
|
+
/** Marca de lo que v2 NO pintaba en el disparador: allí solo iba el `textValue` del item. */
|
|
2737
|
+
const ITEM_EXTRA_ATTR = "data-aura-item-extra";
|
|
2738
|
+
/**
|
|
2739
|
+
* Clase para `Select.Value`: esconde en el disparador lo que v2 solo pintaba en la lista.
|
|
2740
|
+
*
|
|
2741
|
+
* `SelectValue` de react-aria repinta los CHILDREN del item elegido dentro del disparador
|
|
2742
|
+
* (`react-aria-components/dist/private/Select.mjs:226-262`). v2 pintaba ahí solo el `textValue`
|
|
2743
|
+
* (`@heroui/select@2.4.33`, `dist/chunk-BJWIL32D.mjs`: `state.selectedItems.map(i =>
|
|
2744
|
+
* i.textValue).join(", ")`), así que el icono lateral no debe colarse junto al valor. La
|
|
2745
|
+
* descripción se esconde SOLA: `Description` de v3 devuelve `null` sin el slot de texto
|
|
2746
|
+
* `description`, y `SelectValue` resetea ese contexto (`TextContext.Provider value={undefined}`,
|
|
2747
|
+
* misma línea) — el mismo mecanismo que usa v3 para esconder su indicador ahí (`select.css:121`).
|
|
2748
|
+
*/
|
|
2749
|
+
const HIDE_ITEM_EXTRAS_CLASS = `[&_[${ITEM_EXTRA_ATTR}]]:hidden`;
|
|
2750
|
+
// Literales de v2 portados a la pieza de v3, sin clases v2 muertas (`text-small`/`text-tiny` no
|
|
2751
|
+
// existen sin el plugin `heroui()` y `cn` las borraría). El tamaño del título NO se fija: el
|
|
2752
|
+
// desplegable de v3 ya es `text-sm` (= el `text-small` de v2) y dentro del disparador tiene que
|
|
2753
|
+
// heredar el tamaño del campo (`lg` = 16px).
|
|
2754
|
+
const ITEM_WRAPPER_CLASS = "flex w-full min-w-0 flex-col items-start justify-center";
|
|
2755
|
+
const ITEM_TITLE_CLASS$1 = "min-w-0 flex-1 font-normal";
|
|
2756
|
+
// `Description` de v3 ya trae `text-xs text-muted` (`description.css`), y `text-muted` es el mismo
|
|
2757
|
+
// zinc-500/400 que el `text-foreground-500` de v2 (medido en `Dropdown.tsx`, bloque de la cabecera).
|
|
2758
|
+
const ITEM_DESCRIPTION_CLASS = "w-full";
|
|
2759
|
+
const ITEM_SIDE_CLASS = "flex shrink-0 items-center";
|
|
2760
|
+
// Cabecera de sección: mismo único ajuste que la fachada `Dropdown` (ver `SECTION_HEADING_CLASS`
|
|
2761
|
+
// en `Dropdown.tsx`: tamaño y color de `.header` ya coinciden con v2; el peso no).
|
|
2762
|
+
const SECTION_HEADING_CLASS$1 = "font-normal";
|
|
2763
|
+
// `mt-2` del slot `divider` de v2; el ancho y el color los pone el `Separator` de v3.
|
|
2764
|
+
const SECTION_DIVIDER_CLASS = "mt-2";
|
|
2765
|
+
/** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/""/false no cuentan. */
|
|
2766
|
+
function hasValue$2(node) {
|
|
2767
|
+
return node !== null && node !== undefined && node !== "" && node !== false;
|
|
2768
|
+
}
|
|
2769
|
+
function translateItem(element) {
|
|
2770
|
+
const { children, className, description, startContent, endContent, classNames, } = element.props;
|
|
2771
|
+
const hasDescription = hasValue$2(description);
|
|
2772
|
+
const hasStart = hasValue$2(startContent);
|
|
2773
|
+
const hasEnd = hasValue$2(endContent);
|
|
2774
|
+
if (!(hasDescription || hasStart || hasEnd || classNames)) {
|
|
2775
|
+
return element;
|
|
2776
|
+
}
|
|
2777
|
+
// Solo `classNames.base` (el caso de `timezone-search-select`): nada que recomponer, basta
|
|
2778
|
+
// fusionar la clase en la raíz. Y children como función (render props de react-aria): no se
|
|
2779
|
+
// puede componer alrededor sin romper la firma, así que se aplican solo las clases de la raíz.
|
|
2780
|
+
const onlyBase = !(hasDescription ||
|
|
2781
|
+
hasStart ||
|
|
2782
|
+
hasEnd ||
|
|
2783
|
+
classNames?.title ||
|
|
2784
|
+
classNames?.wrapper ||
|
|
2785
|
+
classNames?.description);
|
|
2786
|
+
if (onlyBase || typeof children === "function") {
|
|
2787
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2788
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2789
|
+
description: undefined,
|
|
2790
|
+
startContent: undefined,
|
|
2791
|
+
endContent: undefined,
|
|
2792
|
+
classNames: undefined,
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
const title = (jsxRuntime.jsx("span", { className: react.cn(ITEM_TITLE_CLASS$1, classNames?.title), children: children }));
|
|
2796
|
+
const next = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasStart && (jsxRuntime.jsx("span", { "data-slot": "start-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: startContent })), hasDescription ? (jsxRuntime.jsxs("div", { className: react.cn(ITEM_WRAPPER_CLASS, classNames?.wrapper), children: [title, jsxRuntime.jsx(react.Description, { className: react.cn(ITEM_DESCRIPTION_CLASS, classNames?.description), children: description })] })) : (title), hasEnd && (jsxRuntime.jsx("span", { "data-slot": "end-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: endContent }))] }));
|
|
2797
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2798
|
+
// v2: `cn(classNames?.base, className)` (`use-listbox-item.mjs`), así que `className` gana.
|
|
2799
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2800
|
+
description: undefined,
|
|
2801
|
+
startContent: undefined,
|
|
2802
|
+
endContent: undefined,
|
|
2803
|
+
classNames: undefined,
|
|
2804
|
+
children: next,
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
function translateSection(element) {
|
|
2808
|
+
const { children, className, title, showDivider, classNames } = element.props;
|
|
2809
|
+
const inner = withV2ItemContent(children);
|
|
2810
|
+
const hasTitle = hasValue$2(title);
|
|
2811
|
+
if (!(hasTitle || showDivider || classNames) && inner === children) {
|
|
2812
|
+
return element;
|
|
2813
|
+
}
|
|
2814
|
+
// ⚠️ Los hijos van como ARGUMENTOS SUELTOS de `cloneElement`, no envueltos en un Fragment:
|
|
2815
|
+
// `withItemIndicator` (que corre después) y `withCollectionItemIds` bajan a los items de una
|
|
2816
|
+
// sección leyendo `props.children` como array o elemento, y un Fragment los escondería — los
|
|
2817
|
+
// items de la sección se quedarían sin checkmark.
|
|
2818
|
+
const parts = [];
|
|
2819
|
+
if (hasTitle) {
|
|
2820
|
+
parts.push(jsxRuntime.jsx(react.Header, { className: react.cn(SECTION_HEADING_CLASS$1, classNames?.heading), children: title }, "aura-section-header"));
|
|
2821
|
+
}
|
|
2822
|
+
parts.push(...(Array.isArray(inner) ? inner : [inner]));
|
|
2823
|
+
if (showDivider) {
|
|
2824
|
+
parts.push(jsxRuntime.jsx(react.Separator, { className: react.cn(SECTION_DIVIDER_CLASS, classNames?.divider) }, "aura-section-divider"));
|
|
2825
|
+
}
|
|
2826
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2827
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2828
|
+
// `title` en v3 es el atributo global de HTML: sin retirarlo sale como *tooltip*
|
|
2829
|
+
// nativo sobre toda la sección (el mismo motivo por el que v2 lo quitaba del spread,
|
|
2830
|
+
// `chunk-Q2EVG7SO.mjs`: «removed title from props to avoid browsers showing a tooltip»).
|
|
2831
|
+
title: undefined,
|
|
2832
|
+
showDivider: undefined,
|
|
2833
|
+
classNames: undefined,
|
|
2834
|
+
}, ...parts);
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Traduce la API de contenido v2 de cada item/sección de la colección. Va DESPUÉS de
|
|
2838
|
+
* `withCollectionItemIds` (que deriva el `textValue` del texto del item: si la descripción ya
|
|
2839
|
+
* estuviera dentro, la sumaría al texto de búsqueda y del disparador) y ANTES de
|
|
2840
|
+
* `withItemIndicator`.
|
|
2841
|
+
*
|
|
2842
|
+
* Acepta también children como FUNCIÓN (colección dinámica con `items`, la forma de 11 de los 11
|
|
2843
|
+
* `AuraAutocomplete` de beweossmbs): se envuelve la función para traducir lo que devuelva.
|
|
2844
|
+
*/
|
|
2845
|
+
function withV2ItemContent(children) {
|
|
2846
|
+
if (typeof children === "function") {
|
|
2847
|
+
const render = children;
|
|
2848
|
+
return ((item) => withV2ItemContent(render(item)));
|
|
2849
|
+
}
|
|
2850
|
+
if (Array.isArray(children)) {
|
|
2851
|
+
return children.map((child) => withV2ItemContent(child));
|
|
2852
|
+
}
|
|
2853
|
+
if (!$8WNJc$react.isValidElement(children)) {
|
|
2854
|
+
return children;
|
|
2855
|
+
}
|
|
2856
|
+
if (children.type === react.ListBoxSection) {
|
|
2857
|
+
return translateSection(children);
|
|
2858
|
+
}
|
|
2859
|
+
if (children.type === react.ListBoxItem) {
|
|
2860
|
+
return translateItem(children);
|
|
2861
|
+
}
|
|
2862
|
+
return children;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2116
2865
|
/** Traducciones por defecto en español (mismo literal que la ✕ de la fachada `Input`). */
|
|
2117
2866
|
const DEFAULT_TRANSLATIONS$f = {
|
|
2118
2867
|
clearButtonAriaLabel: "Limpiar",
|
|
2868
|
+
emptyContent: "No se encontraron resultados",
|
|
2119
2869
|
};
|
|
2870
|
+
/*
|
|
2871
|
+
* La lista vacía (TECH-2459, hallazgo beweossmbs 23-sep: 6 `AuraAutocomplete` pasan
|
|
2872
|
+
* `listboxProps.emptyContent` y ninguno se veía).
|
|
2873
|
+
*
|
|
2874
|
+
* v2 (`@heroui/autocomplete@2.3.34`, `dist/chunk-J7SBPTP2.mjs`, leído del tarball):
|
|
2875
|
+
*
|
|
2876
|
+
* allowsEmptyCollection = true, (l.36, default)
|
|
2877
|
+
* listboxProps: mergeProps({
|
|
2878
|
+
* hideEmptyContent: allowsCustomValue && !listboxProps?.emptyContent, (l.160)
|
|
2879
|
+
* emptyContent: "No results found.", (l.161)
|
|
2880
|
+
* }, listboxProps)
|
|
2881
|
+
*
|
|
2882
|
+
* O sea: abría la lista aunque el filtro la dejara vacía, y pintaba un texto — salvo con
|
|
2883
|
+
* `allowsCustomValue` sin texto propio (campo libre: "sin resultados" no es un error ahí). v3 invierte
|
|
2884
|
+
* el default (`allowsEmptyCollection` a `false`: la lista se CIERRA al quedarse vacía) y su `ListBox`
|
|
2885
|
+
* no pinta nada sin `renderEmptyState`. Se reponen las dos cosas con la misma regla de v2; un
|
|
2886
|
+
* `allowsEmptyCollection` explícito del consumidor sigue mandando. El texto por defecto va
|
|
2887
|
+
* traducido (patrón i18n del repo), no el inglés de v2.
|
|
2888
|
+
*
|
|
2889
|
+
* Medidas: las del estado vacío de `Select` (hueco del alto de una opción de v3, gris
|
|
2890
|
+
* `text-foreground-400` del slot `emptyContent` de v2), con el `px-2` del item de v3 porque este
|
|
2891
|
+
* popover no pisa el padding de los items como sí hace el del Select.
|
|
2892
|
+
*/
|
|
2893
|
+
const EMPTY_CONTENT_CLASS$1 = "flex min-h-9 w-full items-center px-2 py-1.5 text-start text-foreground-400";
|
|
2894
|
+
/*
|
|
2895
|
+
* El mensaje de error (v2 `errorMessage`, 3 usos en beweossmbs que hoy no se veían). v2 lo pintaba
|
|
2896
|
+
* bajo el campo en el `helperWrapper` del `Input` (`p-1` + `text-tiny text-danger`), solo con el
|
|
2897
|
+
* campo inválido. `FieldError` de v3 ya trae `px-1 text-xs text-danger` (`field-error.css`) y se
|
|
2898
|
+
* muestra solo cuando react-aria da el campo por inválido; el `text-left` es por si el campo vive
|
|
2899
|
+
* en un contenedor centrado. El hueco lo da el `gap-2` del stack, en vez de los 4px del `p-1`: la
|
|
2900
|
+
* diferencia es de 4px y se deja en el ritmo de v3 (§9.1).
|
|
2901
|
+
*/
|
|
2902
|
+
const ERROR_MESSAGE_CLASS = "text-left";
|
|
2903
|
+
/*
|
|
2904
|
+
* `isLoading` (v2): el `selectorButton` pasaba a `isLoading` y su spinner ocupaba el sitio del
|
|
2905
|
+
* chevron (`chunk-J7SBPTP2.mjs:168`). ⚠️ En la fachada de v2 eso no se veía con la lupa puesta,
|
|
2906
|
+
* porque esa fachada ocultaba el `selectorButton` cuando había icono (`selectorButton: "!hidden"`,
|
|
2907
|
+
* `git show b39d28f:src/components/autocomplete/AutoComplete.tsx`): el spinner se escondía con él.
|
|
2908
|
+
* Aquí se muestra SIEMPRE, en el sitio del extremo que haya (icono o chevron), porque una prop que
|
|
2909
|
+
* no pinta nada con la configuración por defecto es justo lo que la app reporta como roto.
|
|
2910
|
+
*/
|
|
2911
|
+
const SPINNER = (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "autocomplete-spinner", size: "sm", color: "current", className: "text-default-400" }));
|
|
2120
2912
|
/**
|
|
2121
2913
|
* Clases del wrapper del icono: lo mete DENTRO del recuadro del campo.
|
|
2122
2914
|
*
|
|
@@ -2417,11 +3209,20 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
|
|
|
2417
3209
|
// limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
|
|
2418
3210
|
// pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
|
|
2419
3211
|
// controlado con `!== undefined`).
|
|
2420
|
-
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, ...props }) => {
|
|
3212
|
+
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, ...props }) => {
|
|
2421
3213
|
const generatedId = $8WNJc$react.useId();
|
|
2422
3214
|
const autoId = id || generatedId;
|
|
2423
|
-
|
|
3215
|
+
// Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
|
|
3216
|
+
// icono, así que todas las decisiones de hueco de abajo lo tratan como tal.
|
|
3217
|
+
const resolvedIcon = isLoading
|
|
3218
|
+
? SPINNER
|
|
3219
|
+
: icon === undefined
|
|
3220
|
+
? DEFAULT_ICON
|
|
3221
|
+
: icon;
|
|
2424
3222
|
const t = { ...DEFAULT_TRANSLATIONS$f, ...translations };
|
|
3223
|
+
// La regla de v2 para la lista vacía (ver `EMPTY_CONTENT_CLASS`).
|
|
3224
|
+
const hideEmptyContent = Boolean(allowsCustomValue) && listboxProps?.emptyContent == null;
|
|
3225
|
+
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
2425
3226
|
/** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
|
|
2426
3227
|
const inputRef = $8WNJc$react.useRef(null);
|
|
2427
3228
|
// `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
|
|
@@ -2457,7 +3258,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
2457
3258
|
onInputChange?.("");
|
|
2458
3259
|
inputRef.current?.focus();
|
|
2459
3260
|
};
|
|
2460
|
-
return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: onInputChange, onSelectionChange: onSelectionChange, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
|
|
3261
|
+
return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: onInputChange, onSelectionChange: onSelectionChange, isInvalid: isInvalid, allowsCustomValue: allowsCustomValue, allowsEmptyCollection: allowsEmptyCollection ?? !hideEmptyContent, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
|
|
2461
3262
|
SIZE_CLASS$2[size],
|
|
2462
3263
|
PADDING_CLASS$1[size],
|
|
2463
3264
|
variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
|
|
@@ -2520,7 +3321,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
2520
3321
|
isDisabled ? ICON_DISABLED_CLASS : "",
|
|
2521
3322
|
]
|
|
2522
3323
|
.filter(Boolean)
|
|
2523
|
-
.join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), jsxRuntime.jsx(react.ComboBox.Popover, { children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first", className: OPCIONES_CLASS,
|
|
3324
|
+
.join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsxRuntime.jsx(react.FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsxRuntime.jsx(react.ComboBox.Popover, { children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first", className: OPCIONES_CLASS, renderEmptyState: hideEmptyContent
|
|
3325
|
+
? undefined
|
|
3326
|
+
: () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
|
|
3327
|
+
// El normalizador de `utils/` está tipado solo para `ReactNode`; con una
|
|
3328
|
+
// función la devuelve tal cual (`isValidElement` es falso), así que el cast
|
|
3329
|
+
// solo alinea el tipo con lo que ya pasa en ejecución.
|
|
3330
|
+
withCollectionItemIds(children)) }) })] }));
|
|
2524
3331
|
};
|
|
2525
3332
|
|
|
2526
3333
|
/**
|
|
@@ -8279,6 +9086,32 @@ const FIELD_BORDER_CLASS = "border-2 border-default-200 bg-background focus-with
|
|
|
8279
9086
|
*/
|
|
8280
9087
|
const ROOT_INLINE_LABEL_CLASS = "flex-row items-center gap-x-2 flex-nowrap";
|
|
8281
9088
|
const LABEL_CLASS$2 = "text-xs font-normal text-default-600";
|
|
9089
|
+
/*
|
|
9090
|
+
* TECH-2459 · hallazgo widgets 23-sep (`findings/app-widgets-timeinput-label-inline.md`) — la
|
|
9091
|
+
* etiqueta ENCIMA, para el nombre `TimeInput` a secas.
|
|
9092
|
+
*
|
|
9093
|
+
* En v2 (0.6.85, `dist/index.esm.js`) había DOS componentes bajo dos nombres: la fachada de arriba
|
|
9094
|
+
* (con `labelPlacement: "outside-left"`) se exportaba SOLO como `TimeInputComponent`
|
|
9095
|
+
* (`export { …, TimeInput as TimeInputComponent, … }`), y `TimeInput` a secas llegaba por el
|
|
9096
|
+
* `export * from '@heroui/react'`: el crudo de HeroUI 2.7.11, con la etiqueta encima. v1 había
|
|
9097
|
+
* apuntado los dos nombres a la fachada en línea, y el alta de agenda de widgets (que importa
|
|
9098
|
+
* `TimeInput` a secas junto a un `DatePicker`) quedó con «Hora» a la izquierda y «Fecha» encima.
|
|
9099
|
+
*
|
|
9100
|
+
* Las clases son las MISMAS que la fachada de `DatePicker` pone en su raíz y en su `Label`
|
|
9101
|
+
* (`DatePicker.tsx`: `"flex flex-col gap-1"` y `"text-default-600 text-xs font-medium"`), para que
|
|
9102
|
+
* en un formulario las dos etiquetas y los dos campos caigan en la misma línea. El grupo ya mide lo
|
|
9103
|
+
* mismo que el `size="md"` por defecto del `DatePicker` (`h-10 min-h-10`).
|
|
9104
|
+
*/
|
|
9105
|
+
const ROOT_OUTSIDE_LABEL_CLASS = "flex flex-col gap-1";
|
|
9106
|
+
const OUTSIDE_LABEL_CLASS = "text-default-600 text-xs font-medium";
|
|
9107
|
+
const ROOT_CLASS = {
|
|
9108
|
+
outside: ROOT_OUTSIDE_LABEL_CLASS,
|
|
9109
|
+
"outside-left": ROOT_INLINE_LABEL_CLASS,
|
|
9110
|
+
};
|
|
9111
|
+
const LABEL_CLASS_BY_PLACEMENT = {
|
|
9112
|
+
outside: OUTSIDE_LABEL_CLASS,
|
|
9113
|
+
"outside-left": LABEL_CLASS$2,
|
|
9114
|
+
};
|
|
8282
9115
|
const GROUP_SIZE_CLASS = "flex-1 h-10 min-h-10 shadow-xs";
|
|
8283
9116
|
/*
|
|
8284
9117
|
* TECH-2459 · F5 · R3 — Avance de los segmentos: se reproduce el de v2 MEDIDO en el PNG.
|
|
@@ -8338,31 +9171,64 @@ const DAY_PERIOD_SEGMENT_CLASS = "ms-0.5";
|
|
|
8338
9171
|
* el texto (`rgb(243,18,96)`). Deuda a reportar aparte, no de este arreglo.
|
|
8339
9172
|
*/
|
|
8340
9173
|
const FIELD_FOCUS_CLASS$2 = "ring-0 ring-offset-0";
|
|
9174
|
+
/*
|
|
9175
|
+
* TECH-2459 · hallazgo beweossmbs 23-sep — `startContent`/`endContent` de v2, repuestas DENTRO de
|
|
9176
|
+
* la caja del campo.
|
|
9177
|
+
*
|
|
9178
|
+
* v2 (`@heroui/date-input@2.3.32`, `dist/time-input.js:319-329`) las pintaba en el `innerWrapper`,
|
|
9179
|
+
* dentro del `inputWrapper` (la caja con borde): `startContent`, los segmentos y `endContent`, en
|
|
9180
|
+
* ese orden. El `tv` de v2 (`@heroui/theme@2.4.15`, `date-input.js`) da a ese envoltorio
|
|
9181
|
+
* `flex items-center … w-full gap-x-2` y al `inputWrapper` `px-3 gap-3`. v1 no aceptaba las
|
|
9182
|
+
* props: el alta de citas de beweossmbs (`datetime-section.component.tsx`) pone ahí las flechas ±
|
|
9183
|
+
* de la rejilla de horas y desaparecían.
|
|
9184
|
+
*
|
|
9185
|
+
* En v3 el sitio equivalente son `TimeField.Prefix`/`TimeField.Suffix` (`DateInputGroupPrefix`/
|
|
9186
|
+
* `Suffix`), hijos del mismo `TimeField.Group`. Sus márgenes ya son los de v2 (`ms-3`/`me-3` = el
|
|
9187
|
+
* `px-3` del wrapper, y el input pasa a `pe-2`/`ps-2` = el `gap-x-2`), así que solo se añade una
|
|
9188
|
+
* cosa: `pointer-events-auto`. `.date-input-group__prefix/__suffix` de `@heroui/styles` los
|
|
9189
|
+
* declara `pointer-events-none` (pensados para un icono decorativo) y solo `.date-picker` los
|
|
9190
|
+
* reactiva; en un `TimeField` un botón puesto ahí no recibiría el clic. v2 no tenía esa regla.
|
|
9191
|
+
*/
|
|
9192
|
+
const CONTENT_SLOT_CLASS = "pointer-events-auto";
|
|
8341
9193
|
/**
|
|
8342
|
-
*
|
|
8343
|
-
*
|
|
8344
|
-
*
|
|
8345
|
-
* que no cambiaron (son de React Aria, no de HeroUI).
|
|
8346
|
-
*
|
|
8347
|
-
* La etiqueta va en línea a la izquierda del campo, como en v2 (ver el bloque de arriba). v2 no
|
|
8348
|
-
* exponía `labelPlacement` en su API pública (`Omit<HeroUITimeInputProps, "labelPlacement">`), así
|
|
8349
|
-
* que aquí tampoco: la colocación es la del diseño de BeweOS y no se configura.
|
|
9194
|
+
* Implementación común de los dos nombres exportados. La única diferencia entre `TimeInput` y
|
|
9195
|
+
* `TimeInputComponent` es el valor por defecto de `labelPlacement`; una `labelPlacement` explícita
|
|
9196
|
+
* gana siempre.
|
|
8350
9197
|
*/
|
|
8351
|
-
function
|
|
9198
|
+
function TimeInputBase({ label, id, className, labelPlacement,
|
|
8352
9199
|
// v2 lo forzaba a `true` en su propio hook (`@heroui/date-input@2.3.24` →
|
|
8353
9200
|
// `dist/use-time-input.js:58`, `shouldForceLeadingZeros = true`), así que la referencia v2
|
|
8354
9201
|
// muestra "09:00 AM"; React Aria por sí solo deja "9:00 AM". Es una prop de React Aria que v3
|
|
8355
9202
|
// sigue aceptando (`AriaTimeFieldProps`), solo cambió el default.
|
|
8356
|
-
shouldForceLeadingZeros = true, ...props }) {
|
|
9203
|
+
shouldForceLeadingZeros = true, startContent, endContent, ...props }) {
|
|
8357
9204
|
const generatedId = $8WNJc$react.useId();
|
|
8358
9205
|
const autoId = id || generatedId;
|
|
8359
9206
|
return (jsxRuntime.jsxs(react.TimeField, { id: autoId,
|
|
8360
9207
|
// `cn` (tailwind-merge) y no una concatenación: el consumidor tiene que poder pisar la
|
|
8361
9208
|
// colocación y el ancho (`ScheduleRow` pasa `w-full`). Todas las clases de aquí son
|
|
8362
9209
|
// vocabulario v3 válido, así que tailwind-merge las resuelve bien.
|
|
8363
|
-
className: react.cn(
|
|
8364
|
-
|
|
8365
|
-
|
|
9210
|
+
className: react.cn(ROOT_CLASS[labelPlacement], className), shouldForceLeadingZeros: shouldForceLeadingZeros, ...props, children: [label && (jsxRuntime.jsx(react.Label, { className: LABEL_CLASS_BY_PLACEMENT[labelPlacement], children: label })), jsxRuntime.jsxs(react.TimeField.Group, { className: react.cn(FIELD_BORDER_CLASS, GROUP_SIZE_CLASS, FIELD_FOCUS_CLASS$2), children: [startContent !== undefined && startContent !== null && (jsxRuntime.jsx(react.TimeField.Prefix, { className: CONTENT_SLOT_CLASS, children: startContent })), jsxRuntime.jsx(react.TimeField.Input, { className: INPUT_CLASS, children: (segment) => (jsxRuntime.jsx(react.TimeField.Segment, { segment: segment, className: segment.type === "dayPeriod"
|
|
9211
|
+
? DAY_PERIOD_SEGMENT_CLASS
|
|
9212
|
+
: undefined })) }), endContent !== undefined && endContent !== null && (jsxRuntime.jsx(react.TimeField.Suffix, { className: CONTENT_SLOT_CLASS, children: endContent }))] })] }));
|
|
9213
|
+
}
|
|
9214
|
+
/**
|
|
9215
|
+
* `TimeInput` — el nombre que en v2 era el crudo de HeroUI: etiqueta ENCIMA del campo
|
|
9216
|
+
* (`labelPlacement="outside"` por defecto), alineada con la de `DatePicker`. Migrado al
|
|
9217
|
+
* `TimeField` compound de v3; conserva `label` + los props de estado de v2 (`value`, `onChange`,
|
|
9218
|
+
* `isDisabled`, `isInvalid`, `isRequired`, `granularity`, `hourCycle`, `minValue`, `maxValue`…),
|
|
9219
|
+
* que son de React Aria y no cambiaron: `onChange` entrega un `TimeValue` de
|
|
9220
|
+
* `@internationalized/date` (o `null`), igual que el crudo de v2.
|
|
9221
|
+
*/
|
|
9222
|
+
function TimeInput({ labelPlacement = "outside", ...props }) {
|
|
9223
|
+
return jsxRuntime.jsx(TimeInputBase, { ...props, labelPlacement: labelPlacement });
|
|
9224
|
+
}
|
|
9225
|
+
/**
|
|
9226
|
+
* `TimeInputComponent` — la fachada BeweOS de v2: etiqueta EN LÍNEA a la izquierda del campo
|
|
9227
|
+
* (`labelPlacement="outside-left"` por defecto, el `outside-left` que v2 fijaba sin exponerlo).
|
|
9228
|
+
* Es lo que usan `ScheduleRow`, beweossmbs y chatbot-claw.
|
|
9229
|
+
*/
|
|
9230
|
+
function TimeInputComponent({ labelPlacement = "outside-left", ...props }) {
|
|
9231
|
+
return jsxRuntime.jsx(TimeInputBase, { ...props, labelPlacement: labelPlacement });
|
|
8366
9232
|
}
|
|
8367
9233
|
|
|
8368
9234
|
/**
|
|
@@ -8567,7 +9433,7 @@ const ScheduleRow = ({ day, daySchedule, translations = {}, showCopyToAll = fals
|
|
|
8567
9433
|
const isLastSlot = index === slotsToDisplay.length - 1;
|
|
8568
9434
|
const isSlotFilled = slot.from && slot.to;
|
|
8569
9435
|
const canDelete = slotsToDisplay.length > 1;
|
|
8570
|
-
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 w-full justify-between", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1", children: [jsxRuntime.jsx(
|
|
9436
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 w-full justify-between", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1", children: [jsxRuntime.jsx(TimeInputComponent, { label: t.from, className: "w-full", value: toTimeValue(slot.from), onChange: (value) => handleTimeChange(index, "from", value), isInvalid: !!slot.error }), jsxRuntime.jsx("span", { className: "text-default-900 xs:hidden xl:block", children: "-" }), jsxRuntime.jsx(TimeInputComponent, { label: t.to, className: "w-full", value: toTimeValue(slot.to), onChange: (value) => handleTimeChange(index, "to", value), isInvalid: !!slot.error })] }), slot.error && (jsxRuntime.jsx("span", { className: "text-xs text-danger", children: slot.error }))] }), isLastSlot ? (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onPress: handleAddTimeSlot, isDisabled: !isSlotFilled, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:add-circle-bold" }) })) : (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "danger", variant: "light", onPress: () => handleRemoveTimeSlot(index), isDisabled: !canDelete, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:trash-bin-minimalistic-outline" }) }))] }, `${day}-timeslot-${index}`));
|
|
8571
9437
|
}) }))] }, day));
|
|
8572
9438
|
};
|
|
8573
9439
|
|
|
@@ -8713,6 +9579,33 @@ const SIZE_TEXT_CLASS$1 = {
|
|
|
8713
9579
|
// `min-w-0` es obligatorio: un ítem flex tiene `min-width: auto`, o sea no baja de su `min-content`,
|
|
8714
9580
|
// y sin eso el texto desborda el trigger en vez de truncar con elipsis.
|
|
8715
9581
|
const VALUE_ROOM_CLASS = "me-6 min-w-0 truncate";
|
|
9582
|
+
/*
|
|
9583
|
+
* `isMultiline` (v2). Literal del `tv` (`@heroui/theme@2.4.15`, `dist/components/select.js:255-263`):
|
|
9584
|
+
*
|
|
9585
|
+
* isMultiline: { true: { label: "relative", trigger: "!h-auto" }, false: { value: "truncate" } }
|
|
9586
|
+
*
|
|
9587
|
+
* O sea: el disparador deja de tener alto fijo y el valor deja de truncarse. El `min-h-*` del
|
|
9588
|
+
* tamaño se conserva (v2 también: el `!h-auto` solo pisaba el `h-*`), así que en una línea mide lo
|
|
9589
|
+
* mismo. El `py-1.5` es nuestro y es obligatorio aquí: `TRIGGER_ALIGN_CLASS` pone `py-0` porque con
|
|
9590
|
+
* alto fijo el centrado lo hace `items-center`; con alto automático, sin relleno vertical la
|
|
9591
|
+
* segunda línea tocaría el borde. 6px + 20px de línea + 6px = los 32px del `sm`, así que en una
|
|
9592
|
+
* sola línea no cambia nada. Van DESPUÉS en el `cn`, que resuelve `h-8`→`h-auto` y `py-0`→`py-1.5`.
|
|
9593
|
+
*/
|
|
9594
|
+
const MULTILINE_TRIGGER_CLASS = "h-auto py-1.5";
|
|
9595
|
+
const MULTILINE_VALUE_ROOM_CLASS = "me-6 min-w-0 whitespace-normal";
|
|
9596
|
+
/*
|
|
9597
|
+
* `startContent` del disparador (v2). En v2 iba dentro del `innerWrapper`, delante del valor, con
|
|
9598
|
+
* el `gap-1.5` de ese slot (`select.js:100`). Aquí el disparador es el flex y el hueco va como
|
|
9599
|
+
* margen de la pieza, para no tocar el `gap` que v3 le pone al disparador entero.
|
|
9600
|
+
*/
|
|
9601
|
+
const START_CONTENT_CLASS = "me-1.5 flex shrink-0 items-center";
|
|
9602
|
+
/*
|
|
9603
|
+
* `isLoading` (v2): el spinner SUSTITUYE al chevron en el mismo sitio. Literales de v2
|
|
9604
|
+
* (`@heroui/select@2.4.33`, `use-select`: `getSpinnerProps` → `color: "current"`, `size: "sm"`;
|
|
9605
|
+
* slot `spinner: "absolute end-3"`). Se centra en vertical como el `Select.Indicator` de v3
|
|
9606
|
+
* (`inset-y-0 my-auto`) y toma el color del chevron (`text-field-placeholder`, `select.css`).
|
|
9607
|
+
*/
|
|
9608
|
+
const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text-field-placeholder";
|
|
8716
9609
|
/*
|
|
8717
9610
|
* EL ESTADO VACÍO DEL DESPLEGABLE (TECH-2459 · F5 · defecto reportado por el usuario y medido por
|
|
8718
9611
|
* la sesión de backoffice: «se pulsa el campo y no pasa nada»).
|
|
@@ -9049,15 +9942,24 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
|
|
|
9049
9942
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
9050
9943
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
9051
9944
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
9052
|
-
fullWidth = true,
|
|
9945
|
+
fullWidth = true, isLoading = false,
|
|
9946
|
+
// Aceptada y descartada: ver `SelectProps.isVirtualized`.
|
|
9947
|
+
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
|
|
9053
9948
|
const generatedId = $8WNJc$react.useId();
|
|
9054
9949
|
const selectId = id || generatedId;
|
|
9055
9950
|
const requiredField = isRequired;
|
|
9056
9951
|
const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
|
|
9952
|
+
// v2 pintaba `listboxProps.emptyContent` en el hueco de la lista vacía; gana a la traducción,
|
|
9953
|
+
// que es el default de la fachada (el de v2 era el literal inglés "No items.").
|
|
9954
|
+
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
9955
|
+
// Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
|
|
9956
|
+
// cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
|
|
9957
|
+
// no se lee (el hook va fuera de la condición por las reglas de hooks).
|
|
9958
|
+
const [ownMultiple, setOwnMultiple] = $8WNJc$react.useState(() => toArray(defaultSelectedKeys) ?? []);
|
|
9057
9959
|
// Mismo criterio que el `group-data-[has-helper=true]` de v2: cuenta como "con ayuda" el campo
|
|
9058
9960
|
// que efectivamente PINTA una descripción o un mensaje de error, no el que solo recibe la prop.
|
|
9059
9961
|
const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
|
|
9060
|
-
const triggerClassName = react.cn(SIZE_CLASS[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
|
|
9962
|
+
const triggerClassName = react.cn(SIZE_CLASS[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, isMultiline ? MULTILINE_TRIGGER_CLASS : "", RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
|
|
9061
9963
|
// En v2 el estado inválido MANDABA también con el campo enfocado
|
|
9062
9964
|
// (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
|
|
9063
9965
|
// el campo está en error: se queda el `danger`.
|
|
@@ -9070,7 +9972,32 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9070
9972
|
"w-full", classNames?.trigger);
|
|
9071
9973
|
const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
|
|
9072
9974
|
? LABEL_OFFSET_HELPER_CLASS[size]
|
|
9073
|
-
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [
|
|
9975
|
+
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(react.Select.Value, { className: react.cn(SIZE_TEXT_CLASS$1[size], isMultiline ? MULTILINE_VALUE_ROOM_CLASS : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
|
|
9976
|
+
? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
|
|
9977
|
+
// con selección, la función con los items en la forma de v2. El nodo de la
|
|
9978
|
+
// colección de v3 trae los mismos campos que el de v2, así que el mapeo es
|
|
9979
|
+
// 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
|
|
9980
|
+
({ state, isPlaceholder, defaultChildren }) => {
|
|
9981
|
+
if (isPlaceholder) {
|
|
9982
|
+
return defaultChildren;
|
|
9983
|
+
}
|
|
9984
|
+
const items = state.selectedItems.map((item) => ({
|
|
9985
|
+
key: item.key,
|
|
9986
|
+
data: item.value,
|
|
9987
|
+
type: item.type,
|
|
9988
|
+
props: item.props,
|
|
9989
|
+
textValue: item.textValue,
|
|
9990
|
+
rendered: item.rendered,
|
|
9991
|
+
"aria-label": item["aria-label"],
|
|
9992
|
+
}));
|
|
9993
|
+
return renderValue(items);
|
|
9994
|
+
}
|
|
9995
|
+
: undefined }), isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
|
|
9996
|
+
/* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
|
|
9997
|
+
`selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
|
|
9998
|
+
acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
|
|
9999
|
+
de más. */
|
|
10000
|
+
jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: jsxRuntime.jsx(react.ListBox
|
|
9074
10001
|
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
9075
10002
|
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
9076
10003
|
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
|
|
@@ -9084,7 +10011,7 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9084
10011
|
// el resultado en un `<div role="option" style="display:contents">`
|
|
9085
10012
|
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
|
|
9086
10013
|
// que se ve es la nuestra.
|
|
9087
|
-
renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children:
|
|
10014
|
+
renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: withItemIndicator(withV2ItemContent(withCollectionItemIds(children))) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
|
|
9088
10015
|
const commonProps = {
|
|
9089
10016
|
id: selectId,
|
|
9090
10017
|
name,
|
|
@@ -9126,7 +10053,20 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9126
10053
|
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
|
|
9127
10054
|
};
|
|
9128
10055
|
if (selectionMode === "multiple") {
|
|
9129
|
-
|
|
10056
|
+
// `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
|
|
10057
|
+
// (`useSelectState.mjs:61`, `disallowEmptySelection: selectionMode === 'single'`, pisando
|
|
10058
|
+
// la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
|
|
10059
|
+
// no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
|
|
10060
|
+
const ownsValue = disallowEmptySelection && selectedKeys === undefined;
|
|
10061
|
+
return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: ownsValue ? ownMultiple : toArray(selectedKeys), defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys), onChange: (keys) => {
|
|
10062
|
+
if (disallowEmptySelection && keys.length === 0) {
|
|
10063
|
+
return;
|
|
10064
|
+
}
|
|
10065
|
+
if (ownsValue) {
|
|
10066
|
+
setOwnMultiple(keys);
|
|
10067
|
+
}
|
|
10068
|
+
onSelectionChange?.(new Set(keys));
|
|
10069
|
+
}, children: inner }));
|
|
9130
10070
|
}
|
|
9131
10071
|
// `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
|
|
9132
10072
|
// cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
|
|
@@ -9260,40 +10200,472 @@ const StatTile = ({ title, value, icon, trend, translations = {}, className, pad
|
|
|
9260
10200
|
.join(" "), ...cardProps, children: [icon && (jsxRuntime.jsx("span", { className: "shrink-0 text-foreground-600", "aria-hidden": "true", children: icon })), jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [jsxRuntime.jsx(P, { className: "truncate", children: title }), jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx(H3, { className: "text-2xl font-semibold", children: value }), trend && (jsxRuntime.jsx("span", { className: `text-xs font-medium ${TREND_COLOR_CLASS[direction]}`, "aria-label": t[TREND_ARIA_KEY[direction]], children: trend.value }))] })] })] }));
|
|
9261
10201
|
};
|
|
9262
10202
|
|
|
10203
|
+
// TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
|
|
10204
|
+
// CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
|
|
10205
|
+
// de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
|
|
10206
|
+
//
|
|
10207
|
+
// HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
|
|
10208
|
+
// `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
|
|
10209
|
+
// `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
|
|
10210
|
+
// `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
|
|
10211
|
+
// (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
|
|
10212
|
+
// — el falso amigo de siempre (skill `auraui-design-tokens`).
|
|
10213
|
+
//
|
|
10214
|
+
// Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
|
|
10215
|
+
// de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
|
|
10216
|
+
// Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
|
|
10217
|
+
// control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
|
|
10218
|
+
// pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
|
|
10219
|
+
// `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
|
|
10220
|
+
//
|
|
10221
|
+
// ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
|
|
10222
|
+
// `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
|
|
10223
|
+
// `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
|
|
10224
|
+
// eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
|
|
10225
|
+
// (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
|
|
9263
10226
|
/**
|
|
9264
|
-
*
|
|
9265
|
-
*
|
|
10227
|
+
* Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
|
|
10228
|
+
* por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
|
|
10229
|
+
* usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
|
|
10230
|
+
*
|
|
10231
|
+
* Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
|
|
9266
10232
|
*/
|
|
9267
|
-
const
|
|
10233
|
+
const CONTROL_COLOR = {
|
|
10234
|
+
primary: "",
|
|
10235
|
+
default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
|
|
10236
|
+
secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
|
|
10237
|
+
success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
|
|
10238
|
+
warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
|
|
10239
|
+
danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
|
|
10240
|
+
};
|
|
9268
10241
|
/**
|
|
9269
|
-
*
|
|
9270
|
-
*
|
|
9271
|
-
* la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
|
|
9272
|
-
* que desbordar.
|
|
10242
|
+
* Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
|
|
10243
|
+
* checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
|
|
9273
10244
|
*/
|
|
9274
|
-
const
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
]
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
10245
|
+
const INDICATOR_COLOR = {
|
|
10246
|
+
primary: "",
|
|
10247
|
+
default: "[&_svg]:text-default-foreground",
|
|
10248
|
+
secondary: "[&_svg]:text-secondary-foreground",
|
|
10249
|
+
success: "[&_svg]:text-success-foreground",
|
|
10250
|
+
warning: "[&_svg]:text-warning-foreground",
|
|
10251
|
+
danger: "[&_svg]:text-danger-foreground",
|
|
10252
|
+
};
|
|
10253
|
+
// v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
|
|
10254
|
+
// `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
|
|
10255
|
+
// `size-2.5`, texto `text-sm`, para todos.
|
|
10256
|
+
const CONTROL_SIZE = {
|
|
10257
|
+
sm: "size-4",
|
|
10258
|
+
md: "size-5",
|
|
10259
|
+
lg: "size-6",
|
|
10260
|
+
};
|
|
10261
|
+
const INDICATOR_SIZE = {
|
|
10262
|
+
sm: "size-3 [&_svg]:size-2.5",
|
|
10263
|
+
md: "size-3.5 [&_svg]:size-3",
|
|
10264
|
+
lg: "size-4 [&_svg]:size-3.5",
|
|
10265
|
+
};
|
|
10266
|
+
const LABEL_SIZE = {
|
|
10267
|
+
sm: "text-sm",
|
|
10268
|
+
md: "text-base",
|
|
10269
|
+
lg: "text-lg",
|
|
10270
|
+
};
|
|
10271
|
+
// v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
|
|
10272
|
+
// defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
|
|
10273
|
+
// mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
|
|
10274
|
+
const RADIUS_CLASS$3 = {
|
|
10275
|
+
none: "rounded-none before:rounded-none",
|
|
10276
|
+
sm: "rounded-[6px] before:rounded-[6px]",
|
|
10277
|
+
md: "rounded-[7.2px] before:rounded-[7.2px]",
|
|
10278
|
+
lg: "rounded-[8.4px] before:rounded-[8.4px]",
|
|
10279
|
+
full: "rounded-full before:rounded-full",
|
|
10280
|
+
};
|
|
10281
|
+
const RADIUS_BY_SIZE = {
|
|
10282
|
+
sm: "sm",
|
|
10283
|
+
md: "md",
|
|
10284
|
+
lg: "lg",
|
|
10285
|
+
};
|
|
10286
|
+
/**
|
|
10287
|
+
* Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
|
|
10288
|
+
* (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
|
|
10289
|
+
*
|
|
10290
|
+
* En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
|
|
10291
|
+
* relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
|
|
10292
|
+
* `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
|
|
10293
|
+
* eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
|
|
10294
|
+
* quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
|
|
10295
|
+
* posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
|
|
10296
|
+
*
|
|
10297
|
+
* Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
|
|
10298
|
+
* (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
|
|
10299
|
+
*
|
|
10300
|
+
* Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
|
|
10301
|
+
* `::before` para que el relleno cubra la caja entera, igual que al marcar.
|
|
10302
|
+
*/
|
|
10303
|
+
const CONTROL_BASE = [
|
|
10304
|
+
"border-2 border-default shadow-none overflow-visible",
|
|
10305
|
+
"before:-inset-0.5",
|
|
10306
|
+
"group-data-[hovered=true]/content:bg-default-100",
|
|
10307
|
+
"group-data-[indeterminate=true]/checkbox:before:scale-100",
|
|
10308
|
+
"group-data-[indeterminate=true]/checkbox:before:opacity-100",
|
|
10309
|
+
].join(" ");
|
|
10310
|
+
// v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
|
|
10311
|
+
// atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
|
|
10312
|
+
const LINE_THROUGH = [
|
|
10313
|
+
"inline-flex items-center justify-center",
|
|
10314
|
+
"before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
|
|
10315
|
+
"group-data-[selected=true]/checkbox:opacity-60",
|
|
10316
|
+
"group-data-[selected=true]/checkbox:before:w-full",
|
|
10317
|
+
].join(" ");
|
|
10318
|
+
// Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
|
|
10319
|
+
// a través de la fachada (`<Checkbox><Checkbox.Control>…`).
|
|
10320
|
+
const COMPOUND_PARTS$1 = new Set([
|
|
10321
|
+
react.Checkbox.Control,
|
|
10322
|
+
react.Checkbox.Indicator,
|
|
10323
|
+
react.Checkbox.Content,
|
|
10324
|
+
]);
|
|
10325
|
+
/** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
|
|
10326
|
+
function hasCompoundChildren$1(children) {
|
|
10327
|
+
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
|
|
9281
10328
|
}
|
|
9282
|
-
|
|
10329
|
+
/**
|
|
10330
|
+
* Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
|
|
10331
|
+
*
|
|
10332
|
+
* - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
|
|
10333
|
+
* (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
|
|
10334
|
+
* dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
|
|
10335
|
+
* - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
|
|
10336
|
+
* cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
|
|
10337
|
+
*/
|
|
10338
|
+
const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
|
|
10339
|
+
const handleChange = (isSelected) => {
|
|
10340
|
+
onChange?.(isSelected);
|
|
10341
|
+
onValueChange?.(isSelected);
|
|
10342
|
+
};
|
|
10343
|
+
if (hasCompoundChildren$1(children)) {
|
|
10344
|
+
return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
|
|
10345
|
+
}
|
|
10346
|
+
const indicatorContent = typeof icon === "function"
|
|
10347
|
+
? (state) => icon({
|
|
10348
|
+
isSelected: state.isSelected,
|
|
10349
|
+
isIndeterminate: state.isIndeterminate,
|
|
10350
|
+
isDisabled: state.isDisabled,
|
|
10351
|
+
})
|
|
10352
|
+
: icon;
|
|
10353
|
+
const hasLabel = children !== undefined && children !== null && children !== false;
|
|
10354
|
+
return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
|
|
10355
|
+
// Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
|
|
10356
|
+
// p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
|
|
10357
|
+
// (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
|
|
10358
|
+
// slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
|
|
10359
|
+
// texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
|
|
10360
|
+
className: react.cn("group/checkbox relative -m-2 inline-flex max-w-fit flex-row items-center p-2", classNames?.base, className), children: jsxRuntime.jsxs(react.Checkbox.Content, { className: "group/content gap-2 font-normal", children: [jsxRuntime.jsx(react.Checkbox.Control, { className: react.cn(CONTROL_BASE, CONTROL_SIZE[size], RADIUS_CLASS$3[radius ?? RADIUS_BY_SIZE[size]], CONTROL_COLOR[color],
|
|
10361
|
+
// v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
|
|
10362
|
+
isInvalid && "border-danger", disableAnimation && "transition-none before:transition-none", classNames?.wrapper), children: jsxRuntime.jsx(react.Checkbox.Indicator, { className: react.cn(INDICATOR_SIZE[size], INDICATOR_COLOR[color], disableAnimation && "[&_svg]:transition-none", classNames?.icon), children: indicatorContent }) }), hasLabel && (
|
|
10363
|
+
// Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
|
|
10364
|
+
// `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
|
|
10365
|
+
// le alcancen igual que a un `<Label>`.
|
|
10366
|
+
jsxRuntime.jsx("span", { "data-slot": "label", className: react.cn("relative select-none text-foreground", LABEL_SIZE[size], lineThrough && LINE_THROUGH, isInvalid && "text-danger", classNames?.label), children: children }))] }) }));
|
|
10367
|
+
};
|
|
10368
|
+
CheckboxFacade.displayName = "Aurora.Checkbox";
|
|
10369
|
+
/**
|
|
10370
|
+
* `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
|
|
10371
|
+
* `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
|
|
10372
|
+
*/
|
|
10373
|
+
const Checkbox = Object.assign(CheckboxFacade, {
|
|
10374
|
+
Control: react.Checkbox.Control,
|
|
10375
|
+
Indicator: react.Checkbox.Indicator,
|
|
10376
|
+
Content: react.Checkbox.Content,
|
|
10377
|
+
});
|
|
10378
|
+
|
|
10379
|
+
// TECH-2459 — Piezas que la fachada `Table.Content` inserta para reponer la API v2 que v3 dejó en
|
|
10380
|
+
// manos de la app (defectos reportados por beweossmbs, 23-sep). Privadas: NO se reexportan desde
|
|
10381
|
+
// `_internal/index.ts` ni desde el `index.ts` de la carpeta.
|
|
10382
|
+
//
|
|
10383
|
+
// Todo se decide por IDENTIDAD de `child.type` contra los objetos reales de HeroUI (mismo patrón
|
|
10384
|
+
// que las fachadas `Alert`, `Tabs` y el propio root de `Table`), nunca por `displayName`.
|
|
10385
|
+
/**
|
|
10386
|
+
* `isHeaderSticky` declarado en el ROOT (`<Table isHeaderSticky>`, que es donde lo ponía v2) viaja
|
|
10387
|
+
* hasta `Table.Content` por contexto: el `<thead>` lo pinta el contenido, no el root.
|
|
10388
|
+
*/
|
|
10389
|
+
const TableStickyContext = $8WNJc$react.createContext(undefined);
|
|
10390
|
+
/**
|
|
10391
|
+
* Clases de v2 para `isHeaderSticky` (`@heroui/theme@2.4.15` → `dist/components/table.js`,
|
|
10392
|
+
* variante `isHeaderSticky.true.thead`): `sticky top-0 z-20 [&>tr]:first:shadow-small`. La sombra
|
|
10393
|
+
* se descarta: `shadow-small` es una clase del plugin de v2 que en Tailwind 4 no genera nada, y
|
|
10394
|
+
* v3 separa la cabecera del cuerpo con su propio borde (`.table__header { border-b }`).
|
|
10395
|
+
*
|
|
10396
|
+
* El «contenedor con scroll» respecto al que se pega es el `Table.ScrollContainer` que inserta la
|
|
10397
|
+
* fachada del root (`overflow-x: auto` ⇒ `overflow-y` computa `auto`), exactamente como en v2 lo
|
|
10398
|
+
* era el `wrapper` (`overflow-auto`): la cabecera se queda fija cuando ese contenedor tiene alto
|
|
10399
|
+
* acotado y desplaza en vertical.
|
|
10400
|
+
*/
|
|
10401
|
+
const STICKY_HEADER_CLASS = "sticky top-0 z-20";
|
|
10402
|
+
/** Clave fija de la columna/celda de selección insertadas (no colisiona con claves de negocio). */
|
|
10403
|
+
const SELECTION_KEY = "__aurora-selection";
|
|
10404
|
+
/**
|
|
10405
|
+
* Columna de «seleccionar todo». El `Checkbox slot="selection"` recibe de react-aria, por
|
|
10406
|
+
* `CheckboxContext`, el estado de la tabla: marcado con todo seleccionado, INDETERMINADO con una
|
|
10407
|
+
* selección parcial, y su `onChange` selecciona/deselecciona todo — `onSelectionChange` recibe
|
|
10408
|
+
* `"all"`, igual que en v2 (`useTableSelectAllCheckbox`). La etiqueta accesible también la pone
|
|
10409
|
+
* react-aria, traducida a la locale activa: por eso esta pieza no tiene texto propio ni
|
|
10410
|
+
* `translations`.
|
|
10411
|
+
*
|
|
10412
|
+
* `pe-0` es lo que hace la anatomía oficial de v3 con su columna de selección: la casilla no
|
|
10413
|
+
* necesita el padding final de una columna de texto.
|
|
10414
|
+
*/
|
|
10415
|
+
function SelectionColumn() {
|
|
10416
|
+
return (jsxRuntime.jsx(react.Table.Column, { className: "pe-0", id: SELECTION_KEY, children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
|
|
10417
|
+
}
|
|
10418
|
+
/** Casilla por fila (`useTableSelectionCheckbox` vía `CheckboxContext`, como en v2). */
|
|
10419
|
+
function SelectionCell() {
|
|
10420
|
+
return (jsxRuntime.jsx(react.Table.Cell, { className: "pe-0", children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
|
|
10421
|
+
}
|
|
10422
|
+
function isElementOf(node, type) {
|
|
10423
|
+
return $8WNJc$react.isValidElement(node) && node.type === type;
|
|
10424
|
+
}
|
|
10425
|
+
/**
|
|
10426
|
+
* Hijos listos para pasarse como argumentos VARIÁDICOS de `cloneElement`/`createElement`. Un array
|
|
10427
|
+
* pasado como argumento único es para React una LISTA y exige `key` en cada elemento: si la app
|
|
10428
|
+
* escribió hijos JSX sueltos (sin clave, legal) y la fachada los reenviara como un solo array,
|
|
10429
|
+
* saltaría «Each child in a list should have a unique key» (medido en jsdom). Esparcidos, React
|
|
10430
|
+
* los trata como los trató al escribirlos.
|
|
10431
|
+
*/
|
|
10432
|
+
function spread(node) {
|
|
10433
|
+
return Array.isArray(node) ? node : [node];
|
|
10434
|
+
}
|
|
10435
|
+
/**
|
|
10436
|
+
* Aplica `fn` a cada nodo de un árbol de `children` atravesando arrays y Fragments, sin tocar
|
|
10437
|
+
* claves (`cloneElement` las conserva). `Children.map` no sirve aquí: re-prefija las claves y NO
|
|
10438
|
+
* entra en Fragments — y las apps envuelven en Fragment cuando el contenido es condicional.
|
|
10439
|
+
*/
|
|
10440
|
+
function mapNodes(node, fn) {
|
|
10441
|
+
if (Array.isArray(node)) {
|
|
10442
|
+
return node.map((child) => mapNodes(child, fn));
|
|
10443
|
+
}
|
|
10444
|
+
if (isElementOf(node, $8WNJc$react.Fragment)) {
|
|
10445
|
+
return $8WNJc$react.cloneElement(node, undefined, ...spread(mapNodes(node.props.children, fn)));
|
|
10446
|
+
}
|
|
10447
|
+
return fn(node);
|
|
10448
|
+
}
|
|
10449
|
+
/**
|
|
10450
|
+
* ¿Hay ya una casilla de selección montada (por la fachada o por la app) en este subárbol
|
|
10451
|
+
* estático? Mira la identidad de las piezas propias y cualquier elemento con `slot="selection"`
|
|
10452
|
+
* (la forma que documenta v3). Las funciones de render no se pueden inspeccionar sin llamarlas:
|
|
10453
|
+
* esas cuentan como «no hay».
|
|
10454
|
+
*/
|
|
10455
|
+
function hasSelectionControl(node) {
|
|
10456
|
+
if (Array.isArray(node)) {
|
|
10457
|
+
return node.some(hasSelectionControl);
|
|
10458
|
+
}
|
|
10459
|
+
if (!$8WNJc$react.isValidElement(node)) {
|
|
10460
|
+
return false;
|
|
10461
|
+
}
|
|
10462
|
+
if (node.type === SelectionColumn || node.type === SelectionCell) {
|
|
10463
|
+
return true;
|
|
10464
|
+
}
|
|
10465
|
+
const props = node.props;
|
|
10466
|
+
if (props.slot === "selection") {
|
|
10467
|
+
return true;
|
|
10468
|
+
}
|
|
10469
|
+
return typeof props.children === "function"
|
|
10470
|
+
? false
|
|
10471
|
+
: hasSelectionControl(props.children);
|
|
10472
|
+
}
|
|
10473
|
+
function containsType(node, type) {
|
|
10474
|
+
if (Array.isArray(node)) {
|
|
10475
|
+
return node.some((child) => containsType(child, type));
|
|
10476
|
+
}
|
|
10477
|
+
if (!$8WNJc$react.isValidElement(node)) {
|
|
10478
|
+
return false;
|
|
10479
|
+
}
|
|
10480
|
+
if (node.type === type) {
|
|
10481
|
+
return true;
|
|
10482
|
+
}
|
|
10483
|
+
const children = node.props.children;
|
|
10484
|
+
return typeof children === "function" ? false : containsType(children, type);
|
|
10485
|
+
}
|
|
10486
|
+
/**
|
|
10487
|
+
* Columna con `allowsSorting`: v2 pintaba el chevron por su cuenta (`TableColumnHeader`,
|
|
10488
|
+
* `ChevronDownIcon` con `data-direction`); v3 no pinta nada y pide que la app monte
|
|
10489
|
+
* `Table.SortableColumnHeader` en el render prop de la columna. Aquí se monta esa pieza de v3
|
|
10490
|
+
* — aspecto de v3 (§9.1): chevron a la derecha que apunta arriba en ascendente y gira en
|
|
10491
|
+
* descendente, y sin chevron mientras la columna no es la ordenada.
|
|
10492
|
+
*
|
|
10493
|
+
* No se toca la columna si la app ya decide el contenido con un render prop (entonces es suyo) o
|
|
10494
|
+
* si ya montó un `SortableColumnHeader` estático.
|
|
10495
|
+
*/
|
|
10496
|
+
function withSortIndicator(node) {
|
|
10497
|
+
if (!isElementOf(node, react.Table.Column)) {
|
|
10498
|
+
return node;
|
|
10499
|
+
}
|
|
10500
|
+
const { allowsSorting, children } = node.props;
|
|
10501
|
+
if (!allowsSorting ||
|
|
10502
|
+
typeof children === "function" ||
|
|
10503
|
+
containsType(children, react.Table.SortableColumnHeader)) {
|
|
10504
|
+
return node;
|
|
10505
|
+
}
|
|
10506
|
+
return $8WNJc$react.cloneElement(node, {
|
|
10507
|
+
children: ({ sortDirection }) => (jsxRuntime.jsx(react.Table.SortableColumnHeader, { sortDirection: sortDirection, children: children })),
|
|
10508
|
+
});
|
|
10509
|
+
}
|
|
10510
|
+
/** Compone una clase sobre un `className` de react-aria, que puede ser string o render prop. */
|
|
10511
|
+
function withClass(className, extra) {
|
|
10512
|
+
if (typeof className === "function") {
|
|
10513
|
+
return (renderProps) => react.cn(className(renderProps), extra);
|
|
10514
|
+
}
|
|
10515
|
+
return react.cn(className, extra);
|
|
10516
|
+
}
|
|
10517
|
+
/**
|
|
10518
|
+
* `Table.Header`: indicador de orden en cada columna ordenable, clase sticky si se pidió y la
|
|
10519
|
+
* columna de selección delante si toca.
|
|
10520
|
+
*
|
|
10521
|
+
* Con columnas dinámicas (`columns` + función) la columna de selección no se puede colar en el
|
|
10522
|
+
* array de la app — su función de render la recibiría como si fuera una columna de negocio. Se usa
|
|
10523
|
+
* la forma que documenta react-aria para mezclar estáticas y dinámicas: la estática delante y las
|
|
10524
|
+
* de la app en un `Table.Collection` con sus `items`. El `Collection` exterior del header hereda
|
|
10525
|
+
* `dependencies` por contexto (`CollectionBuilder`), así que no se pierden.
|
|
10526
|
+
*/
|
|
10527
|
+
function transformHeader(header, options) {
|
|
10528
|
+
const { children, columns, className } = header.props;
|
|
10529
|
+
const props = options.sticky
|
|
10530
|
+
? { className: withClass(className, STICKY_HEADER_CLASS) }
|
|
10531
|
+
: {};
|
|
10532
|
+
if (typeof children === "function") {
|
|
10533
|
+
const render = children;
|
|
10534
|
+
const wrapped = (column) => withSortIndicator(render(column));
|
|
10535
|
+
if (!options.insertSelection) {
|
|
10536
|
+
return $8WNJc$react.cloneElement(header, { ...props, children: wrapped });
|
|
10537
|
+
}
|
|
10538
|
+
return $8WNJc$react.cloneElement(header, { ...props, columns: undefined }, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: wrapped }, "columns"));
|
|
10539
|
+
}
|
|
10540
|
+
const mapped = spread(mapNodes(children, withSortIndicator));
|
|
10541
|
+
if (!options.insertSelection) {
|
|
10542
|
+
return $8WNJc$react.cloneElement(header, props, ...mapped);
|
|
10543
|
+
}
|
|
10544
|
+
return $8WNJc$react.cloneElement(header, props, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), ...mapped);
|
|
10545
|
+
}
|
|
10546
|
+
/**
|
|
10547
|
+
* Casilla delante de las celdas de una fila. Solo sabe tratar `Table.Row` (el mismo objeto que el
|
|
10548
|
+
* `TableRow` suelto): una fila envuelta en un componente propio de la app no se puede abrir, y se
|
|
10549
|
+
* devuelve tal cual (límite documentado en el README).
|
|
10550
|
+
*/
|
|
10551
|
+
function withSelectionCell(row) {
|
|
10552
|
+
if (!isElementOf(row, react.Table.Row)) {
|
|
10553
|
+
return row;
|
|
10554
|
+
}
|
|
10555
|
+
const { children, columns } = row.props;
|
|
10556
|
+
if (typeof children === "function") {
|
|
10557
|
+
// Mismo mecanismo que el header: estática + `Collection` con las columnas de la app. El
|
|
10558
|
+
// `idScope` de la fila (su `id`) lo hereda la colección interior por contexto, así que las
|
|
10559
|
+
// claves de las celdas siguen siendo únicas por fila.
|
|
10560
|
+
return $8WNJc$react.cloneElement(row, { columns: undefined }, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: children }, "cells"));
|
|
10561
|
+
}
|
|
10562
|
+
if (hasSelectionControl(children)) {
|
|
10563
|
+
return row;
|
|
10564
|
+
}
|
|
10565
|
+
return $8WNJc$react.cloneElement(row, undefined, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), ...spread(children));
|
|
10566
|
+
}
|
|
10567
|
+
/** `Table.Body`: una casilla por fila, tanto con `items` + función como con filas estáticas. */
|
|
10568
|
+
function transformBody(body) {
|
|
10569
|
+
const { children } = body.props;
|
|
10570
|
+
if (typeof children === "function") {
|
|
10571
|
+
const render = children;
|
|
10572
|
+
return $8WNJc$react.cloneElement(body, {
|
|
10573
|
+
children: (item) => withSelectionCell(render(item)),
|
|
10574
|
+
});
|
|
10575
|
+
}
|
|
10576
|
+
return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
|
|
10577
|
+
}
|
|
10578
|
+
|
|
10579
|
+
/** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
|
|
10580
|
+
function defaultShowCheckboxes(props) {
|
|
10581
|
+
const behavior = props.selectionBehavior ?? "toggle";
|
|
10582
|
+
return props.selectionMode === "multiple" && behavior !== "replace";
|
|
10583
|
+
}
|
|
10584
|
+
/** Primer `Table.Header` del árbol (atravesando arrays y Fragments), para decidir la selección. */
|
|
10585
|
+
function findHeader(node) {
|
|
10586
|
+
let found;
|
|
10587
|
+
mapNodes(node, (child) => {
|
|
10588
|
+
if (!found && isElementOf(child, react.Table.Header)) {
|
|
10589
|
+
found = child;
|
|
10590
|
+
}
|
|
10591
|
+
return child;
|
|
10592
|
+
});
|
|
10593
|
+
return found;
|
|
10594
|
+
}
|
|
10595
|
+
/**
|
|
10596
|
+
* `Table.Content` de Aurora: el `Table.Content` de HeroUI v3 con las casillas de selección, el
|
|
10597
|
+
* indicador de orden y la cabecera fija de v2. Idempotente: si la app ya montó sus casillas
|
|
10598
|
+
* (`slot="selection"`) o su `SortableColumnHeader`, no se inserta nada encima.
|
|
10599
|
+
*/
|
|
10600
|
+
function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...props }) {
|
|
10601
|
+
const rootSticky = $8WNJc$react.useContext(TableStickyContext);
|
|
10602
|
+
const sticky = isHeaderSticky ?? rootSticky ?? false;
|
|
10603
|
+
const header = findHeader(children);
|
|
10604
|
+
// La decisión es de TABLA, no de fila: si la cabecera ya trae casilla (la montó la app), no
|
|
10605
|
+
// se inserta ni en la cabecera ni en las filas — insertar solo en unas descuadraría el número
|
|
10606
|
+
// de celdas y react-aria lanza `Cell count must match column count`.
|
|
10607
|
+
const insertSelection = (showSelectionCheckboxes ?? defaultShowCheckboxes(props)) &&
|
|
10608
|
+
header !== undefined &&
|
|
10609
|
+
!(typeof header.props.children !== "function" &&
|
|
10610
|
+
hasSelectionControl(header.props.children));
|
|
10611
|
+
const content = mapNodes(children, (child) => {
|
|
10612
|
+
if (isElementOf(child, react.Table.Header)) {
|
|
10613
|
+
return transformHeader(child, { insertSelection, sticky });
|
|
10614
|
+
}
|
|
10615
|
+
if (insertSelection && isElementOf(child, react.Table.Body)) {
|
|
10616
|
+
return transformBody(child);
|
|
10617
|
+
}
|
|
10618
|
+
return child;
|
|
10619
|
+
});
|
|
10620
|
+
// Esparcido y no `{content}`: ver `spread` (un array como hijo único exigiría claves).
|
|
10621
|
+
return $8WNJc$react.createElement(react.Table.Content, props, ...spread(content));
|
|
10622
|
+
}
|
|
10623
|
+
TableContent.displayName = "Aurora.Table.Content";
|
|
10624
|
+
|
|
10625
|
+
/**
|
|
10626
|
+
* Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
|
|
10627
|
+
* a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
|
|
10628
|
+
*/
|
|
10629
|
+
const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
|
|
10630
|
+
/**
|
|
10631
|
+
* Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
|
|
10632
|
+
* `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
|
|
10633
|
+
* la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
|
|
10634
|
+
* que desbordar.
|
|
10635
|
+
*/
|
|
10636
|
+
const OWN_SCROLL = new Set([
|
|
10637
|
+
react.Table.ScrollContainer,
|
|
10638
|
+
react.Table.ResizableContainer,
|
|
10639
|
+
]);
|
|
10640
|
+
/** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
|
|
10641
|
+
function isPart$2(node, parts) {
|
|
10642
|
+
return $8WNJc$react.isValidElement(node) && parts.has(node.type);
|
|
10643
|
+
}
|
|
10644
|
+
/**
|
|
10645
|
+
* `isHeaderSticky` del root viaja a `Table.Content` por contexto (el `<thead>` lo pinta el
|
|
10646
|
+
* contenido). Solo se monta el provider cuando la app declara la prop: sin ella el árbol del root
|
|
10647
|
+
* queda exactamente como antes (`.Provider` y no `<Context value>` porque el peer admite React 18).
|
|
10648
|
+
*/
|
|
10649
|
+
function withSticky(isHeaderSticky, root) {
|
|
10650
|
+
return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
|
|
10651
|
+
}
|
|
10652
|
+
const TableFacade = ({ children, isHeaderSticky, ...props }) => {
|
|
9283
10653
|
const nodes = $8WNJc$react.Children.toArray(children);
|
|
9284
10654
|
// Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
|
|
9285
10655
|
if (nodes.length === 0 || nodes.some((node) => isPart$2(node, OWN_SCROLL))) {
|
|
9286
|
-
return jsxRuntime.jsx(react.Table, { ...props, children: children });
|
|
10656
|
+
return withSticky(isHeaderSticky, jsxRuntime.jsx(react.Table, { ...props, children: children }));
|
|
9287
10657
|
}
|
|
9288
10658
|
const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
|
|
9289
10659
|
const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
|
|
9290
|
-
return (jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
|
|
10660
|
+
return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
|
|
9291
10661
|
};
|
|
9292
10662
|
TableFacade.displayName = "Aurora.Table";
|
|
9293
10663
|
/**
|
|
9294
10664
|
* `Table` de Aurora: el root de HeroUI v3 con el `ScrollContainer` que faltaba, más los estáticos
|
|
9295
|
-
* del compound de v3 — los MISMOS objetos que exporta `@heroui/react
|
|
9296
|
-
* `Table.
|
|
10665
|
+
* del compound de v3 — los MISMOS objetos que exporta `@heroui/react` (así que
|
|
10666
|
+
* `Table.Header`/`Table.Column isRowHeader`… se comportan igual que antes), SALVO `Table.Content`,
|
|
10667
|
+
* que es la fachada de Aurora: inserta las casillas de selección múltiple, el indicador de orden
|
|
10668
|
+
* de `allowsSorting` y la cabecera fija de `isHeaderSticky` (ver `TableContent.tsx`).
|
|
9297
10669
|
*
|
|
9298
10670
|
* `Table.Root` queda deliberadamente crudo (el root pelado de v3, sin scroll): es la vía de escape
|
|
9299
10671
|
* para quien quiera montar la anatomía entera a mano.
|
|
@@ -9304,7 +10676,7 @@ const Table = Object.assign(TableFacade, {
|
|
|
9304
10676
|
Collection: react.Table.Collection,
|
|
9305
10677
|
Column: react.Table.Column,
|
|
9306
10678
|
ColumnResizer: react.Table.ColumnResizer,
|
|
9307
|
-
Content:
|
|
10679
|
+
Content: TableContent,
|
|
9308
10680
|
Footer: react.Table.Footer,
|
|
9309
10681
|
Header: react.Table.Header,
|
|
9310
10682
|
LoadMore: react.Table.LoadMore,
|
|
@@ -9373,6 +10745,225 @@ function AuraTable({ columns, items, renderCell, children, className, ...props }
|
|
|
9373
10745
|
return (jsxRuntime.jsx(Table, { className: `rounded-none ${className ?? ""}`.trim(), children: jsxRuntime.jsxs(Table.Content, { ...props, children: [jsxRuntime.jsx(Table.Header, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Column, { isRowHeader: column.isRowHeader || column.key === rowHeaderKey, children: column.label }, column.key)) }), jsxRuntime.jsx(Table.Body, { items: items, children: (item) => (jsxRuntime.jsx(Table.Row, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Cell, { children: renderCell ? renderCell(item, column.key) : children })) }, item.id)) })] }) }));
|
|
9374
10746
|
}
|
|
9375
10747
|
|
|
10748
|
+
// ── Autocrecimiento entre `minRows` y `maxRows` (reposición de v2) ─────────────────────────────
|
|
10749
|
+
//
|
|
10750
|
+
// v2 montaba `react-textarea-autosize@8.5.9` (`@heroui/input@2.4.22`, `dist/chunk-RIE4DNUQ.mjs`)
|
|
10751
|
+
// y aquí esa dependencia no existe, así que se reimplementa su algoritmo, leído en su compilado
|
|
10752
|
+
// (`dist/react-textarea-autosize.browser.esm.js`, `calculateNodeHeight` + `resizeTextarea`):
|
|
10753
|
+
//
|
|
10754
|
+
// 1. Se copia el estilo que decide el ajuste de línea (fuente, relleno, borde, ancho…) a un
|
|
10755
|
+
// `<textarea>` OCULTO compartido, se le pone el valor (o el placeholder, o "x") y se lee su
|
|
10756
|
+
// `scrollHeight`. Se mide en un clon y no en el nodo real porque encoger el real a 0 para
|
|
10757
|
+
// medirlo mueve el scroll de la página cuando el campo está cerca del borde inferior.
|
|
10758
|
+
// 2. El alto de UNA línea sale del mismo clon con el valor "x", no de una constante: así cuadra
|
|
10759
|
+
// con la fuente y el tamaño que el campo tenga de verdad.
|
|
10760
|
+
// 3. `alto = min(máx, max(mín, contenido))`, con mín/máx = filas × línea + relleno (+ borde en
|
|
10761
|
+
// `border-box`). Si `maxRows < minRows`, gana `maxRows` — el `min` va por fuera, como en v2.
|
|
10762
|
+
// 4. Se escribe con `setProperty("height", …, "important")`, literal de v2.
|
|
10763
|
+
//
|
|
10764
|
+
// Cuándo se recalcula (los mismos disparadores que v2, más el ancho):
|
|
10765
|
+
// · en cada render de la fachada (layout effect sin dependencias, como v2) → cubre el valor
|
|
10766
|
+
// CONTROLADO y cualquier cambio de `size`/`className` que mueva la fuente o el ancho;
|
|
10767
|
+
// · al escribir, en los dos modos (`resize` desde el `onChange` de la fachada). Hace falta
|
|
10768
|
+
// aunque el valor viva en estado de React: en modo NO controlado ese estado es del
|
|
10769
|
+
// `TextField` de react-aria, y su re-render no re-renderiza la fachada, así que el layout
|
|
10770
|
+
// effect de arriba no corre;
|
|
10771
|
+
// · al cambiar el ANCHO del campo (`ResizeObserver`; v2 solo escuchaba `window.resize`, que no
|
|
10772
|
+
// ve un panel que se estrecha) — solo si cambió el ancho, para no reaccionar a nuestro propio
|
|
10773
|
+
// cambio de alto;
|
|
10774
|
+
// · al terminar de cargar una fuente web (`document.fonts` → `loadingdone`, como v2);
|
|
10775
|
+
// · tras el `reset` del formulario (como v2): `useFormReset` de react-aria devuelve el estado del
|
|
10776
|
+
// `TextField` al `defaultValue`, otra vez sin re-renderizar la fachada. Se mide en el frame
|
|
10777
|
+
// siguiente, cuando React ya ha escrito el valor restaurado en el DOM.
|
|
10778
|
+
//
|
|
10779
|
+
// Scroll: v2 ocultaba la barra mientras no se alcanzaba el tope (`data-hide-scroll` →
|
|
10780
|
+
// `scrollbar-hide`, `@heroui/theme@2.4.15`); aquí se consigue lo mismo con `overflow-y: hidden`
|
|
10781
|
+
// inline hasta que el contenido supera el máximo, y a partir de ahí se deja el scroll nativo.
|
|
10782
|
+
const HIDDEN_TEXTAREA_STYLE = {
|
|
10783
|
+
"min-height": "0",
|
|
10784
|
+
"max-height": "none",
|
|
10785
|
+
height: "0",
|
|
10786
|
+
visibility: "hidden",
|
|
10787
|
+
overflow: "hidden",
|
|
10788
|
+
position: "absolute",
|
|
10789
|
+
"z-index": "-1000",
|
|
10790
|
+
top: "0",
|
|
10791
|
+
right: "0",
|
|
10792
|
+
display: "block",
|
|
10793
|
+
};
|
|
10794
|
+
// Lista literal de `react-textarea-autosize` (`SIZING_STYLE`).
|
|
10795
|
+
const SIZING_STYLE = [
|
|
10796
|
+
"borderBottomWidth",
|
|
10797
|
+
"borderLeftWidth",
|
|
10798
|
+
"borderRightWidth",
|
|
10799
|
+
"borderTopWidth",
|
|
10800
|
+
"boxSizing",
|
|
10801
|
+
"fontFamily",
|
|
10802
|
+
"fontSize",
|
|
10803
|
+
"fontStyle",
|
|
10804
|
+
"fontWeight",
|
|
10805
|
+
"letterSpacing",
|
|
10806
|
+
"lineHeight",
|
|
10807
|
+
"paddingBottom",
|
|
10808
|
+
"paddingLeft",
|
|
10809
|
+
"paddingRight",
|
|
10810
|
+
"paddingTop",
|
|
10811
|
+
"tabSize",
|
|
10812
|
+
"textIndent",
|
|
10813
|
+
"textRendering",
|
|
10814
|
+
"textTransform",
|
|
10815
|
+
"width",
|
|
10816
|
+
"wordBreak",
|
|
10817
|
+
"wordSpacing",
|
|
10818
|
+
"scrollbarGutter",
|
|
10819
|
+
];
|
|
10820
|
+
let hiddenTextarea = null;
|
|
10821
|
+
const forceHiddenStyles = (node) => {
|
|
10822
|
+
for (const key of Object.keys(HIDDEN_TEXTAREA_STYLE)) {
|
|
10823
|
+
node.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], "important");
|
|
10824
|
+
}
|
|
10825
|
+
};
|
|
10826
|
+
const px = (value) => Number.parseFloat(value) || 0;
|
|
10827
|
+
/**
|
|
10828
|
+
* Mide el alto que necesita `node` para su contenido y lo acota entre `minRows` y `maxRows`.
|
|
10829
|
+
* Devuelve `null` si el nodo está fuera del DOM (no hay estilo computado que leer).
|
|
10830
|
+
*/
|
|
10831
|
+
function measureTextareaHeight(node, minRows, maxRows) {
|
|
10832
|
+
// v2 detectaba el nodo desconectado por un `boxSizing` vacío; aquí se pregunta directamente,
|
|
10833
|
+
// y un `boxSizing` vacío en un nodo conectado se trata como `content-box` (el valor inicial).
|
|
10834
|
+
if (!node.isConnected) {
|
|
10835
|
+
return null;
|
|
10836
|
+
}
|
|
10837
|
+
const computed = window.getComputedStyle(node);
|
|
10838
|
+
const isBorderBox = computed.boxSizing === "border-box";
|
|
10839
|
+
const paddingSize = px(computed.paddingTop) + px(computed.paddingBottom);
|
|
10840
|
+
const borderSize = px(computed.borderTopWidth) + px(computed.borderBottomWidth);
|
|
10841
|
+
if (!hiddenTextarea) {
|
|
10842
|
+
hiddenTextarea = document.createElement("textarea");
|
|
10843
|
+
hiddenTextarea.setAttribute("tabindex", "-1");
|
|
10844
|
+
hiddenTextarea.setAttribute("aria-hidden", "true");
|
|
10845
|
+
}
|
|
10846
|
+
if (hiddenTextarea.parentNode === null) {
|
|
10847
|
+
document.body.appendChild(hiddenTextarea);
|
|
10848
|
+
}
|
|
10849
|
+
const clone = hiddenTextarea;
|
|
10850
|
+
const cloneStyle = clone.style;
|
|
10851
|
+
for (const key of SIZING_STYLE) {
|
|
10852
|
+
cloneStyle[key] =
|
|
10853
|
+
computed[key] ?? "";
|
|
10854
|
+
}
|
|
10855
|
+
forceHiddenStyles(clone);
|
|
10856
|
+
const contentOf = () => isBorderBox
|
|
10857
|
+
? clone.scrollHeight + borderSize
|
|
10858
|
+
: clone.scrollHeight - paddingSize;
|
|
10859
|
+
const text = node.value || node.placeholder || "x";
|
|
10860
|
+
clone.value = text;
|
|
10861
|
+
let content = contentOf();
|
|
10862
|
+
// Doble asignación y medida: bug de Firefox que v2 también esquiva (bugzilla 1795904).
|
|
10863
|
+
clone.value = text;
|
|
10864
|
+
content = contentOf();
|
|
10865
|
+
clone.value = "x";
|
|
10866
|
+
const rowHeight = clone.scrollHeight - paddingSize;
|
|
10867
|
+
const extra = isBorderBox ? paddingSize + borderSize : 0;
|
|
10868
|
+
const minHeight = rowHeight * minRows + extra;
|
|
10869
|
+
const maxHeight = rowHeight * maxRows + extra;
|
|
10870
|
+
return {
|
|
10871
|
+
height: Math.min(maxHeight, Math.max(minHeight, content)),
|
|
10872
|
+
rowHeight,
|
|
10873
|
+
overflows: content > maxHeight,
|
|
10874
|
+
};
|
|
10875
|
+
}
|
|
10876
|
+
// `useLayoutEffect` en el servidor avisa en React 18; el alto solo tiene sentido en el cliente.
|
|
10877
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
|
|
10878
|
+
/**
|
|
10879
|
+
* Autocrecimiento del `<textarea>` apuntado por `nodeRef`. Devuelve el `resize` para llamarlo
|
|
10880
|
+
* desde el `onChange` de la fachada.
|
|
10881
|
+
*/
|
|
10882
|
+
function useTextareaAutosize(nodeRef, { enabled, minRows, maxRows }) {
|
|
10883
|
+
const heightRef = $8WNJc$react.useRef(0);
|
|
10884
|
+
const overflowRef = $8WNJc$react.useRef(null);
|
|
10885
|
+
const resize = $8WNJc$react.useCallback(() => {
|
|
10886
|
+
const node = nodeRef.current;
|
|
10887
|
+
if (!(enabled && node)) {
|
|
10888
|
+
return;
|
|
10889
|
+
}
|
|
10890
|
+
const result = measureTextareaHeight(node, minRows, maxRows);
|
|
10891
|
+
if (!result) {
|
|
10892
|
+
return;
|
|
10893
|
+
}
|
|
10894
|
+
if (heightRef.current !== result.height) {
|
|
10895
|
+
heightRef.current = result.height;
|
|
10896
|
+
node.style.setProperty("height", `${result.height}px`, "important");
|
|
10897
|
+
}
|
|
10898
|
+
if (overflowRef.current !== result.overflows) {
|
|
10899
|
+
overflowRef.current = result.overflows;
|
|
10900
|
+
node.style.setProperty("overflow-y", result.overflows ? "auto" : "hidden");
|
|
10901
|
+
}
|
|
10902
|
+
}, [enabled, minRows, maxRows, nodeRef]);
|
|
10903
|
+
// La función más reciente para los listeners, que se registran una sola vez.
|
|
10904
|
+
const resizeRef = $8WNJc$react.useRef(resize);
|
|
10905
|
+
resizeRef.current = resize;
|
|
10906
|
+
// Cada render, como v2: el valor controlado, el `size` o la clase pueden haber cambiado.
|
|
10907
|
+
useIsomorphicLayoutEffect(() => {
|
|
10908
|
+
resize();
|
|
10909
|
+
});
|
|
10910
|
+
// Al apagar el autosize, se retira lo que se escribió a mano para que mande el CSS.
|
|
10911
|
+
useIsomorphicLayoutEffect(() => {
|
|
10912
|
+
const node = nodeRef.current;
|
|
10913
|
+
if (enabled || !node) {
|
|
10914
|
+
return;
|
|
10915
|
+
}
|
|
10916
|
+
node.style.removeProperty("height");
|
|
10917
|
+
node.style.removeProperty("overflow-y");
|
|
10918
|
+
heightRef.current = 0;
|
|
10919
|
+
overflowRef.current = null;
|
|
10920
|
+
}, [enabled, nodeRef]);
|
|
10921
|
+
$8WNJc$react.useEffect(() => {
|
|
10922
|
+
const node = nodeRef.current;
|
|
10923
|
+
if (!(enabled && node)) {
|
|
10924
|
+
return;
|
|
10925
|
+
}
|
|
10926
|
+
const cleanups = [];
|
|
10927
|
+
// Ancho: re-ajuste de línea. Solo cuando cambia el ANCHO, no el alto que ponemos nosotros.
|
|
10928
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
10929
|
+
let lastWidth = node.getBoundingClientRect().width;
|
|
10930
|
+
const observer = new ResizeObserver((entries) => {
|
|
10931
|
+
const width = entries[0]?.contentRect.width ?? lastWidth;
|
|
10932
|
+
if (width !== lastWidth) {
|
|
10933
|
+
lastWidth = width;
|
|
10934
|
+
resizeRef.current();
|
|
10935
|
+
}
|
|
10936
|
+
});
|
|
10937
|
+
observer.observe(node);
|
|
10938
|
+
cleanups.push(() => observer.disconnect());
|
|
10939
|
+
}
|
|
10940
|
+
else {
|
|
10941
|
+
const onWindowResize = () => resizeRef.current();
|
|
10942
|
+
window.addEventListener("resize", onWindowResize);
|
|
10943
|
+
cleanups.push(() => window.removeEventListener("resize", onWindowResize));
|
|
10944
|
+
}
|
|
10945
|
+
const fonts = document.fonts;
|
|
10946
|
+
if (fonts && typeof fonts.addEventListener === "function") {
|
|
10947
|
+
const onFonts = () => resizeRef.current();
|
|
10948
|
+
fonts.addEventListener("loadingdone", onFonts);
|
|
10949
|
+
cleanups.push(() => fonts.removeEventListener("loadingdone", onFonts));
|
|
10950
|
+
}
|
|
10951
|
+
const onReset = (event) => {
|
|
10952
|
+
if (node.form && node.form === event.target) {
|
|
10953
|
+
requestAnimationFrame(() => resizeRef.current());
|
|
10954
|
+
}
|
|
10955
|
+
};
|
|
10956
|
+
document.body.addEventListener("reset", onReset);
|
|
10957
|
+
cleanups.push(() => document.body.removeEventListener("reset", onReset));
|
|
10958
|
+
return () => {
|
|
10959
|
+
for (const cleanup of cleanups) {
|
|
10960
|
+
cleanup();
|
|
10961
|
+
}
|
|
10962
|
+
};
|
|
10963
|
+
}, [enabled, nodeRef]);
|
|
10964
|
+
return resize;
|
|
10965
|
+
}
|
|
10966
|
+
|
|
9376
10967
|
// TECH-2459 · F3 · G1-forms — v3 divide el `Textarea` monolítico de v2 en `TextField` +
|
|
9377
10968
|
// `TextArea` (el `<textarea>` real, sin `InputGroup` intermedio: a diferencia de Input, aquí el
|
|
9378
10969
|
// borde/fondo/tamaño de texto viven todos en el MISMO elemento — ver `textarea.css`). Por eso no
|
|
@@ -9411,6 +11002,8 @@ const FIELD_BORDER_Y = 4;
|
|
|
9411
11002
|
// El default de v2, literal: `({ minRows = 3, maxRows = 8, … })` en `@heroui/input@2.4.22`,
|
|
9412
11003
|
// `dist/chunk-RIE4DNUQ.mjs`.
|
|
9413
11004
|
const DEFAULT_MIN_ROWS = 3;
|
|
11005
|
+
// Mismo literal de v2: `maxRows = 8`. Tope del autocrecimiento cuando el consumidor no lo pasa.
|
|
11006
|
+
const DEFAULT_MAX_ROWS = 8;
|
|
9414
11007
|
// ── `minRows` gana al atributo nativo `rows` ─────────────────────────────────────────────────
|
|
9415
11008
|
//
|
|
9416
11009
|
// Las dos props dicen lo mismo —cuántas líneas de alto tiene la caja al aparecer— así que la
|
|
@@ -9448,7 +11041,7 @@ Math.max(1, Math.round(minRows ?? rows ?? DEFAULT_MIN_ROWS));
|
|
|
9448
11041
|
// esquina. Es el único resto que quedaba entre `textarea--default` y su referencia: 12 píxeles en
|
|
9449
11042
|
// x[780..786] y[407..413], gris `102,102,102` donde v2 tiene el fondo.
|
|
9450
11043
|
const RESIZE_CLASS = "resize-none";
|
|
9451
|
-
const RADIUS_CLASS$
|
|
11044
|
+
const RADIUS_CLASS$2 = {
|
|
9452
11045
|
none: "rounded-none",
|
|
9453
11046
|
sm: "rounded-[8px]",
|
|
9454
11047
|
md: "rounded-[12px]",
|
|
@@ -9566,7 +11159,12 @@ const INVALID_TEXT_CLASS = "text-danger";
|
|
|
9566
11159
|
const Textarea = $8WNJc$react.forwardRef(({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md",
|
|
9567
11160
|
// Sin default en el destructuring a propósito: hace falta distinguir "no me la han pasado"
|
|
9568
11161
|
// de un valor, para que `rows` solo se mire cuando `minRows` no viene (ver `resolveRows`).
|
|
9569
|
-
minRows, rows,
|
|
11162
|
+
minRows, rows,
|
|
11163
|
+
// Tope del autocrecimiento, default de v2 (8). Se desestructura también para que no
|
|
11164
|
+
// llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
|
|
11165
|
+
maxRows = DEFAULT_MAX_ROWS,
|
|
11166
|
+
// Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
|
|
11167
|
+
disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
9570
11168
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
9571
11169
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
9572
11170
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -9577,7 +11175,32 @@ minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled =
|
|
|
9577
11175
|
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
|
|
9578
11176
|
const generatedId = $8WNJc$react.useId();
|
|
9579
11177
|
const textareaId = id || generatedId;
|
|
9580
|
-
const
|
|
11178
|
+
const requestedRows = resolveRows(minRows, rows);
|
|
11179
|
+
const maxFieldRows = Math.max(1, Math.round(maxRows));
|
|
11180
|
+
// Con autocrecimiento, `maxRows` es el techo también del mínimo: v2 acotaba
|
|
11181
|
+
// `min(máx, max(mín, contenido))`, así que con `maxRows < minRows` ganaba `maxRows`. Sin
|
|
11182
|
+
// autocrecimiento `maxRows` no pinta nada (v2 ni se lo pasaba al `<textarea>` plano).
|
|
11183
|
+
const fieldRows = disableAutosize
|
|
11184
|
+
? requestedRows
|
|
11185
|
+
: Math.min(requestedRows, maxFieldRows);
|
|
11186
|
+
const innerRef = $8WNJc$react.useRef(null);
|
|
11187
|
+
const setRefs = $8WNJc$react.useCallback((node) => {
|
|
11188
|
+
innerRef.current = node;
|
|
11189
|
+
if (typeof ref === "function") {
|
|
11190
|
+
ref(node);
|
|
11191
|
+
}
|
|
11192
|
+
else if (ref) {
|
|
11193
|
+
ref.current = node;
|
|
11194
|
+
}
|
|
11195
|
+
}, [ref]);
|
|
11196
|
+
const resize = useTextareaAutosize(innerRef, {
|
|
11197
|
+
enabled: !disableAutosize,
|
|
11198
|
+
minRows: fieldRows,
|
|
11199
|
+
maxRows: maxFieldRows,
|
|
11200
|
+
});
|
|
11201
|
+
// El `min-height` se emite aunque haya autocrecimiento: es el alto correcto ANTES de la
|
|
11202
|
+
// primera medida (SSR, o sin hoja de estilos) y el suelo si la medida sale menor (p. ej.
|
|
11203
|
+
// `flat`, sin los 4px de borde). El `height` lo escribe después `useTextareaAutosize`.
|
|
9581
11204
|
// Inline y no una clase: el valor es un número arbitrario y Tailwind 4 solo emite las clases
|
|
9582
11205
|
// que encuentra escritas en el fuente, así que `min-h-[${n}px]` no existiría en el CSS. El
|
|
9583
11206
|
// `style` del consumidor va DESPUÉS para que pueda pisarlo (y una clase con `!` también gana,
|
|
@@ -9586,14 +11209,32 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
9586
11209
|
minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
|
|
9587
11210
|
...style,
|
|
9588
11211
|
};
|
|
11212
|
+
// El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
|
|
11213
|
+
// `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
|
|
11214
|
+
// el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
|
|
9589
11215
|
const handleChange = (event) => {
|
|
11216
|
+
resize();
|
|
9590
11217
|
onChange?.(event);
|
|
9591
11218
|
onValueChange?.(event.target.value);
|
|
9592
11219
|
};
|
|
9593
|
-
return (
|
|
11220
|
+
return (
|
|
11221
|
+
// ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
|
|
11222
|
+
//
|
|
11223
|
+
// En react-aria el dueño del valor es el `TextField`: `useTextField` guarda el estado con
|
|
11224
|
+
// `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<textarea>` como
|
|
11225
|
+
// `value` por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la
|
|
11226
|
+
// fachada se los pasaba al `<textarea>`, así que:
|
|
11227
|
+
// · en modo NO controlado el `TextField` arrancaba con "" y el `<textarea>` recibía a la
|
|
11228
|
+
// vez `value=""` (del contexto) y `defaultValue` → React avisaba de «both value and
|
|
11229
|
+
// defaultValue» y el primer re-render del padre dejaba el campo VACÍO;
|
|
11230
|
+
// · en modo controlado funcionaba de rebote (`mergeProps` deja ganar al `value` propio),
|
|
11231
|
+
// pero el estado interno del `TextField` seguía en "".
|
|
11232
|
+
// Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
|
|
11233
|
+
// es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
|
|
11234
|
+
jsxRuntime.jsxs(react.TextField, { id: textareaId, value: value, defaultValue: defaultValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
|
|
9594
11235
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
9595
11236
|
// v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
|
|
9596
|
-
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsx(react.TextArea, { ...rest, ref:
|
|
11237
|
+
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, className: react.cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
|
|
9597
11238
|
// Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
|
|
9598
11239
|
// y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
|
|
9599
11240
|
"w-full", classNames?.input ?? classNames?.inputWrapper) }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
|
|
@@ -13855,10 +15496,12 @@ const createCurrencyFormatter = (locale, currency, fractionDigits = 0) => {
|
|
|
13855
15496
|
return (val) => formatter.format(val);
|
|
13856
15497
|
};
|
|
13857
15498
|
|
|
15499
|
+
const ModalFacadeContext = $8WNJc$react.createContext({});
|
|
15500
|
+
|
|
13858
15501
|
// v2 no tiene prop `radius` en el diálogo del modal: v3 tampoco (eliminada, "usar Tailwind"), así
|
|
13859
15502
|
// que se reimplementa vía className arbitrario — mismo criterio y mismos valores exactos que
|
|
13860
15503
|
// Button/Card de F3.0 (RADIUS_CLASS).
|
|
13861
|
-
const RADIUS_CLASS$
|
|
15504
|
+
const RADIUS_CLASS$1 = {
|
|
13862
15505
|
none: "rounded-none",
|
|
13863
15506
|
sm: "rounded-[8px]",
|
|
13864
15507
|
md: "rounded-[12px]",
|
|
@@ -13916,39 +15559,170 @@ const MAX_WIDTH_CLASS = {
|
|
|
13916
15559
|
* `ModalContent` tenga que ser hijo directo de `Modal.Dialog`.
|
|
13917
15560
|
*/
|
|
13918
15561
|
const ModalCloseContext = $8WNJc$react.createContext(() => { });
|
|
15562
|
+
/**
|
|
15563
|
+
* Atributos que el `Dialog` de react-aria SÍ deja pasar a su `<section>`: `id`, `role` (vía
|
|
15564
|
+
* `useDialog`), `data-*` y los cuatro "labelable" (`filterDOMProps(props, { labelable: true })`
|
|
15565
|
+
* en `react-aria/dist/private/utils/filterDOMProps.mjs`). Estos van como props para que
|
|
15566
|
+
* react-aria los vea — sobre todo `aria-label`/`aria-labelledby`, que deciden si avisa de un
|
|
15567
|
+
* diálogo sin nombre.
|
|
15568
|
+
*/
|
|
15569
|
+
const LABELABLE_PROPS = new Set([
|
|
15570
|
+
"aria-label",
|
|
15571
|
+
"aria-labelledby",
|
|
15572
|
+
"aria-describedby",
|
|
15573
|
+
"aria-details",
|
|
15574
|
+
]);
|
|
15575
|
+
/**
|
|
15576
|
+
* Reparte el resto de props de `Modal` entre las dos vías de llegada al diálogo. En v2 TODAS
|
|
15577
|
+
* llegaban (`mergeProps(modalProps, otherProps, …)` sobre el `<section>`, sin filtrar); en v3 el
|
|
15578
|
+
* `Dialog` filtra, así que un `aria-busy` se perdería en silencio si solo fuera como prop. Lo que
|
|
15579
|
+
* no es `aria-*`/`data-*`/`id`/`role` se descarta: `ModalProps` no lo declara y v2 lo habría
|
|
15580
|
+
* volcado al DOM con el aviso de React de atributo desconocido.
|
|
15581
|
+
*/
|
|
15582
|
+
const splitDialogDOMProps = (rest) => {
|
|
15583
|
+
const dialogProps = {};
|
|
15584
|
+
const extraAria = {};
|
|
15585
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
15586
|
+
// Un `aria-labelledby={undefined}` explícito no debe borrar el nombre por defecto.
|
|
15587
|
+
if (value === undefined) {
|
|
15588
|
+
continue;
|
|
15589
|
+
}
|
|
15590
|
+
if (key === "id" ||
|
|
15591
|
+
key === "role" ||
|
|
15592
|
+
key.startsWith("data-") ||
|
|
15593
|
+
LABELABLE_PROPS.has(key)) {
|
|
15594
|
+
dialogProps[key] = value;
|
|
15595
|
+
}
|
|
15596
|
+
else if (key.startsWith("aria-")) {
|
|
15597
|
+
extraAria[key] = value;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
return {
|
|
15601
|
+
dialogProps: dialogProps,
|
|
15602
|
+
extraAria: extraAria,
|
|
15603
|
+
};
|
|
15604
|
+
};
|
|
13919
15605
|
/**
|
|
13920
15606
|
* Modal - fachada BeweOS sobre el `Modal` compound de HeroUI v3.
|
|
13921
15607
|
*
|
|
13922
15608
|
* TECH-2459 · G2-overlays-nav — v3 convierte `Modal` en compound (`Modal.Backdrop/Container/
|
|
13923
15609
|
* Dialog/Header/Body/Footer/CloseTrigger`, ver `upload-file/UploadFile.tsx` D-G1-7 y la guía
|
|
13924
15610
|
* oficial `modal`). Esta fachada arma esa anatomía por dentro pero conserva la API PLANA de v2
|
|
13925
|
-
* (`isOpen`/`onOpenChange`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
|
|
13926
|
-
* `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`),
|
|
13927
|
-
*
|
|
15611
|
+
* (`isOpen`/`onOpenChange`/`onClose`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
|
|
15612
|
+
* `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), más
|
|
15613
|
+
* `classNames` por slot y `aria-*`/`data-*`/`id`/`role` al diálogo, para que el código ya escrito
|
|
15614
|
+
* en las 4 apps consumidoras siga funcionando sin cambios.
|
|
13928
15615
|
*
|
|
13929
15616
|
* Accesibilidad (criterio de aceptación de la issue, verificado con Playwright): el foco entra al
|
|
13930
15617
|
* abrir, queda atrapado dentro del diálogo, Escape cierra (salvo `isKeyboardDismissDisabled`), el
|
|
13931
15618
|
* foco vuelve al disparador al cerrar y la página no hace scroll por detrás — los cuatro los
|
|
13932
15619
|
* gestiona react-aria-components internamente (`ModalOverlay`/`Dialog`), sin código propio.
|
|
13933
15620
|
*/
|
|
13934
|
-
const Modal = ({ isOpen, defaultOpen, onOpenChange, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
|
|
13935
|
-
|
|
15621
|
+
const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, classNames, children, ...rest }) => {
|
|
15622
|
+
// v2 aceptaba `onClose` además de `onOpenChange` y lo llamaba en cada cierre; v3 solo conoce
|
|
15623
|
+
// `onOpenChange`. Sin este puente, un modal controlado con `isOpen` + `onClose` (el patrón
|
|
15624
|
+
// v2 dominante en las apps: ~118 usos en beweossmbs frente a 8 de `onOpenChange`) no se
|
|
15625
|
+
// cerraba con la X, el backdrop ni Escape, porque nadie escuchaba `onOpenChange(false)`.
|
|
15626
|
+
//
|
|
15627
|
+
// Basta con engancharse aquí: TODAS las vías de cierre acaban en este `onOpenChange` del
|
|
15628
|
+
// `ModalOverlay`. La X (`Modal.CloseTrigger`, `slot="close"`) y el `close` del render-prop de
|
|
15629
|
+
// `Modal.Dialog` —el que `ModalCloseContext` pasa a `ModalContent`— llaman a `state.close()`
|
|
15630
|
+
// del estado del overlay, y Escape/backdrop también; ese estado (`useOverlayTriggerState` →
|
|
15631
|
+
// `useControlledState`) solo notifica cuando el valor cambia, así que `onClose` se dispara
|
|
15632
|
+
// una vez por cierre y ninguna al abrir.
|
|
15633
|
+
const handleOpenChange = (open) => {
|
|
15634
|
+
onOpenChange?.(open);
|
|
15635
|
+
if (!open) {
|
|
15636
|
+
onClose?.();
|
|
15637
|
+
}
|
|
15638
|
+
};
|
|
15639
|
+
// v2 nombraba el diálogo con su cabecera (`aria-labelledby` = id del `ModalHeader`,
|
|
15640
|
+
// `getDialogProps` de `@heroui/modal@2.2.19`); en v3 solo lo hace `Modal.Heading`, que las apps
|
|
15641
|
+
// no usan. Mismo arreglo que `Drawer`: `ModalHeader` pone este id en su nodo.
|
|
15642
|
+
//
|
|
15643
|
+
// Si el consumidor trae su propio `aria-label`/`aria-labelledby` (3 `<Modal>` de beweossmbs,
|
|
15644
|
+
// p. ej. `create-note-modal`), manda el suyo. Aquí v3 mejora a v2: v2 escribía
|
|
15645
|
+
// `aria-labelledby` DESPUÉS del spread, así que pisaba siempre el del consumidor.
|
|
15646
|
+
const headerId = $8WNJc$react.useId();
|
|
15647
|
+
// Props de `ModalContent` (registradas desde dentro del diálogo, ver `ModalContent.tsx`). En
|
|
15648
|
+
// v2 `ModalContent` era el diálogo: `getDialogProps(mergeProps(dialogProps, otherProps))`
|
|
15649
|
+
// (`@heroui/modal@2.2.19`, `chunk-UKDXCIYN.mjs`), y dentro de `getDialogProps`
|
|
15650
|
+
// (`chunk-Z2LRST4N.mjs`):
|
|
15651
|
+
// - atributos: `mergeProps(modalProps, <resto de Modal>, <props de ModalContent>)` →
|
|
15652
|
+
// ModalContent gana a Modal;
|
|
15653
|
+
// - clase: `slots.base({ class: clsx(clsx(classNames.base, className), content.className) })`
|
|
15654
|
+
// → fachada (tv) < `classNames.base` < `className` de Modal < `className` de ModalContent,
|
|
15655
|
+
// con `twMerge`. Es el orden del `cn` de abajo.
|
|
15656
|
+
const [content, setContent] = $8WNJc$react.useState({});
|
|
15657
|
+
const registerContentProps = $8WNJc$react.useCallback((props) => setContent(props ?? {}), []);
|
|
15658
|
+
const { className: contentClassName, ...contentRest } = content;
|
|
15659
|
+
const { dialogProps, extraAria } = splitDialogDOMProps({
|
|
15660
|
+
...rest,
|
|
15661
|
+
...contentRest,
|
|
15662
|
+
});
|
|
15663
|
+
const hasOwnName = dialogProps["aria-label"] !== undefined ||
|
|
15664
|
+
dialogProps["aria-labelledby"] !== undefined;
|
|
15665
|
+
const hasExtraAria = Object.keys(extraAria).length > 0;
|
|
15666
|
+
// Sin `Modal.Root` (el `DialogTrigger` de react-aria) a propósito, igual que `Drawer`: la
|
|
15667
|
+
// fachada no tiene disparador propio —la abre el `isOpen` de la app— y un `DialogTrigger`
|
|
15668
|
+
// sin hijo pulsable avisaba en consola en CADA montaje («A PressResponder was rendered
|
|
15669
|
+
// without a pressable child», medido en jsdom: 9 avisos en `Modal.onClose.test.tsx`).
|
|
15670
|
+
// `Modal.Backdrop` (`ModalOverlay`) ya crea y publica su propio estado de overlay con
|
|
15671
|
+
// `isOpen`/`onOpenChange` —el que usan la X y el `close` del render-prop— y calcula todos los
|
|
15672
|
+
// slots de `modalVariants()` aunque no haya root encima; el root solo aportaba ese aviso y un
|
|
15673
|
+
// `aria-labelledby` de reserva que apuntaba al id de un disparador que no existe.
|
|
15674
|
+
return (jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled,
|
|
15675
|
+
// v2 `classNames.backdrop` → la capa que oscurece la página. Utilidad en
|
|
15676
|
+
// `@layer utilities`, gana a `.modal__backdrop--opaque` (`@layer components`).
|
|
15677
|
+
className: classNames?.backdrop, children: jsxRuntime.jsx(react.Modal.Container, { size: SIZE_MAP[size], placement: placement, scroll: scrollBehavior === "inside" ? "inside" : undefined,
|
|
15678
|
+
// v2 `classNames.wrapper` (el `div` que posiciona el diálogo, `data-slot="wrapper"`)
|
|
15679
|
+
// es el `Modal.Container` de v3: su `p-4 sm:p-10` es lo que pisa un `wrapper="p-0"`.
|
|
15680
|
+
className: classNames?.wrapper, children: jsxRuntime.jsx(react.Modal.Dialog, { ...(hasOwnName ? {} : { "aria-labelledby": headerId }), ...dialogProps,
|
|
15681
|
+
// Los `aria-*` que react-aria filtra (`aria-busy`…) entran por el `render` del
|
|
15682
|
+
// `<section>`: van ANTES de sus props, así que `role`/`aria-modal`/`tabIndex`
|
|
15683
|
+
// de react-aria no se pueden pisar por esta vía.
|
|
15684
|
+
render: hasExtraAria
|
|
15685
|
+
? (sectionProps) => jsxRuntime.jsx("section", { ...extraAria, ...sectionProps })
|
|
15686
|
+
: undefined, className: react.cn(RADIUS_CLASS$1[radius], size !== "full" && MAX_WIDTH_CLASS[size], "px-6 py-9", classNames?.base, className, contentClassName), children: ({ close }) => (jsxRuntime.jsx(ModalDialogBody, { close: close, headerId: headerId, classNames: classNames, registerContentProps: registerContentProps, hideCloseButton: hideCloseButton, children: children })) }) }) }));
|
|
15687
|
+
};
|
|
15688
|
+
/** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
|
|
15689
|
+
const ModalDialogBody = ({ close, headerId, classNames, registerContentProps, hideCloseButton, children, }) => {
|
|
15690
|
+
const value = $8WNJc$react.useMemo(() => ({ headerId, classNames, registerContentProps }), [headerId, classNames, registerContentProps]);
|
|
15691
|
+
return (jsxRuntime.jsx(ModalCloseContext.Provider, { value: close, children: jsxRuntime.jsxs(ModalFacadeContext.Provider, { value: value, children: [!hideCloseButton && (jsxRuntime.jsx(react.Modal.CloseTrigger, { className: classNames?.closeButton })), children] }) }));
|
|
13936
15692
|
};
|
|
13937
15693
|
|
|
15694
|
+
/**
|
|
15695
|
+
* Clave estable de las props registradas: solo primitivos (`className`, `aria-*`, `data-*`…), así
|
|
15696
|
+
* que basta con serializarlas. Evita re-registrar —y re-renderizar el `Modal`— en cada render.
|
|
15697
|
+
*/
|
|
15698
|
+
const keyOf = (props) => JSON.stringify(Object.entries(props)
|
|
15699
|
+
.filter(([, value]) => value !== undefined)
|
|
15700
|
+
.sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
13938
15701
|
/**
|
|
13939
15702
|
* `ModalContent` de v2 no sobrevive en v3 (compound: `Modal.Container`/`Modal.Dialog`, ver D-G1-7
|
|
13940
|
-
* / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es
|
|
13941
|
-
* compatibilidad:
|
|
13942
|
-
* `
|
|
13943
|
-
*
|
|
15703
|
+
* / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es azúcar de
|
|
15704
|
+
* compatibilidad: sus `children` ya se renderizan dentro de `Modal.Dialog` (ver `Modal.tsx`) y, si
|
|
15705
|
+
* `children` es una función, la invoca con el `close` real del diálogo (vía contexto) para que el
|
|
15706
|
+
* patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
|
|
15707
|
+
*
|
|
15708
|
+
* Sus demás props (`className`, `aria-busy`, `data-*`…) NO se pintan aquí: en v2 `ModalContent`
|
|
15709
|
+
* era el propio diálogo, así que se registran en el `Modal` y este las aplica al `role="dialog"`
|
|
15710
|
+
* (antes caían en un `<div>` intermedio y, p. ej., el `h-[95vh] p-0 rounded-2xl` de
|
|
15711
|
+
* `chatbot-config` no llegaba al diálogo). Sin props no se registra nada: el render es el mismo.
|
|
13944
15712
|
*/
|
|
13945
|
-
function ModalContent({ children,
|
|
15713
|
+
function ModalContent({ children, ...dialogProps }) {
|
|
13946
15714
|
const close = $8WNJc$react.useContext(ModalCloseContext);
|
|
13947
|
-
const
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
15715
|
+
const { registerContentProps } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15716
|
+
const key = keyOf(dialogProps);
|
|
15717
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `key` resume `dialogProps` (primitivos); depender del objeto re-registraría en cada render.
|
|
15718
|
+
$8WNJc$react.useLayoutEffect(() => {
|
|
15719
|
+
if (!registerContentProps || key === "[]") {
|
|
15720
|
+
return;
|
|
15721
|
+
}
|
|
15722
|
+
registerContentProps(dialogProps);
|
|
15723
|
+
return () => registerContentProps(undefined);
|
|
15724
|
+
}, [registerContentProps, key]);
|
|
15725
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: typeof children === "function" ? children(close) : children });
|
|
13952
15726
|
}
|
|
13953
15727
|
|
|
13954
15728
|
/**
|
|
@@ -13960,9 +15734,23 @@ function ModalContent({ children, className }) {
|
|
|
13960
15734
|
* era una fila sin gap.
|
|
13961
15735
|
*
|
|
13962
15736
|
* El `className` del consumidor se mezcla con `cn` (tailwind-merge), así que sigue
|
|
13963
|
-
* ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna.
|
|
15737
|
+
* ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna. El slot
|
|
15738
|
+
* `classNames.header` del `<Modal>` se aplica entre medias (gana a la fachada, pierde con el
|
|
15739
|
+
* `className`), y el nodo lleva el `id` que nombra el diálogo, como en v2.
|
|
13964
15740
|
*/
|
|
13965
|
-
const ModalHeader = ({ className, ...props }) =>
|
|
15741
|
+
const ModalHeader = ({ className, ...props }) => {
|
|
15742
|
+
const { headerId, classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15743
|
+
return (jsxRuntime.jsx(react.ModalHeader
|
|
15744
|
+
// Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
|
|
15745
|
+
// propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
|
|
15746
|
+
, {
|
|
15747
|
+
// Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
|
|
15748
|
+
// propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
|
|
15749
|
+
id: headerId, ...props, className: react.cn("flex-row gap-0 px-6 py-4 text-lg font-semibold",
|
|
15750
|
+
// v2 `classNames.header` del `<Modal>`; el `className` propio gana
|
|
15751
|
+
// (`clsx(classNames.header, className)` en `@heroui/modal@2.2.19`).
|
|
15752
|
+
classNames?.header, className) }));
|
|
15753
|
+
};
|
|
13966
15754
|
ModalHeader.displayName = "ModalHeader";
|
|
13967
15755
|
|
|
13968
15756
|
/**
|
|
@@ -13978,8 +15766,9 @@ ModalHeader.displayName = "ModalHeader";
|
|
|
13978
15766
|
* 24px de `px-6` ese margen negativo solo desalineaba el contenido 3px respecto al header.
|
|
13979
15767
|
* `mt-0` anula el `.modal__header + .modal__body { mt-2 }` de v3, que v2 no tenía.
|
|
13980
15768
|
*/
|
|
13981
|
-
const BODY_V2_GEOMETRY = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
|
|
15769
|
+
const BODY_V2_GEOMETRY$1 = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
|
|
13982
15770
|
const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef) => {
|
|
15771
|
+
const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
13983
15772
|
const nodeRef = $8WNJc$react.useRef(null);
|
|
13984
15773
|
const callbackRef = $8WNJc$react.useCallback((node) => {
|
|
13985
15774
|
nodeRef.current = node;
|
|
@@ -14125,19 +15914,217 @@ const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef
|
|
|
14125
15914
|
track.remove();
|
|
14126
15915
|
};
|
|
14127
15916
|
}, []);
|
|
14128
|
-
return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
|
|
15917
|
+
return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
|
|
15918
|
+
// v2 `classNames.body` del `<Modal>` entre la geometría y el `className` propio,
|
|
15919
|
+
// que gana (`clsx(classNames.body, className)` en `@heroui/modal@2.2.19`).
|
|
15920
|
+
className: react.cn(BODY_V2_GEOMETRY$1, classNames?.body, className) }));
|
|
15921
|
+
});
|
|
15922
|
+
ModalBody.displayName = "ModalBody";
|
|
15923
|
+
|
|
15924
|
+
/**
|
|
15925
|
+
* Geometría v2 del slot `footer` del modal (TECH-2459 / F5 · P-15). v2:
|
|
15926
|
+
* `flex flex-row gap-2 px-6 py-4 justify-end` (`@heroui/theme@2.4.15`, slot `footer`).
|
|
15927
|
+
* v3 conserva `flex-row justify-end gap-2` pero pierde el padding, y añade un
|
|
15928
|
+
* `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
|
|
15929
|
+
* con su propio padding) — de ahí el `mt-0`.
|
|
15930
|
+
*/
|
|
15931
|
+
const ModalFooter = ({ className, ...props }) => {
|
|
15932
|
+
const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15933
|
+
return (jsxRuntime.jsx(react.ModalFooter, { ...props,
|
|
15934
|
+
// v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
|
|
15935
|
+
// gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
|
|
15936
|
+
className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
|
|
15937
|
+
};
|
|
15938
|
+
ModalFooter.displayName = "ModalFooter";
|
|
15939
|
+
|
|
15940
|
+
const DrawerFacadeContext = $8WNJc$react.createContext({
|
|
15941
|
+
close: () => {
|
|
15942
|
+
// fuera de un `Drawer` no hay diálogo que cerrar
|
|
15943
|
+
},
|
|
15944
|
+
scrollBehavior: "inside",
|
|
15945
|
+
});
|
|
15946
|
+
|
|
15947
|
+
// Tope del lado corto del panel por tamaño, con los valores EXACTOS de v2 — leídos del oráculo,
|
|
15948
|
+
// no de memoria: `@heroui/theme@2.4.15`, `dist/chunk-EP7KPCL3.mjs` (tv `drawer`) aplica
|
|
15949
|
+
// `max-w-<size>` + `max-h-[<rem>]` al slot `base` (el panel), y el `placement` anula el eje que
|
|
15950
|
+
// no toca (`max-h-[none]` en left/right, `max-w-[none]` en top/bottom). La escala `maxWidth` de
|
|
15951
|
+
// Tailwind 3 resuelve `max-w-<size>` a los mismos rem que el `max-h` literal. Clases literales:
|
|
15952
|
+
// Tailwind solo genera lo que ve escrito entero en el fuente.
|
|
15953
|
+
//
|
|
15954
|
+
// left/right: v2 era `w-full` (slot `base` del modal) + `max-w-<size>`. v3 fija
|
|
15955
|
+
// `w-80 max-w-[85vw] sm:w-96` (320/384px) para cualquier tamaño: sin esto, `size="lg"` medía
|
|
15956
|
+
// 384px donde v2 medía 512px, y `size` no hacía nada. Ganan a `.drawer__dialog[data-placement]`
|
|
15957
|
+
// por capa (`@layer components` < `@layer utilities`), no por especificidad, igual que el
|
|
15958
|
+
// `MAX_WIDTH_CLASS` de `Modal`.
|
|
15959
|
+
const HORIZONTAL_SIZE_CLASS = {
|
|
15960
|
+
xs: "w-full max-w-[20rem]",
|
|
15961
|
+
sm: "w-full max-w-[24rem]",
|
|
15962
|
+
md: "w-full max-w-[28rem]",
|
|
15963
|
+
lg: "w-full max-w-[32rem]",
|
|
15964
|
+
xl: "w-full max-w-[36rem]",
|
|
15965
|
+
"2xl": "w-full max-w-[42rem]",
|
|
15966
|
+
"3xl": "w-full max-w-[48rem]",
|
|
15967
|
+
"4xl": "w-full max-w-[56rem]",
|
|
15968
|
+
"5xl": "w-full max-w-[64rem]",
|
|
15969
|
+
};
|
|
15970
|
+
// top/bottom: v3 ya es `w-full` y topa el alto a `max-h-[85vh]`; v2 lo topaba por tamaño.
|
|
15971
|
+
const VERTICAL_SIZE_CLASS = {
|
|
15972
|
+
xs: "max-h-[20rem]",
|
|
15973
|
+
sm: "max-h-[24rem]",
|
|
15974
|
+
md: "max-h-[28rem]",
|
|
15975
|
+
lg: "max-h-[32rem]",
|
|
15976
|
+
xl: "max-h-[36rem]",
|
|
15977
|
+
"2xl": "max-h-[42rem]",
|
|
15978
|
+
"3xl": "max-h-[48rem]",
|
|
15979
|
+
"4xl": "max-h-[56rem]",
|
|
15980
|
+
"5xl": "max-h-[64rem]",
|
|
15981
|
+
};
|
|
15982
|
+
// v2 `full`: `max-w-full max-h-full h-[100dvh] !rounded-none` en cualquier `placement`.
|
|
15983
|
+
const FULL_SIZE_CLASS = "w-full max-w-full h-[100dvh] max-h-full rounded-none";
|
|
15984
|
+
const sizeClass = (size, placement) => {
|
|
15985
|
+
if (size === "full") {
|
|
15986
|
+
return FULL_SIZE_CLASS;
|
|
15987
|
+
}
|
|
15988
|
+
return placement === "left" || placement === "right"
|
|
15989
|
+
? HORIZONTAL_SIZE_CLASS[size]
|
|
15990
|
+
: VERTICAL_SIZE_CLASS[size];
|
|
15991
|
+
};
|
|
15992
|
+
/**
|
|
15993
|
+
* Drawer - fachada BeweOS sobre el `Drawer` compound de HeroUI v3.
|
|
15994
|
+
*
|
|
15995
|
+
* TECH-2459 — hasta el 23-sep `Drawer`/`DrawerContent`/`DrawerHeader`/`DrawerBody`/`DrawerFooter`
|
|
15996
|
+
* se servían CRUDOS de v3, que es compound y no entiende la API plana de v2: `isOpen`/`onClose`
|
|
15997
|
+
* no llegaban a ningún overlay, así que los drawers de las apps no montaban o no cerraban. Esta
|
|
15998
|
+
* fachada arma la anatomía de v3 por dentro (`Drawer` → `Backdrop` → `Content` → `Dialog`) y
|
|
15999
|
+
* conserva la API de v2 (`isOpen`/`defaultOpen`/`onOpenChange`/`onClose`/`placement`/`size`/
|
|
16000
|
+
* `backdrop`/`scrollBehavior`/`isDismissable`/`isKeyboardDismissDisabled`/`hideCloseButton` +
|
|
16001
|
+
* `<DrawerContent>{(onClose) => ...}</DrawerContent>`), con los defaults de v2 (`useDrawer` de
|
|
16002
|
+
* `@heroui/drawer@2.2.29`: `placement="right"`, `size="md"`, `scrollBehavior="inside"`).
|
|
16003
|
+
*
|
|
16004
|
+
* Lo que NO se repone a propósito (criterio de la migración: adaptarse a v3, no identidad de
|
|
16005
|
+
* píxel): fondo, sombra, radio y posición de la X son los de v3. Sí se repone la geometría de la
|
|
16006
|
+
* que depende el contenido de las apps: el ancho/alto por `size`, el padding de los slots
|
|
16007
|
+
* (`DrawerHeader`/`Body`/`Footer`) y el panel sin padding propio, porque las apps ya compensan
|
|
16008
|
+
* sobre esos valores (p. ej. `DrawerHeader className="pt-6"`).
|
|
16009
|
+
*
|
|
16010
|
+
* La anatomía v3 completa queda publicada como estáticos (`Drawer.Backdrop`, `Drawer.Content`,
|
|
16011
|
+
* `Drawer.Dialog`, …): son los MISMOS objetos que exporta `@heroui/react`.
|
|
16012
|
+
*/
|
|
16013
|
+
const DrawerFacade = ({ isOpen, defaultOpen, onOpenChange, onClose, placement = "right", size = "md", backdrop = "opaque", scrollBehavior = "inside", isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
|
|
16014
|
+
// Mismo puente que `Modal` (f3ce18c): v3 solo conoce `onOpenChange`. Todas las vías de cierre
|
|
16015
|
+
// (la X —`Drawer.CloseTrigger`, `slot="close"`—, el `close` del render-prop de
|
|
16016
|
+
// `Drawer.Dialog`, Escape, backdrop y el arrastre de v3) acaban en `state.close()` del
|
|
16017
|
+
// overlay, que solo notifica cuando el valor cambia: `onClose` sale una vez por cierre y
|
|
16018
|
+
// ninguna al abrir.
|
|
16019
|
+
// v2 nombraba el diálogo con su cabecera (`ModalContent` → `aria-labelledby` = id del
|
|
16020
|
+
// `ModalHeader`). En v3 solo lo hace `Drawer.Heading`, que las apps no usan: sin esto el
|
|
16021
|
+
// panel quedaba sin nombre accesible y react-aria avisaba en consola en cada apertura («A
|
|
16022
|
+
// dialog must have a title for accessibility», medido en jsdom). `DrawerHeader` pone este id.
|
|
16023
|
+
const headerId = $8WNJc$react.useId();
|
|
16024
|
+
const handleOpenChange = (open) => {
|
|
16025
|
+
onOpenChange?.(open);
|
|
16026
|
+
if (!open) {
|
|
16027
|
+
onClose?.();
|
|
16028
|
+
}
|
|
16029
|
+
};
|
|
16030
|
+
// Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito: la fachada no tiene
|
|
16031
|
+
// disparador propio —lo abre el `isOpen` de la app— y un `DialogTrigger` sin hijo pulsable
|
|
16032
|
+
// avisa en consola en CADA montaje («A PressResponder was rendered without a pressable
|
|
16033
|
+
// child», medido en jsdom; `Modal` lo sigue emitiendo). `Drawer.Backdrop` (`ModalOverlay`)
|
|
16034
|
+
// ya crea y publica su propio estado de overlay con `isOpen`/`onOpenChange`, y trae todos los
|
|
16035
|
+
// slots de `drawerVariants()`, así que el root no aportaba nada.
|
|
16036
|
+
return (jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled, children: jsxRuntime.jsx(react.Drawer.Content, { placement: placement, children: jsxRuntime.jsx(react.Drawer.Dialog, { "aria-labelledby": headerId, className: react.cn(sizeClass(size, placement), "p-0", scrollBehavior === "outside" && "overflow-y-auto", className), children: ({ close }) => (jsxRuntime.jsx(DrawerDialogBody, { close: close, headerId: headerId, scrollBehavior: scrollBehavior, hideCloseButton: hideCloseButton, children: children })) }) }) }));
|
|
16037
|
+
};
|
|
16038
|
+
/** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
|
|
16039
|
+
const DrawerDialogBody = ({ close, headerId, scrollBehavior, hideCloseButton, children }) => {
|
|
16040
|
+
const value = $8WNJc$react.useMemo(() => ({ close, headerId, scrollBehavior }), [close, headerId, scrollBehavior]);
|
|
16041
|
+
return (jsxRuntime.jsxs(DrawerFacadeContext.Provider, { value: value, children: [!hideCloseButton && jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), children] }));
|
|
16042
|
+
};
|
|
16043
|
+
DrawerFacade.displayName = "Aurora.Drawer";
|
|
16044
|
+
/**
|
|
16045
|
+
* `Drawer` de Aurora: fachada v2 + estáticos compound de v3 (los mismos objetos que exporta
|
|
16046
|
+
* `@heroui/react`), para quien quiera montar la anatomía de v3 a mano.
|
|
16047
|
+
*
|
|
16048
|
+
* ⚠️ `Drawer.Content`/`Drawer.Header`/`Drawer.Body`/`Drawer.Footer` son las piezas CRUDAS de v3
|
|
16049
|
+
* (sin la geometría v2 ni el render-prop): no son los `DrawerContent`/`DrawerHeader`/… planos de
|
|
16050
|
+
* esta librería. No se mezclan las dos formas en el mismo drawer.
|
|
16051
|
+
*/
|
|
16052
|
+
const Drawer = Object.assign(DrawerFacade, {
|
|
16053
|
+
Root: react.Drawer.Root,
|
|
16054
|
+
Trigger: react.Drawer.Trigger,
|
|
16055
|
+
Backdrop: react.Drawer.Backdrop,
|
|
16056
|
+
Content: react.Drawer.Content,
|
|
16057
|
+
Dialog: react.Drawer.Dialog,
|
|
16058
|
+
Header: react.Drawer.Header,
|
|
16059
|
+
Heading: react.Drawer.Heading,
|
|
16060
|
+
Body: react.Drawer.Body,
|
|
16061
|
+
Footer: react.Drawer.Footer,
|
|
16062
|
+
Handle: react.Drawer.Handle,
|
|
16063
|
+
CloseTrigger: react.Drawer.CloseTrigger,
|
|
14129
16064
|
});
|
|
14130
|
-
ModalBody.displayName = "ModalBody";
|
|
14131
16065
|
|
|
14132
16066
|
/**
|
|
14133
|
-
*
|
|
14134
|
-
*
|
|
14135
|
-
*
|
|
14136
|
-
*
|
|
14137
|
-
*
|
|
16067
|
+
* `DrawerContent` de v2 (en v2 era el mismo `ModalContent`). En v3 `Drawer.Content` es el
|
|
16068
|
+
* posicionador del overlay, que la fachada `Drawer` ya monta por dentro; este componente NO lo
|
|
16069
|
+
* envuelve: es azúcar de compatibilidad. Si `children` es una función, la invoca con el `close`
|
|
16070
|
+
* real del diálogo (vía contexto) para que el patrón v2 `{(onClose) => <>...</>}` siga
|
|
16071
|
+
* funcionando (6 de los 12 drawers de las apps lo usan, medido el 23-sep).
|
|
14138
16072
|
*/
|
|
14139
|
-
|
|
14140
|
-
|
|
16073
|
+
function DrawerContent({ children, className }) {
|
|
16074
|
+
const { close } = $8WNJc$react.useContext(DrawerFacadeContext);
|
|
16075
|
+
const content = typeof children === "function" ? children(close) : children;
|
|
16076
|
+
if (!className) {
|
|
16077
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
|
|
16078
|
+
}
|
|
16079
|
+
return jsxRuntime.jsx("div", { className: className, children: content });
|
|
16080
|
+
}
|
|
16081
|
+
DrawerContent.displayName = "Aurora.DrawerContent";
|
|
16082
|
+
|
|
16083
|
+
/**
|
|
16084
|
+
* Geometría v2 del slot `header`, reintroducida sobre `.drawer__header` de v3. v2 (el drawer
|
|
16085
|
+
* reusaba los slots del modal): `flex py-4 px-6 flex-initial text-large font-semibold`
|
|
16086
|
+
* (`@heroui/theme@2.4.15`, `chunk-YXVO2HGK.mjs`, slot `header`; `text-large` → `text-lg`).
|
|
16087
|
+
* `.drawer__header` de v3 no tiene padding ni tipografía y apila en columna con `gap-3`; mismo
|
|
16088
|
+
* arreglo que `ModalHeader`.
|
|
16089
|
+
*
|
|
16090
|
+
* El `className` del consumidor se mezcla con `cn` (tailwind-merge) en última posición, así que
|
|
16091
|
+
* sigue ganando: `className="flex flex-col gap-1"` mantiene su columna y `pt-6` pisa el `py-4`.
|
|
16092
|
+
*
|
|
16093
|
+
* Lleva el `id` que el panel usa como `aria-labelledby`: la cabecera NOMBRA el diálogo, como en
|
|
16094
|
+
* v2 (ver `Drawer.tsx`).
|
|
16095
|
+
*/
|
|
16096
|
+
const DrawerHeader = ({ className, children }) => {
|
|
16097
|
+
const { headerId } = $8WNJc$react.useContext(DrawerFacadeContext);
|
|
16098
|
+
return (jsxRuntime.jsx(react.DrawerHeader, { id: headerId, className: react.cn("flex-initial flex-row gap-0 px-6 py-4 text-lg font-semibold", className), children: children }));
|
|
16099
|
+
};
|
|
16100
|
+
DrawerHeader.displayName = "Aurora.DrawerHeader";
|
|
16101
|
+
|
|
16102
|
+
/**
|
|
16103
|
+
* Geometría v2 del slot `body`: `flex flex-1 flex-col gap-3 px-6 py-2` (`@heroui/theme@2.4.15`,
|
|
16104
|
+
* `chunk-YXVO2HGK.mjs`, slot `body`). `.drawer__body` de v3 es un bloque sin padding ni gap
|
|
16105
|
+
* (`-m-[3px] my-0 p-[3px]`), así que los campos de un formulario quedarían pegados y a ras del
|
|
16106
|
+
* borde. `mx-0` anula el margen negativo de v3 y `mt-0` el `.drawer__header + .drawer__body
|
|
16107
|
+
* { mt-2 }`, que v2 no tenía (mismo arreglo que `ModalBody`).
|
|
16108
|
+
*
|
|
16109
|
+
* Scroll: con `scrollBehavior="inside"` (default v2 del drawer) el cuerpo hace scroll, que es lo
|
|
16110
|
+
* que ya trae `.drawer__body` de v3. Con `outside` el que hace scroll es el panel, así que el
|
|
16111
|
+
* cuerpo lo suelta (`overflow-visible`) y deja de encogerse (`flex-none`).
|
|
16112
|
+
*/
|
|
16113
|
+
const BODY_V2_GEOMETRY = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
|
|
16114
|
+
const DrawerBody = ({ className, children }) => {
|
|
16115
|
+
const { scrollBehavior } = $8WNJc$react.useContext(DrawerFacadeContext);
|
|
16116
|
+
return (jsxRuntime.jsx(react.DrawerBody, { className: react.cn(BODY_V2_GEOMETRY, scrollBehavior === "outside" && "flex-none overflow-visible", className), children: children }));
|
|
16117
|
+
};
|
|
16118
|
+
DrawerBody.displayName = "Aurora.DrawerBody";
|
|
16119
|
+
|
|
16120
|
+
/**
|
|
16121
|
+
* Geometría v2 del slot `footer`: `flex flex-row gap-2 px-6 py-4 justify-end`
|
|
16122
|
+
* (`@heroui/theme@2.4.15`, slot `footer`). v3 conserva `flex-row justify-end gap-2` pero pierde
|
|
16123
|
+
* el padding y añade `.drawer__body + .drawer__footer { mt-5 }`, que v2 no tenía — de ahí el
|
|
16124
|
+
* `mt-0` (mismo arreglo que `ModalFooter`).
|
|
16125
|
+
*/
|
|
16126
|
+
const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 px-6 py-4", className), children: children }));
|
|
16127
|
+
DrawerFooter.displayName = "Aurora.DrawerFooter";
|
|
14141
16128
|
|
|
14142
16129
|
// TECH-2459 — Fachada `Alert` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Alert` CRUDO de
|
|
14143
16130
|
// v3 y solo shimeaba el TIPO `AlertProps` v2 → el JSX v2 compilaba pero el componente era v3).
|
|
@@ -14181,7 +16168,7 @@ const COLOR_BY_STATUS = {
|
|
|
14181
16168
|
};
|
|
14182
16169
|
// v3 usa `min(32px, var(--radius-3xl))`; v2 `radius="md"` = `rounded-medium` = 12px. Mismos
|
|
14183
16170
|
// valores EXACTOS que Button/Card/Chip (los `rounded-md`/`lg` de Tailwind son 6/8px, no 12/14).
|
|
14184
|
-
const RADIUS_CLASS
|
|
16171
|
+
const RADIUS_CLASS = {
|
|
14185
16172
|
none: "rounded-none",
|
|
14186
16173
|
sm: "rounded-[8px]",
|
|
14187
16174
|
md: "rounded-[12px]",
|
|
@@ -14317,291 +16304,192 @@ function slotClasses(variant, color) {
|
|
|
14317
16304
|
description: defaultDescription,
|
|
14318
16305
|
iconWrapper: `${ICON_WRAPPER_SHADOW} ${BORDERED_ICON_WRAPPER[color]}`,
|
|
14319
16306
|
closeButton: BORDERED_CLOSE[color],
|
|
14320
|
-
};
|
|
14321
|
-
case "faded":
|
|
14322
|
-
return {
|
|
14323
|
-
base: `border ${FLAT_BASE[color]} ${FADED_BORDER[color]}`,
|
|
14324
|
-
description: defaultDescription,
|
|
14325
|
-
iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
|
|
14326
|
-
closeButton: FLAT_CLOSE[color],
|
|
14327
|
-
};
|
|
14328
|
-
default:
|
|
14329
|
-
return {
|
|
14330
|
-
base: FLAT_BASE[color],
|
|
14331
|
-
description: defaultDescription,
|
|
14332
|
-
iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
|
|
14333
|
-
closeButton: FLAT_CLOSE[color],
|
|
14334
|
-
};
|
|
14335
|
-
}
|
|
14336
|
-
}
|
|
14337
|
-
// Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
|
|
14338
|
-
// v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
|
|
14339
|
-
const COMPOUND_PARTS$1 = new Set([
|
|
14340
|
-
react.Alert.Indicator,
|
|
14341
|
-
react.Alert.Content,
|
|
14342
|
-
react.Alert.Title,
|
|
14343
|
-
react.Alert.Description,
|
|
14344
|
-
]);
|
|
14345
|
-
/**
|
|
14346
|
-
* Detecta la API compound de v3: si algún hijo directo es `Alert.Indicator`/`Content`/`Title`/
|
|
14347
|
-
* `Description`, el consumidor ya compone la estructura y la fachada NO debe envolver nada (si lo
|
|
14348
|
-
* hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
|
|
14349
|
-
* API v2: children bajo la descripción, con icono y círculo, como en v2.
|
|
14350
|
-
*/
|
|
14351
|
-
function hasCompoundChildren$1(children) {
|
|
14352
|
-
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
|
|
14353
|
-
}
|
|
14354
|
-
/** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
|
|
14355
|
-
function hasValue$1(node) {
|
|
14356
|
-
return node !== null && node !== undefined && node !== "" && node !== false;
|
|
14357
|
-
}
|
|
14358
|
-
/**
|
|
14359
|
-
* Alert - Aviso contextual basado en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
|
|
14360
|
-
*
|
|
14361
|
-
* Dos modos, decididos por los `children`:
|
|
14362
|
-
* - **API v2** (`color`/`variant`/`title`/`description`/`isClosable`…): la fachada compone el
|
|
14363
|
-
* compound de v3 y repone `variant` (fondo teñido en `flat`), `radius`, el círculo del icono,
|
|
14364
|
-
* el botón de cerrar (`CloseButton` de v3 con `aria-label` traducido) y la visibilidad.
|
|
14365
|
-
* - **API compound v3** (`<Alert status="danger"><Alert.Indicator/><Alert.Content>…`): pasa tal
|
|
14366
|
-
* cual al `Alert` de HeroUI; `color`, si viene, se traduce a `status`.
|
|
14367
|
-
*/
|
|
14368
|
-
const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, description, icon, hideIcon = false, hideIconWrapper = false, isClosable = false, onClose, isVisible: isVisibleProp, isDefaultVisible, onVisibleChange, startContent, endContent, classNames, className, children, translations = {}, ...rest }) => {
|
|
14369
|
-
// `useControlledState` de v2 (`use-alert.ts`): controlado si llega `isVisible`; si no, estado
|
|
14370
|
-
// propio que arranca en `isDefaultVisible ?? true` y se apaga al cerrar.
|
|
14371
|
-
const [uncontrolledVisible, setUncontrolledVisible] = $8WNJc$react.useState(isDefaultVisible ?? true);
|
|
14372
|
-
const isControlled = isVisibleProp !== undefined;
|
|
14373
|
-
const isVisible = isControlled ? isVisibleProp : uncontrolledVisible;
|
|
14374
|
-
const t = { ...DEFAULT_TRANSLATIONS, ...translations };
|
|
14375
|
-
// `status` (v3) gana sobre `color` (v2) para la clase de estado de HeroUI; y si SOLO llega
|
|
14376
|
-
// `status`, las utilidades de Aurora siguen el color equivalente (`accent` → `primary`).
|
|
14377
|
-
const effectiveColor = color ?? (status ? COLOR_BY_STATUS[status] : "default");
|
|
14378
|
-
const effectiveStatus = status ?? STATUS_BY_COLOR[effectiveColor];
|
|
14379
|
-
if (!isVisible) {
|
|
14380
|
-
return null;
|
|
14381
|
-
}
|
|
14382
|
-
if (hasCompoundChildren$1(children)) {
|
|
14383
|
-
return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
|
|
14384
|
-
}
|
|
14385
|
-
const handleClose = () => {
|
|
14386
|
-
if (!isControlled) {
|
|
14387
|
-
setUncontrolledVisible(false);
|
|
14388
|
-
}
|
|
14389
|
-
onVisibleChange?.(false);
|
|
14390
|
-
onClose?.();
|
|
14391
|
-
};
|
|
14392
|
-
const slots = slotClasses(variant, effectiveColor);
|
|
14393
|
-
const hasTitle = hasValue$1(title);
|
|
14394
|
-
const hasDescription = hasValue$1(description);
|
|
14395
|
-
// `hasContent` de v2: sin descripción ni children, el título se centra verticalmente con el icono.
|
|
14396
|
-
const hasContent = hasDescription || hasValue$1(children);
|
|
14397
|
-
const showClose = isClosable || Boolean(onClose);
|
|
14398
|
-
return (jsxRuntime.jsxs(react.Alert, { role: "alert", "data-visible": "true", "data-closeable": showClose ? "true" : undefined, "data-has-title": hasTitle ? "true" : undefined, "data-has-description": hasDescription ? "true" : undefined, ...rest, status: effectiveStatus,
|
|
14399
|
-
// Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
|
|
14400
|
-
// flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
|
|
14401
|
-
// `shadow-surface` (v2 no tenía sombra en el base) y se aplica variant/radius.
|
|
14402
|
-
className: react.cn("grow gap-x-1 shadow-none", slots.base, RADIUS_CLASS$1[radius], hideIconWrapper && "gap-x-0", classNames?.base, className), children: [startContent, !hideIcon && (jsxRuntime.jsx(react.Alert.Indicator, { className: react.cn(ICON_WRAPPER_BASE,
|
|
14403
|
-
// v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
|
|
14404
|
-
// las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
|
|
14405
|
-
// `dark:bg-*` (grupo distinto para tailwind-merge).
|
|
14406
|
-
hideIconWrapper ? "bg-transparent" : slots.iconWrapper, classNames?.iconWrapper), children: icon !== undefined && icon !== null ? (classNames?.alertIcon ? (
|
|
14407
|
-
// `alertIcon` de v2 se aplicaba al propio SVG; aquí solo alcanza al icono
|
|
14408
|
-
// personalizado (el de v3 por defecto se renderiza dentro del indicador).
|
|
14409
|
-
jsxRuntime.jsx("span", { className: classNames.alertIcon, children: icon })) : (icon)) : undefined })), jsxRuntime.jsxs(react.Alert.Content, { className: react.cn("ms-2 box-border min-h-10 justify-center text-inherit", !hasContent && "items-center", classNames?.mainWrapper), children: [hasTitle && (
|
|
14410
|
-
// v2: `text-small w-full font-medium block text-inherit leading-5`; v3 (`.alert__title`)
|
|
14411
|
-
// pone `leading-6` y el color del estado — `text-inherit` devuelve el de v2.
|
|
14412
|
-
jsxRuntime.jsx(react.Alert.Title, { className: react.cn("w-full leading-5 text-inherit", classNames?.title), children: title })), hasDescription && (
|
|
14413
|
-
// v2: `pl-[1px] text-small font-normal text-inherit`; v3 (`.alert__description`) pone
|
|
14414
|
-
// `text-muted`.
|
|
14415
|
-
jsxRuntime.jsx(react.Alert.Description, { className: react.cn("pl-px font-normal text-inherit", slots.description, classNames?.description), children: description })), children] }), endContent, showClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: react.cn(CLOSE_BASE, slots.closeButton, classNames?.closeButton), onPress: handleClose }))] }));
|
|
14416
|
-
};
|
|
14417
|
-
AlertFacade.displayName = "Aurora.Alert";
|
|
14418
|
-
/**
|
|
14419
|
-
* `Alert` de Aurora: fachada v2 + estáticos compound de v3 (`Alert.Indicator`, `Alert.Content`,
|
|
14420
|
-
* `Alert.Title`, `Alert.Description`), los mismos objetos que exporta `@heroui/react`.
|
|
14421
|
-
*/
|
|
14422
|
-
const Alert = Object.assign(AlertFacade, {
|
|
14423
|
-
Indicator: react.Alert.Indicator,
|
|
14424
|
-
Content: react.Alert.Content,
|
|
14425
|
-
Title: react.Alert.Title,
|
|
14426
|
-
Description: react.Alert.Description,
|
|
14427
|
-
});
|
|
14428
|
-
|
|
14429
|
-
// TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
|
|
14430
|
-
// CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
|
|
14431
|
-
// de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
|
|
14432
|
-
//
|
|
14433
|
-
// HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
|
|
14434
|
-
// `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
|
|
14435
|
-
// `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
|
|
14436
|
-
// `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
|
|
14437
|
-
// (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
|
|
14438
|
-
// — el falso amigo de siempre (skill `auraui-design-tokens`).
|
|
14439
|
-
//
|
|
14440
|
-
// Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
|
|
14441
|
-
// de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
|
|
14442
|
-
// Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
|
|
14443
|
-
// control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
|
|
14444
|
-
// pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
|
|
14445
|
-
// `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
|
|
14446
|
-
//
|
|
14447
|
-
// ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
|
|
14448
|
-
// `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
|
|
14449
|
-
// `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
|
|
14450
|
-
// eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
|
|
14451
|
-
// (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
|
|
14452
|
-
/**
|
|
14453
|
-
* Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
|
|
14454
|
-
* por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
|
|
14455
|
-
* usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
|
|
14456
|
-
*
|
|
14457
|
-
* Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
|
|
14458
|
-
*/
|
|
14459
|
-
const CONTROL_COLOR = {
|
|
14460
|
-
primary: "",
|
|
14461
|
-
default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
|
|
14462
|
-
secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
|
|
14463
|
-
success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
|
|
14464
|
-
warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
|
|
14465
|
-
danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
|
|
14466
|
-
};
|
|
14467
|
-
/**
|
|
14468
|
-
* Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
|
|
14469
|
-
* checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
|
|
14470
|
-
*/
|
|
14471
|
-
const INDICATOR_COLOR = {
|
|
14472
|
-
primary: "",
|
|
14473
|
-
default: "[&_svg]:text-default-foreground",
|
|
14474
|
-
secondary: "[&_svg]:text-secondary-foreground",
|
|
14475
|
-
success: "[&_svg]:text-success-foreground",
|
|
14476
|
-
warning: "[&_svg]:text-warning-foreground",
|
|
14477
|
-
danger: "[&_svg]:text-danger-foreground",
|
|
14478
|
-
};
|
|
14479
|
-
// v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
|
|
14480
|
-
// `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
|
|
14481
|
-
// `size-2.5`, texto `text-sm`, para todos.
|
|
14482
|
-
const CONTROL_SIZE = {
|
|
14483
|
-
sm: "size-4",
|
|
14484
|
-
md: "size-5",
|
|
14485
|
-
lg: "size-6",
|
|
14486
|
-
};
|
|
14487
|
-
const INDICATOR_SIZE = {
|
|
14488
|
-
sm: "size-3 [&_svg]:size-2.5",
|
|
14489
|
-
md: "size-3.5 [&_svg]:size-3",
|
|
14490
|
-
lg: "size-4 [&_svg]:size-3.5",
|
|
14491
|
-
};
|
|
14492
|
-
const LABEL_SIZE = {
|
|
14493
|
-
sm: "text-sm",
|
|
14494
|
-
md: "text-base",
|
|
14495
|
-
lg: "text-lg",
|
|
14496
|
-
};
|
|
14497
|
-
// v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
|
|
14498
|
-
// defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
|
|
14499
|
-
// mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
|
|
14500
|
-
const RADIUS_CLASS = {
|
|
14501
|
-
none: "rounded-none before:rounded-none",
|
|
14502
|
-
sm: "rounded-[6px] before:rounded-[6px]",
|
|
14503
|
-
md: "rounded-[7.2px] before:rounded-[7.2px]",
|
|
14504
|
-
lg: "rounded-[8.4px] before:rounded-[8.4px]",
|
|
14505
|
-
full: "rounded-full before:rounded-full",
|
|
14506
|
-
};
|
|
14507
|
-
const RADIUS_BY_SIZE = {
|
|
14508
|
-
sm: "sm",
|
|
14509
|
-
md: "md",
|
|
14510
|
-
lg: "lg",
|
|
14511
|
-
};
|
|
14512
|
-
/**
|
|
14513
|
-
* Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
|
|
14514
|
-
* (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
|
|
14515
|
-
*
|
|
14516
|
-
* En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
|
|
14517
|
-
* relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
|
|
14518
|
-
* `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
|
|
14519
|
-
* eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
|
|
14520
|
-
* quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
|
|
14521
|
-
* posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
|
|
14522
|
-
*
|
|
14523
|
-
* Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
|
|
14524
|
-
* (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
|
|
14525
|
-
*
|
|
14526
|
-
* Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
|
|
14527
|
-
* `::before` para que el relleno cubra la caja entera, igual que al marcar.
|
|
14528
|
-
*/
|
|
14529
|
-
const CONTROL_BASE = [
|
|
14530
|
-
"border-2 border-default shadow-none overflow-visible",
|
|
14531
|
-
"before:-inset-0.5",
|
|
14532
|
-
"group-data-[hovered=true]/content:bg-default-100",
|
|
14533
|
-
"group-data-[indeterminate=true]/checkbox:before:scale-100",
|
|
14534
|
-
"group-data-[indeterminate=true]/checkbox:before:opacity-100",
|
|
14535
|
-
].join(" ");
|
|
14536
|
-
// v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
|
|
14537
|
-
// atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
|
|
14538
|
-
const LINE_THROUGH = [
|
|
14539
|
-
"inline-flex items-center justify-center",
|
|
14540
|
-
"before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
|
|
14541
|
-
"group-data-[selected=true]/checkbox:opacity-60",
|
|
14542
|
-
"group-data-[selected=true]/checkbox:before:w-full",
|
|
14543
|
-
].join(" ");
|
|
14544
|
-
// Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
|
|
14545
|
-
// a través de la fachada (`<Checkbox><Checkbox.Control>…`).
|
|
16307
|
+
};
|
|
16308
|
+
case "faded":
|
|
16309
|
+
return {
|
|
16310
|
+
base: `border ${FLAT_BASE[color]} ${FADED_BORDER[color]}`,
|
|
16311
|
+
description: defaultDescription,
|
|
16312
|
+
iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
|
|
16313
|
+
closeButton: FLAT_CLOSE[color],
|
|
16314
|
+
};
|
|
16315
|
+
default:
|
|
16316
|
+
return {
|
|
16317
|
+
base: FLAT_BASE[color],
|
|
16318
|
+
description: defaultDescription,
|
|
16319
|
+
iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
|
|
16320
|
+
closeButton: FLAT_CLOSE[color],
|
|
16321
|
+
};
|
|
16322
|
+
}
|
|
16323
|
+
}
|
|
16324
|
+
// Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
|
|
16325
|
+
// v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
|
|
14546
16326
|
const COMPOUND_PARTS = new Set([
|
|
14547
|
-
react.
|
|
14548
|
-
react.
|
|
14549
|
-
react.
|
|
16327
|
+
react.Alert.Indicator,
|
|
16328
|
+
react.Alert.Content,
|
|
16329
|
+
react.Alert.Title,
|
|
16330
|
+
react.Alert.Description,
|
|
14550
16331
|
]);
|
|
14551
|
-
/**
|
|
16332
|
+
/**
|
|
16333
|
+
* Detecta la API compound de v3: si algún hijo directo es `Alert.Indicator`/`Content`/`Title`/
|
|
16334
|
+
* `Description`, el consumidor ya compone la estructura y la fachada NO debe envolver nada (si lo
|
|
16335
|
+
* hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
|
|
16336
|
+
* API v2: children bajo la descripción, con icono y círculo, como en v2.
|
|
16337
|
+
*/
|
|
14552
16338
|
function hasCompoundChildren(children) {
|
|
14553
16339
|
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS.has(child.type));
|
|
14554
16340
|
}
|
|
16341
|
+
/** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
|
|
16342
|
+
function hasValue$1(node) {
|
|
16343
|
+
return node !== null && node !== undefined && node !== "" && node !== false;
|
|
16344
|
+
}
|
|
14555
16345
|
/**
|
|
14556
|
-
*
|
|
16346
|
+
* Alert - Aviso contextual basado en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
|
|
14557
16347
|
*
|
|
14558
|
-
*
|
|
14559
|
-
*
|
|
14560
|
-
*
|
|
14561
|
-
*
|
|
14562
|
-
*
|
|
16348
|
+
* Dos modos, decididos por los `children`:
|
|
16349
|
+
* - **API v2** (`color`/`variant`/`title`/`description`/`isClosable`…): la fachada compone el
|
|
16350
|
+
* compound de v3 y repone `variant` (fondo teñido en `flat`), `radius`, el círculo del icono,
|
|
16351
|
+
* el botón de cerrar (`CloseButton` de v3 con `aria-label` traducido) y la visibilidad.
|
|
16352
|
+
* - **API compound v3** (`<Alert status="danger"><Alert.Indicator/><Alert.Content>…`): pasa tal
|
|
16353
|
+
* cual al `Alert` de HeroUI; `color`, si viene, se traduce a `status`.
|
|
14563
16354
|
*/
|
|
14564
|
-
const
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
16355
|
+
const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, description, icon, hideIcon = false, hideIconWrapper = false, isClosable = false, onClose, isVisible: isVisibleProp, isDefaultVisible, onVisibleChange, startContent, endContent, classNames, className, children, translations = {}, ...rest }) => {
|
|
16356
|
+
// `useControlledState` de v2 (`use-alert.ts`): controlado si llega `isVisible`; si no, estado
|
|
16357
|
+
// propio que arranca en `isDefaultVisible ?? true` y se apaga al cerrar.
|
|
16358
|
+
const [uncontrolledVisible, setUncontrolledVisible] = $8WNJc$react.useState(isDefaultVisible ?? true);
|
|
16359
|
+
const isControlled = isVisibleProp !== undefined;
|
|
16360
|
+
const isVisible = isControlled ? isVisibleProp : uncontrolledVisible;
|
|
16361
|
+
const t = { ...DEFAULT_TRANSLATIONS, ...translations };
|
|
16362
|
+
// `status` (v3) gana sobre `color` (v2) para la clase de estado de HeroUI; y si SOLO llega
|
|
16363
|
+
// `status`, las utilidades de Aurora siguen el color equivalente (`accent` → `primary`).
|
|
16364
|
+
const effectiveColor = color ?? (status ? COLOR_BY_STATUS[status] : "default");
|
|
16365
|
+
const effectiveStatus = status ?? STATUS_BY_COLOR[effectiveColor];
|
|
16366
|
+
if (!isVisible) {
|
|
16367
|
+
return null;
|
|
16368
|
+
}
|
|
14569
16369
|
if (hasCompoundChildren(children)) {
|
|
14570
|
-
return (jsxRuntime.jsx(react.
|
|
16370
|
+
return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
|
|
14571
16371
|
}
|
|
14572
|
-
const
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
const
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
// `
|
|
14591
|
-
//
|
|
14592
|
-
|
|
16372
|
+
const handleClose = () => {
|
|
16373
|
+
if (!isControlled) {
|
|
16374
|
+
setUncontrolledVisible(false);
|
|
16375
|
+
}
|
|
16376
|
+
onVisibleChange?.(false);
|
|
16377
|
+
onClose?.();
|
|
16378
|
+
};
|
|
16379
|
+
const slots = slotClasses(variant, effectiveColor);
|
|
16380
|
+
const hasTitle = hasValue$1(title);
|
|
16381
|
+
const hasDescription = hasValue$1(description);
|
|
16382
|
+
// `hasContent` de v2: sin descripción ni children, el título se centra verticalmente con el icono.
|
|
16383
|
+
const hasContent = hasDescription || hasValue$1(children);
|
|
16384
|
+
const showClose = isClosable || Boolean(onClose);
|
|
16385
|
+
return (jsxRuntime.jsxs(react.Alert, { role: "alert", "data-visible": "true", "data-closeable": showClose ? "true" : undefined, "data-has-title": hasTitle ? "true" : undefined, "data-has-description": hasDescription ? "true" : undefined, ...rest, status: effectiveStatus,
|
|
16386
|
+
// Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
|
|
16387
|
+
// flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
|
|
16388
|
+
// `shadow-surface` (v2 no tenía sombra en el base) y se aplica variant/radius.
|
|
16389
|
+
className: react.cn("grow gap-x-1 shadow-none", slots.base, RADIUS_CLASS[radius], hideIconWrapper && "gap-x-0", classNames?.base, className), children: [startContent, !hideIcon && (jsxRuntime.jsx(react.Alert.Indicator, { className: react.cn(ICON_WRAPPER_BASE,
|
|
16390
|
+
// v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
|
|
16391
|
+
// las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
|
|
16392
|
+
// `dark:bg-*` (grupo distinto para tailwind-merge).
|
|
16393
|
+
hideIconWrapper ? "bg-transparent" : slots.iconWrapper, classNames?.iconWrapper), children: icon !== undefined && icon !== null ? (classNames?.alertIcon ? (
|
|
16394
|
+
// `alertIcon` de v2 se aplicaba al propio SVG; aquí solo alcanza al icono
|
|
16395
|
+
// personalizado (el de v3 por defecto se renderiza dentro del indicador).
|
|
16396
|
+
jsxRuntime.jsx("span", { className: classNames.alertIcon, children: icon })) : (icon)) : undefined })), jsxRuntime.jsxs(react.Alert.Content, { className: react.cn("ms-2 box-border min-h-10 justify-center text-inherit", !hasContent && "items-center", classNames?.mainWrapper), children: [hasTitle && (
|
|
16397
|
+
// v2: `text-small w-full font-medium block text-inherit leading-5`; v3 (`.alert__title`)
|
|
16398
|
+
// pone `leading-6` y el color del estado — `text-inherit` devuelve el de v2.
|
|
16399
|
+
jsxRuntime.jsx(react.Alert.Title, { className: react.cn("w-full leading-5 text-inherit", classNames?.title), children: title })), hasDescription && (
|
|
16400
|
+
// v2: `pl-[1px] text-small font-normal text-inherit`; v3 (`.alert__description`) pone
|
|
16401
|
+
// `text-muted`.
|
|
16402
|
+
jsxRuntime.jsx(react.Alert.Description, { className: react.cn("pl-px font-normal text-inherit", slots.description, classNames?.description), children: description })), children] }), endContent, showClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: react.cn(CLOSE_BASE, slots.closeButton, classNames?.closeButton), onPress: handleClose }))] }));
|
|
14593
16403
|
};
|
|
14594
|
-
|
|
16404
|
+
AlertFacade.displayName = "Aurora.Alert";
|
|
14595
16405
|
/**
|
|
14596
|
-
* `
|
|
14597
|
-
* `
|
|
16406
|
+
* `Alert` de Aurora: fachada v2 + estáticos compound de v3 (`Alert.Indicator`, `Alert.Content`,
|
|
16407
|
+
* `Alert.Title`, `Alert.Description`), los mismos objetos que exporta `@heroui/react`.
|
|
14598
16408
|
*/
|
|
14599
|
-
const
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
16409
|
+
const Alert = Object.assign(AlertFacade, {
|
|
16410
|
+
Indicator: react.Alert.Indicator,
|
|
16411
|
+
Content: react.Alert.Content,
|
|
16412
|
+
Title: react.Alert.Title,
|
|
16413
|
+
Description: react.Alert.Description,
|
|
14603
16414
|
});
|
|
14604
16415
|
|
|
16416
|
+
const OMIT$1 = new Set(["children", "className", "type"]);
|
|
16417
|
+
const CONCAT = new Set(["aria-describedby", "aria-labelledby"]);
|
|
16418
|
+
function mergeRefs$1(...refs) {
|
|
16419
|
+
return (value) => {
|
|
16420
|
+
for (const ref of refs) {
|
|
16421
|
+
if (typeof ref === "function") {
|
|
16422
|
+
ref(value);
|
|
16423
|
+
}
|
|
16424
|
+
else if (ref) {
|
|
16425
|
+
ref.current = value;
|
|
16426
|
+
}
|
|
16427
|
+
}
|
|
16428
|
+
};
|
|
16429
|
+
}
|
|
16430
|
+
function mergeTriggerProps$1(trigger, child) {
|
|
16431
|
+
const merged = { ...trigger, ...child };
|
|
16432
|
+
for (const key of Object.keys(trigger)) {
|
|
16433
|
+
const a = trigger[key];
|
|
16434
|
+
const b = child[key];
|
|
16435
|
+
if (/^on[A-Z]/.test(key) &&
|
|
16436
|
+
typeof a === "function" &&
|
|
16437
|
+
typeof b === "function") {
|
|
16438
|
+
merged[key] = (...args) => {
|
|
16439
|
+
a(...args);
|
|
16440
|
+
b(...args);
|
|
16441
|
+
};
|
|
16442
|
+
}
|
|
16443
|
+
else if (CONCAT.has(key) && a && b) {
|
|
16444
|
+
merged[key] = `${a} ${b}`;
|
|
16445
|
+
}
|
|
16446
|
+
else if (b === undefined) {
|
|
16447
|
+
merged[key] = a;
|
|
16448
|
+
}
|
|
16449
|
+
}
|
|
16450
|
+
return merged;
|
|
16451
|
+
}
|
|
16452
|
+
/** ¿Se puede clonar como trigger? Un único elemento que no sea Fragment. */
|
|
16453
|
+
function isClonableTriggerChild(children) {
|
|
16454
|
+
return $8WNJc$react.isValidElement(children) && children.type !== $8WNJc$react.Fragment;
|
|
16455
|
+
}
|
|
16456
|
+
/**
|
|
16457
|
+
* `render` para `Dropdown.Trigger`: clona las props resueltas del `<button>` de v3 sobre `child`.
|
|
16458
|
+
* `triggerClassName` es el `className` que la app puso en el propio `DropdownTrigger`.
|
|
16459
|
+
*/
|
|
16460
|
+
function renderDropdownTrigger(child, triggerClassName) {
|
|
16461
|
+
return (buttonProps) => {
|
|
16462
|
+
const { ref, ...rest } = buttonProps;
|
|
16463
|
+
const triggerProps = {};
|
|
16464
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
16465
|
+
if (OMIT$1.has(key) || key.startsWith("data-")) {
|
|
16466
|
+
continue;
|
|
16467
|
+
}
|
|
16468
|
+
triggerProps[key] = value;
|
|
16469
|
+
}
|
|
16470
|
+
const hostTag = typeof child.type === "string" ? child.type : null;
|
|
16471
|
+
if (hostTag === "button") {
|
|
16472
|
+
triggerProps.type = rest.type;
|
|
16473
|
+
}
|
|
16474
|
+
else if (hostTag && hostTag !== "a") {
|
|
16475
|
+
triggerProps.role = "button";
|
|
16476
|
+
triggerProps.tabIndex = 0;
|
|
16477
|
+
}
|
|
16478
|
+
const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
|
|
16479
|
+
? child.ref
|
|
16480
|
+
: child.props.ref);
|
|
16481
|
+
const childClass = child.props.className;
|
|
16482
|
+
const className = triggerClassName && typeof childClass !== "function"
|
|
16483
|
+
? react.cn(triggerClassName, childClass)
|
|
16484
|
+
: childClass;
|
|
16485
|
+
return $8WNJc$react.cloneElement(child, {
|
|
16486
|
+
...mergeTriggerProps$1(triggerProps, child.props),
|
|
16487
|
+
...(className === undefined ? {} : { className }),
|
|
16488
|
+
ref: mergeRefs$1(childRef, ref),
|
|
16489
|
+
});
|
|
16490
|
+
};
|
|
16491
|
+
}
|
|
16492
|
+
|
|
14605
16493
|
// TECH-2459 — Fachada `Dropdown` / `DropdownSection`: repone la CABECERA DE SECCIÓN que la
|
|
14606
16494
|
// migración a v3 dejó sin vía legal desde una app.
|
|
14607
16495
|
//
|
|
@@ -14742,15 +16630,153 @@ const OWN_POPOVER = new Set([
|
|
|
14742
16630
|
function isPart$1(node, parts) {
|
|
14743
16631
|
return $8WNJc$react.isValidElement(node) && parts.has(node.type);
|
|
14744
16632
|
}
|
|
14745
|
-
|
|
14746
|
-
|
|
16633
|
+
function isFragment(node) {
|
|
16634
|
+
return $8WNJc$react.isValidElement(node) && node.type === $8WNJc$react.Fragment;
|
|
16635
|
+
}
|
|
16636
|
+
/**
|
|
16637
|
+
* `Children.toArray` aplana arrays pero NO Fragments (un `<>…</>` cuenta como UN nodo). Esto baja
|
|
16638
|
+
* por ellos, y prefija la `key` de cada hijo con la del Fragment para que dos Fragments hermanos
|
|
16639
|
+
* no produzcan claves repetidas (`toArray` numera cada nivel desde `.0`).
|
|
16640
|
+
*/
|
|
16641
|
+
function flatten(children, prefix = "") {
|
|
16642
|
+
return $8WNJc$react.Children.toArray(children).flatMap((node) => {
|
|
16643
|
+
if (isFragment(node)) {
|
|
16644
|
+
return flatten(node.props.children, `${prefix}${String(node.key)}`);
|
|
16645
|
+
}
|
|
16646
|
+
if (prefix && $8WNJc$react.isValidElement(node)) {
|
|
16647
|
+
return [$8WNJc$react.cloneElement(node, { key: `${prefix}${String(node.key)}` })];
|
|
16648
|
+
}
|
|
16649
|
+
return [node];
|
|
16650
|
+
});
|
|
16651
|
+
}
|
|
16652
|
+
/**
|
|
16653
|
+
* Mapea el árbol conservando su forma: atraviesa Fragments (se reconstruyen con sus hijos
|
|
16654
|
+
* mapeados) y aplica `fn` al resto de nodos. Para el caso en el que el popover es del consumidor:
|
|
16655
|
+
* no hay que mover nada de sitio, solo completar props.
|
|
16656
|
+
*/
|
|
16657
|
+
function mapTree(children, fn) {
|
|
16658
|
+
return $8WNJc$react.Children.toArray(children).map((node) => isFragment(node)
|
|
16659
|
+
? $8WNJc$react.cloneElement(node, undefined, ...mapTree(node.props.children, fn))
|
|
16660
|
+
: fn(node));
|
|
16661
|
+
}
|
|
16662
|
+
/**
|
|
16663
|
+
* Traducción de colocación v2 → v3, copiada del oráculo, no deducida: `@heroui/aria-utils@2.2.29`
|
|
16664
|
+
* → `dist/chunk-WQVQ7P2I.mjs:53-69` (`toReactAriaPlacement`), que es lo que el `Popover` de v2
|
|
16665
|
+
* hacía con la prop antes de dársela a react-aria. Ojo a los laterales: `left-start` NO es
|
|
16666
|
+
* `"left start"` (no existe en react-aria) sino `"left top"`.
|
|
16667
|
+
*/
|
|
16668
|
+
const V2_TO_V3_PLACEMENT = {
|
|
16669
|
+
top: "top",
|
|
16670
|
+
bottom: "bottom",
|
|
16671
|
+
left: "left",
|
|
16672
|
+
right: "right",
|
|
16673
|
+
"top-start": "top start",
|
|
16674
|
+
"top-end": "top end",
|
|
16675
|
+
"bottom-start": "bottom start",
|
|
16676
|
+
"bottom-end": "bottom end",
|
|
16677
|
+
"left-start": "left top",
|
|
16678
|
+
"left-end": "left bottom",
|
|
16679
|
+
"right-start": "right top",
|
|
16680
|
+
"right-end": "right bottom",
|
|
16681
|
+
};
|
|
16682
|
+
/** v2 → v3; lo que ya viene en sintaxis de v3 pasa tal cual. */
|
|
16683
|
+
function toV3Placement(placement) {
|
|
16684
|
+
return (V2_TO_V3_PLACEMENT[placement] ??
|
|
16685
|
+
placement);
|
|
16686
|
+
}
|
|
16687
|
+
/** Solo las claves que el consumidor escribió: `undefined` no pisa nada. */
|
|
16688
|
+
function definedOnly(values) {
|
|
16689
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
16690
|
+
}
|
|
16691
|
+
/**
|
|
16692
|
+
* `closeOnSelect` de v2 → `shouldCloseOnSelect` del `Menu` de v3. Precedencia, la de v2
|
|
16693
|
+
* (`@heroui/dropdown@2.3.32` → `dist/chunk-HT5XYLWZ.mjs`, `getCloseOnSelect`): lo del menú gana a
|
|
16694
|
+
* lo del root. Del menú se lee `shouldCloseOnSelect` (v3) y, si no, `closeOnSelect` (v2: 6 de los
|
|
16695
|
+
* 20 `<DropdownMenu>` de beweossmbs lo escriben en el MENÚ, no en el root, y el `Menu` de v3 lo
|
|
16696
|
+
* ignora — sin esto, un multiselect se cerraba en cada clic). El `closeOnSelect` se retira del
|
|
16697
|
+
* elemento: v3 no lo conoce.
|
|
16698
|
+
*/
|
|
16699
|
+
function withCloseOnSelect(node, rootCloseOnSelect) {
|
|
16700
|
+
if (!isPart$1(node, INSIDE_POPOVER)) {
|
|
16701
|
+
return node;
|
|
16702
|
+
}
|
|
16703
|
+
const menu = node;
|
|
16704
|
+
const { closeOnSelect, shouldCloseOnSelect } = menu.props;
|
|
16705
|
+
const resolved = shouldCloseOnSelect ?? closeOnSelect ?? rootCloseOnSelect;
|
|
16706
|
+
if (resolved === undefined && !("closeOnSelect" in menu.props)) {
|
|
16707
|
+
return node;
|
|
16708
|
+
}
|
|
16709
|
+
const { closeOnSelect: _dropped, ...rest } = menu.props;
|
|
16710
|
+
return $8WNJc$react.createElement(menu.type, {
|
|
16711
|
+
...rest,
|
|
16712
|
+
key: menu.key,
|
|
16713
|
+
shouldCloseOnSelect: resolved,
|
|
16714
|
+
});
|
|
16715
|
+
}
|
|
16716
|
+
/**
|
|
16717
|
+
* `<DropdownTrigger><Button/></DropdownTrigger>` de v2 → UN botón, el de la app, con las props del
|
|
16718
|
+
* trigger de v3 clonadas encima vía su `render` (ver `_internal/renderTrigger.tsx`). Sin esto v3
|
|
16719
|
+
* pinta dos `<button>` anidados. Se deja el trigger de v3 tal cual si el consumidor ya pasa su
|
|
16720
|
+
* propio `render`, o si el hijo no es UN elemento (texto, Fragment, función de render props):
|
|
16721
|
+
* entonces el `<button>` de v3 es el trigger, que es lo correcto.
|
|
16722
|
+
*
|
|
16723
|
+
* Medido en beweossmbs `origin/staging` (f6d5be1c2): 20 `<DropdownTrigger>`, 19 con un `<Button>`
|
|
16724
|
+
* de hijo directo y 1 con `{trigger || defaultTrigger}` (también un `Button` en ejecución).
|
|
16725
|
+
*/
|
|
16726
|
+
function withClonedTrigger(node) {
|
|
16727
|
+
if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Trigger) {
|
|
16728
|
+
return node;
|
|
16729
|
+
}
|
|
16730
|
+
const trigger = node;
|
|
16731
|
+
const { children, render, className } = trigger.props;
|
|
16732
|
+
if (render !== undefined || !isClonableTriggerChild(children)) {
|
|
16733
|
+
return node;
|
|
16734
|
+
}
|
|
16735
|
+
return $8WNJc$react.cloneElement(trigger, {
|
|
16736
|
+
render: renderDropdownTrigger(children, typeof className === "string" ? className : undefined),
|
|
16737
|
+
});
|
|
16738
|
+
}
|
|
16739
|
+
const DropdownFacade = ({ children, placement, offset, crossOffset, shouldFlip, containerPadding, closeOnSelect, shouldCloseOnScroll: _noEffectInV3, className, classNames, ...props }) => {
|
|
16740
|
+
const overrides = definedOnly({
|
|
16741
|
+
placement: placement === undefined ? undefined : toV3Placement(placement),
|
|
16742
|
+
offset,
|
|
16743
|
+
crossOffset,
|
|
16744
|
+
shouldFlip,
|
|
16745
|
+
containerPadding,
|
|
16746
|
+
});
|
|
16747
|
+
// v2: `base` (caja exterior), luego `content` + `className` (caja interior); en v3 es una sola
|
|
16748
|
+
// caja, así que van juntas y en ese orden. `cn` funde conflictos entre ellas; frente a la clase
|
|
16749
|
+
// de v3 (`.dropdown__popover`, capa `components`) cualquier utilidad ya gana por capa.
|
|
16750
|
+
const popoverClass = react.cn(classNames?.base, classNames?.content, className) || undefined;
|
|
16751
|
+
const menuWithClose = (node) => withCloseOnSelect(node, closeOnSelect);
|
|
16752
|
+
const nodes = flatten(children);
|
|
16753
|
+
// El popover es del consumidor: se respeta su árbol entero y se le COMPLETA lo que no dijo
|
|
16754
|
+
// (lo que sí dijo manda). Los menús dentro de él reciben el `closeOnSelect` igualmente.
|
|
16755
|
+
if (nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
|
|
16756
|
+
const fillPopover = (node) => {
|
|
16757
|
+
if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Popover) {
|
|
16758
|
+
return node;
|
|
16759
|
+
}
|
|
16760
|
+
const own = node;
|
|
16761
|
+
const missing = Object.fromEntries(Object.entries(overrides).filter(([key]) => own.props[key] === undefined));
|
|
16762
|
+
const ownClass = own.props.className;
|
|
16763
|
+
return $8WNJc$react.cloneElement(own, {
|
|
16764
|
+
...missing,
|
|
16765
|
+
...(popoverClass && typeof ownClass !== "function"
|
|
16766
|
+
? { className: react.cn(popoverClass, ownClass) }
|
|
16767
|
+
: {}),
|
|
16768
|
+
}, ...mapTree(own.props.children, menuWithClose));
|
|
16769
|
+
};
|
|
16770
|
+
return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, (node) => fillPopover(withClonedTrigger(node))) }));
|
|
16771
|
+
}
|
|
14747
16772
|
const menus = nodes.filter((node) => isPart$1(node, INSIDE_POPOVER));
|
|
14748
|
-
// Nada que envolver (sin hijos
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
return jsxRuntime.jsx(react.Dropdown, { ...props, children: children });
|
|
16773
|
+
// Nada que envolver (sin hijos o ningún menú suelto): el root tal cual, como el crudo de v3.
|
|
16774
|
+
if (menus.length === 0) {
|
|
16775
|
+
return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, withClonedTrigger) }));
|
|
14752
16776
|
}
|
|
14753
|
-
return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
|
|
16777
|
+
return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
|
|
16778
|
+
.filter((node) => !isPart$1(node, INSIDE_POPOVER))
|
|
16779
|
+
.map(withClonedTrigger), jsxRuntime.jsx(react.Dropdown.Popover, { offset: 7, placement: "bottom", ...overrides, className: popoverClass, children: menus.map(menuWithClose) })] }));
|
|
14754
16780
|
};
|
|
14755
16781
|
DropdownFacade.displayName = "Aurora.Dropdown";
|
|
14756
16782
|
/**
|
|
@@ -14793,9 +16819,12 @@ const Dropdown = Object.assign(DropdownFacade, {
|
|
|
14793
16819
|
// | beweosagency | 5 | 5 | 5 | 14 | **0** | la seleccionada sin píldora |
|
|
14794
16820
|
// | backoffice-beweos | 5 | 5 | 5 | 18 | 18 (a mano) | bien |
|
|
14795
16821
|
// | beweanalytics | 3 | 3 | 3 | 8 | 8 (a mano) | bien |
|
|
14796
|
-
// | beweossmbs / loginos / linda-business | 0 | 0 | 0 | 0 | 0 | no usan
|
|
16822
|
+
// | beweossmbs / loginos / linda-business | 0 | 0 | 0 | 0 | 0 | no usan la forma v3 |
|
|
14797
16823
|
//
|
|
14798
16824
|
// (conteo de tags de APERTURA; beweanalytics escribe `Tab` suelto en vez de `Tabs.Tab`.)
|
|
16825
|
+
// Corrección del 23-sep: esa última fila solo contaba la forma v3. Las apps SIN migrar sí usan
|
|
16826
|
+
// tabs, y todas en la forma v2 (`<Tab key title>` suelto): beweossmbs 42 raíces, chatbot-claw 1,
|
|
16827
|
+
// linda-business 1. Esa forma la cubre la rama v2 del root, más abajo.
|
|
14799
16828
|
// Dos lecturas que salen de esta tabla y que deciden el diseño:
|
|
14800
16829
|
// · **26 indicadores montados a mano** contra 14 que faltan. La idempotencia no es un adorno:
|
|
14801
16830
|
// sin ella esas 26 pestañas se quedarían con DOS indicadores.
|
|
@@ -14900,7 +16929,12 @@ function hasIndicator(children) {
|
|
|
14900
16929
|
return node.type === $8WNJc$react.Fragment && hasIndicator(node.props.children);
|
|
14901
16930
|
});
|
|
14902
16931
|
}
|
|
14903
|
-
const TabFacade = ({ children,
|
|
16932
|
+
const TabFacade = ({ children: ownChildren, title,
|
|
16933
|
+
// `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
|
|
16934
|
+
titleValue: _titleValue, ...props }) => {
|
|
16935
|
+
// En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
|
|
16936
|
+
// `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
|
|
16937
|
+
const children = ownChildren ?? title;
|
|
14904
16938
|
// `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
|
|
14905
16939
|
// puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
|
|
14906
16940
|
// hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
|
|
@@ -14941,7 +16975,179 @@ const Tab = TabFacade;
|
|
|
14941
16975
|
const INSIDE_CONTAINER = new Set([react.Tabs.List]);
|
|
14942
16976
|
/** Si el consumidor ya monta el contenedor, la fachada no envuelve nada. */
|
|
14943
16977
|
const OWN_CONTAINER = new Set([react.Tabs.ListContainer]);
|
|
14944
|
-
|
|
16978
|
+
// ---------------------------------------------------------------------------------------------
|
|
16979
|
+
// Forma v2: `<Tabs …><Tab key="x" title="X">panel</Tab></Tabs>`
|
|
16980
|
+
// ---------------------------------------------------------------------------------------------
|
|
16981
|
+
//
|
|
16982
|
+
// Las apps que faltan por migrar escriben SOLO esta forma (inventario con el parser de TypeScript
|
|
16983
|
+
// sobre `git show`, 23-sep: beweossmbs `origin/staging` 42 raíces, chatbot-claw
|
|
16984
|
+
// `origin/feat/TECH-3586/linda-claw` 1, linda-business `origin/staging` 1 — 44 raíces, 97 `Tab`,
|
|
16985
|
+
// todos con `key` y `title`, 39 con hijos = panel, 0 `Tabs.List`). En v3 esa forma no monta:
|
|
16986
|
+
// `Tab === Tabs.Tab` es un item de colección y fuera de `Tabs.List` react-aria lanza «cannot be
|
|
16987
|
+
// rendered outside a collection». La fachada la reescribe a la forma COMPLETA de v3:
|
|
16988
|
+
//
|
|
16989
|
+
// Tabs (root)
|
|
16990
|
+
// ├─ div data-slot="tabs-base" ← el `base` de v2: `classNames.base` + `className`
|
|
16991
|
+
// │ └─ Tabs.ListContainer ← el carril (sin él la píldora no se ve, ver arriba)
|
|
16992
|
+
// │ └─ Tabs.List aria-label del root ← `classNames.tabList`, `role="tablist"`
|
|
16993
|
+
// │ └─ Tabs.Tab id = key del Tab ← `classNames.tab` + `className` del Tab
|
|
16994
|
+
// │ ├─ Tabs.Indicator ← `classNames.cursor` (UNO por tab; v3 solo pinta el activo)
|
|
16995
|
+
// │ └─ title
|
|
16996
|
+
// └─ Tabs.Panel id = key (solo los Tab con hijos) ← `classNames.panel` + `className` del Tab
|
|
16997
|
+
//
|
|
16998
|
+
// Oráculo v2: `@heroui/tabs@2.2.29` `dist/tabs.js` y `@heroui/theme@2.4.15`
|
|
16999
|
+
// `dist/components/tabs.js` (npm pack, no de memoria). De ahí sale cada destino:
|
|
17000
|
+
// · `className` del root → `slots.base` junto a `classNames.base` (`tabs.js:88,114`), y `base`
|
|
17001
|
+
// envuelve SOLO la tira: los paneles son sus hermanos (`tabs.js:414-430`).
|
|
17002
|
+
// · `className` del Tab → la pestaña (`cn(classNames.tab, className)`, `tabs.js:207`) Y su panel
|
|
17003
|
+
// (`cn(classNames.panel, …, selectedItem.props.className)`, `tabs.js:282`).
|
|
17004
|
+
// · `classNames.cursor` → el `<span>` de la píldora (`tabs.js:145`), que en v3 es el indicador.
|
|
17005
|
+
// · Un Tab sin hijos NO genera panel (`if (!content) return null`, `tabs.js:284`).
|
|
17006
|
+
//
|
|
17007
|
+
// Las clases de la app van SIEMPRE en última posición de un `cn` (tailwind-merge): el `tv` de v2
|
|
17008
|
+
// corría con `twMerge: true`, así que su clase borraba la de la librería, y aquí igual — la
|
|
17009
|
+
// concatenación simple es el defecto ya medido en otras fachadas (la clase de la app perdía).
|
|
17010
|
+
/** Los dos nombres por los que una app llega a un `Tab`: la fachada y, por si acaso, el crudo. */
|
|
17011
|
+
const TAB_TYPES = new Set([TabFacade, react.Tabs.Tab]);
|
|
17012
|
+
/** Presencia de cualquiera de estas piezas = la app ya escribe la forma v3. */
|
|
17013
|
+
const V3_LIST_TYPES = new Set([
|
|
17014
|
+
react.Tabs.List,
|
|
17015
|
+
react.Tabs.ListContainer,
|
|
17016
|
+
]);
|
|
17017
|
+
/**
|
|
17018
|
+
* Los `Tab` sueltos del root, en orden, **con su `key` original**.
|
|
17019
|
+
*
|
|
17020
|
+
* Se recorre con `Children.forEach` y no con `Children.toArray` a propósito: `toArray` REESCRIBE
|
|
17021
|
+
* las claves (`"all"` → `".$all"`) y la clave es justo lo que se necesita, porque en v2 es el
|
|
17022
|
+
* identificador que recibe `onSelectionChange` y que la app compara con su `selectedKey`.
|
|
17023
|
+
* `forEach` entrega cada hijo tal cual (comprobado: `key` intacta) y aplana arrays, o sea los
|
|
17024
|
+
* `.map()` y los `{cond && <Tab/>}` (los `false`/`null` se descartan). Los Fragments no los
|
|
17025
|
+
* aplana nadie, así que se desciende a mano — como en `hasIndicator`.
|
|
17026
|
+
*/
|
|
17027
|
+
function collectV2Tabs(children, out = []) {
|
|
17028
|
+
$8WNJc$react.Children.forEach(children, (node) => {
|
|
17029
|
+
if (!$8WNJc$react.isValidElement(node)) {
|
|
17030
|
+
return;
|
|
17031
|
+
}
|
|
17032
|
+
if (node.type === $8WNJc$react.Fragment) {
|
|
17033
|
+
collectV2Tabs(node.props.children, out);
|
|
17034
|
+
}
|
|
17035
|
+
else if (TAB_TYPES.has(node.type)) {
|
|
17036
|
+
out.push(node);
|
|
17037
|
+
}
|
|
17038
|
+
});
|
|
17039
|
+
return out;
|
|
17040
|
+
}
|
|
17041
|
+
/** ¿Hay un `Tabs.List` / `Tabs.ListContainer` entre los hijos (descendiendo por Fragments)? */
|
|
17042
|
+
function hasV3List(children) {
|
|
17043
|
+
let found = false;
|
|
17044
|
+
$8WNJc$react.Children.forEach(children, (node) => {
|
|
17045
|
+
if (found || !$8WNJc$react.isValidElement(node)) {
|
|
17046
|
+
return;
|
|
17047
|
+
}
|
|
17048
|
+
found =
|
|
17049
|
+
V3_LIST_TYPES.has(node.type) ||
|
|
17050
|
+
(node.type === $8WNJc$react.Fragment && hasV3List(node.props.children));
|
|
17051
|
+
});
|
|
17052
|
+
return found;
|
|
17053
|
+
}
|
|
17054
|
+
/** v2 `underlined` es la línea inferior → `secondary` de v3; el resto de v2 → `primary` (default). */
|
|
17055
|
+
function toV3Variant(variant) {
|
|
17056
|
+
if (variant === "primary" || variant === "secondary") {
|
|
17057
|
+
return variant;
|
|
17058
|
+
}
|
|
17059
|
+
return variant === "underlined" ? "secondary" : undefined;
|
|
17060
|
+
}
|
|
17061
|
+
/** ¿El `Tab` de v2 trae contenido de panel? Mismo criterio que `if (!content)` de v2. */
|
|
17062
|
+
function hasPanelContent(children) {
|
|
17063
|
+
return typeof children === "function" || $8WNJc$react.Children.count(children) > 0;
|
|
17064
|
+
}
|
|
17065
|
+
const TabsFacade = (allProps) => {
|
|
17066
|
+
const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
|
|
17067
|
+
// Sin equivalente en v3: se consumen para que no lleguen al DOM (ver README, «Props v2»).
|
|
17068
|
+
size: _size, radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
|
|
17069
|
+
const v3Variant = toV3Variant(variant);
|
|
17070
|
+
const v3Orientation = orientation ??
|
|
17071
|
+
(isVertical || placement === "start" || placement === "end"
|
|
17072
|
+
? "vertical"
|
|
17073
|
+
: undefined);
|
|
17074
|
+
// Forma v2 dinámica: `items` + `children` como función `(item) => <Tab key=…/>`.
|
|
17075
|
+
const dynamic = typeof children === "function";
|
|
17076
|
+
const v2Tabs = dynamic
|
|
17077
|
+
? Array.from(items ?? [], (item, index) => {
|
|
17078
|
+
const element = children(item);
|
|
17079
|
+
const fallback = item.key ?? item.id ?? index;
|
|
17080
|
+
return element.key == null
|
|
17081
|
+
? { element, key: String(fallback) }
|
|
17082
|
+
: { element, key: element.key };
|
|
17083
|
+
})
|
|
17084
|
+
: hasV3List(children)
|
|
17085
|
+
? []
|
|
17086
|
+
: collectV2Tabs(children).map((element, index) => ({
|
|
17087
|
+
element,
|
|
17088
|
+
key: element.key ?? String(index),
|
|
17089
|
+
}));
|
|
17090
|
+
if (v2Tabs.length > 0) {
|
|
17091
|
+
const { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, ...rootProps } = rest;
|
|
17092
|
+
const tabs = v2Tabs.map(({ element, key }) => {
|
|
17093
|
+
const { id, title, titleValue: _titleValue, className: tabClassName, children: panel, ...tabProps } = element.props;
|
|
17094
|
+
return {
|
|
17095
|
+
id: id ?? key,
|
|
17096
|
+
title,
|
|
17097
|
+
tabClassName,
|
|
17098
|
+
panel,
|
|
17099
|
+
tabProps,
|
|
17100
|
+
};
|
|
17101
|
+
});
|
|
17102
|
+
// EL `base` DE v2: un div que envuelve SOLO la tira. Ahí van `className`, `classNames.base` y
|
|
17103
|
+
// el `w-full` de `fullWidth`, no al root. En v2 los paneles eran HERMANOS de `base`, fuera de
|
|
17104
|
+
// él (`tabs.js`: `<div {...getBaseProps()}><tabList/></div>` y luego los paneles), y en v3
|
|
17105
|
+
// el root envuelve tira + paneles. Mandarlas al root cambiaba dos pantallas de beweossmbs
|
|
17106
|
+
// sin que la app tocara nada: `service-detail.page.tsx` (`mb-4` debajo del bloque en vez de
|
|
17107
|
+
// entre tira y panel) y `whatsapp-campaign-full.page.tsx` (`flex justify-center` dejaba de
|
|
17108
|
+
// centrar la tira).
|
|
17109
|
+
//
|
|
17110
|
+
// Por qué un div propio y no el `Tabs.ListContainer`: el contenedor ES el carril gris, así
|
|
17111
|
+
// que un `w-full` o un `justify-center` de la app lo estirarían a todo el ancho. En v2 esa
|
|
17112
|
+
// clase caía en un envoltorio `inline-flex` y la tira (el carril de v2) seguía midiendo su
|
|
17113
|
+
// contenido. Este div es `flex`: el carril es un item de fila y mide su contenido, igual.
|
|
17114
|
+
// El scroller sigue funcionando por el `max-width: 100%` de `neutralize-v3.css`, que acota
|
|
17115
|
+
// el mínimo automático del item.
|
|
17116
|
+
//
|
|
17117
|
+
// El precio de meter un nodo entre el root y el carril: TODAS las reglas de la variante
|
|
17118
|
+
// `secondary` de v3 cuelgan de un combinador de hijo DIRECTO
|
|
17119
|
+
// (`.tabs--secondary > .tabs__list-container` y
|
|
17120
|
+
// `.tabs--secondary[data-orientation=…] > .tabs__list-container`, `tabs.css:234-282`), y
|
|
17121
|
+
// 5 pantallas de smbs llegan a ella por `variant="underlined"`. Por eso el div repite el
|
|
17122
|
+
// modificador y el `data-orientation` del root: esos selectores casan con div > carril (el
|
|
17123
|
+
// test lo comprueba con `matches()` sobre los selectores literales). `.tabs--secondary` no
|
|
17124
|
+
// tiene reglas propias, solo las de hijo directo, así que repetirlo no pinta nada en el div.
|
|
17125
|
+
// La única regla que deja de aplicarse es
|
|
17126
|
+
// `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
|
|
17127
|
+
// neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
|
|
17128
|
+
const orientationAttr = v3Orientation ?? "horizontal";
|
|
17129
|
+
return (jsxRuntime.jsxs(react.Tabs, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsxRuntime.jsx("div", { className: react.cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsxRuntime.jsx(react.Tabs.ListContainer, { className: react.cn(fullWidth && "w-full"), children: jsxRuntime.jsx(react.Tabs.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: react.cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => ($8WNJc$react.createElement(react.Tabs.Tab, { ...tabProps, className: react.cn(classNames?.tab, tabClassName), id: id, key: id },
|
|
17130
|
+
jsxRuntime.jsx(react.Tabs.Indicator, { className: react.cn(classNames?.cursor) }),
|
|
17131
|
+
classNames?.tabContent ? (jsxRuntime.jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
|
|
17132
|
+
.filter(({ panel }) => hasPanelContent(panel))
|
|
17133
|
+
.map(({ id, panel, tabClassName }) => (jsxRuntime.jsx(react.Tabs.Panel, { className: react.cn(classNames?.panel, tabClassName), id: id, shouldForceMount: !destroyInactiveTabPanel || undefined, children: typeof panel === "function" ? null : panel }, id)))] }));
|
|
17134
|
+
}
|
|
17135
|
+
// Forma v3 (o nada que traducir): el camino de siempre. Solo se traducen las props de v2 que
|
|
17136
|
+
// tienen equivalente, para que una app a medio migrar no las pierda.
|
|
17137
|
+
// Sin `fullWidth` ni `classNames.base` el `className` pasa INTACTO (sin `cn`), igual que antes
|
|
17138
|
+
// de existir la forma v2: `cn` deduplica y reordena, y aquí no hay nada con qué fusionarlo.
|
|
17139
|
+
const v3Children = dynamic ? null : children;
|
|
17140
|
+
const v3Props = {
|
|
17141
|
+
...rest,
|
|
17142
|
+
className: fullWidth || classNames?.base
|
|
17143
|
+
? react.cn(fullWidth && "w-full", classNames?.base, className)
|
|
17144
|
+
: className,
|
|
17145
|
+
orientation: v3Orientation,
|
|
17146
|
+
variant: v3Variant,
|
|
17147
|
+
};
|
|
17148
|
+
return jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
|
|
17149
|
+
};
|
|
17150
|
+
const TabsV3 = ({ children, ...props }) => {
|
|
14945
17151
|
const nodes = $8WNJc$react.Children.toArray(children);
|
|
14946
17152
|
const needsContainer = nodes.some((node) => isPart(node, INSIDE_CONTAINER)) &&
|
|
14947
17153
|
!nodes.some((node) => isPart(node, OWN_CONTAINER));
|
|
@@ -15004,6 +17210,87 @@ const Tabs = Object.assign(TabsFacade, {
|
|
|
15004
17210
|
Tab: TabFacade,
|
|
15005
17211
|
});
|
|
15006
17212
|
|
|
17213
|
+
const OMIT = new Set([
|
|
17214
|
+
"role",
|
|
17215
|
+
"tabIndex",
|
|
17216
|
+
"className",
|
|
17217
|
+
"data-slot",
|
|
17218
|
+
"children",
|
|
17219
|
+
]);
|
|
17220
|
+
function mergeRefs(...refs) {
|
|
17221
|
+
return (value) => {
|
|
17222
|
+
for (const ref of refs) {
|
|
17223
|
+
if (typeof ref === "function") {
|
|
17224
|
+
ref(value);
|
|
17225
|
+
}
|
|
17226
|
+
else if (ref) {
|
|
17227
|
+
ref.current = value;
|
|
17228
|
+
}
|
|
17229
|
+
}
|
|
17230
|
+
};
|
|
17231
|
+
}
|
|
17232
|
+
/** `mergeProps` de v2: handlers `on*` encadenados (trigger → hijo), el resto gana el hijo. */
|
|
17233
|
+
function mergeTriggerProps(trigger, child) {
|
|
17234
|
+
const merged = { ...trigger, ...child };
|
|
17235
|
+
for (const key of Object.keys(trigger)) {
|
|
17236
|
+
const a = trigger[key];
|
|
17237
|
+
const b = child[key];
|
|
17238
|
+
if (/^on[A-Z]/.test(key) &&
|
|
17239
|
+
typeof a === "function" &&
|
|
17240
|
+
typeof b === "function") {
|
|
17241
|
+
merged[key] = (...args) => {
|
|
17242
|
+
a(...args);
|
|
17243
|
+
b(...args);
|
|
17244
|
+
};
|
|
17245
|
+
}
|
|
17246
|
+
else if (key === "aria-describedby" && a && b) {
|
|
17247
|
+
merged[key] = `${a} ${b}`;
|
|
17248
|
+
}
|
|
17249
|
+
else if (b === undefined) {
|
|
17250
|
+
merged[key] = a;
|
|
17251
|
+
}
|
|
17252
|
+
}
|
|
17253
|
+
return merged;
|
|
17254
|
+
}
|
|
17255
|
+
function renderTooltipTrigger(children) {
|
|
17256
|
+
return (divProps) => {
|
|
17257
|
+
const { ref, ...rest } = divProps;
|
|
17258
|
+
const triggerProps = {};
|
|
17259
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
17260
|
+
if (!OMIT.has(key)) {
|
|
17261
|
+
triggerProps[key] = value;
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
17264
|
+
const single = $8WNJc$react.Children.count(children) === 1 && $8WNJc$react.isValidElement(children);
|
|
17265
|
+
if (single && children.type !== $8WNJc$react.Fragment) {
|
|
17266
|
+
const child = children;
|
|
17267
|
+
const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
|
|
17268
|
+
? child.ref
|
|
17269
|
+
: child.props.ref);
|
|
17270
|
+
return $8WNJc$react.cloneElement(child, {
|
|
17271
|
+
...mergeTriggerProps(triggerProps, child.props),
|
|
17272
|
+
ref: mergeRefs(childRef, ref),
|
|
17273
|
+
});
|
|
17274
|
+
}
|
|
17275
|
+
return (jsxRuntime.jsx("div", { ...triggerProps, ref: ref, className: rest.className, "data-slot": "tooltip-trigger", children: children }));
|
|
17276
|
+
};
|
|
17277
|
+
}
|
|
17278
|
+
|
|
17279
|
+
/**
|
|
17280
|
+
* `color` de v2 (`@heroui/theme@2.4.15`, `popover` → `colorVariants.solid`): fondo y texto del
|
|
17281
|
+
* color en la caja, y la flecha del mismo color (v2 la pintaba con `before:bg-*`; en v3 es un SVG
|
|
17282
|
+
* con `fill: var(--overlay)`, así que se pisa su `fill`). `default` no añade nada: se ve como v3
|
|
17283
|
+
* (§9.1). Clases LITERALES — Tailwind 4 solo genera lo que está escrito en el fuente.
|
|
17284
|
+
*/
|
|
17285
|
+
const COLOR_CLASSES = {
|
|
17286
|
+
default: "",
|
|
17287
|
+
foreground: "bg-foreground text-background [&_[data-slot=overlay-arrow]]:fill-foreground",
|
|
17288
|
+
primary: "bg-primary text-primary-foreground [&_[data-slot=overlay-arrow]]:fill-primary",
|
|
17289
|
+
secondary: "bg-secondary text-secondary-foreground [&_[data-slot=overlay-arrow]]:fill-secondary",
|
|
17290
|
+
success: "bg-success text-success-foreground [&_[data-slot=overlay-arrow]]:fill-success",
|
|
17291
|
+
warning: "bg-warning text-warning-foreground [&_[data-slot=overlay-arrow]]:fill-warning",
|
|
17292
|
+
danger: "bg-danger text-danger-foreground [&_[data-slot=overlay-arrow]]:fill-danger",
|
|
17293
|
+
};
|
|
15007
17294
|
/**
|
|
15008
17295
|
* Tooltip - Componente BeweOS basado en HeroUI.
|
|
15009
17296
|
*
|
|
@@ -15019,7 +17306,11 @@ const Tabs = Object.assign(TabsFacade, {
|
|
|
15019
17306
|
const Tooltip = ({ enableTapOnTouch = true, touchPlacement = "top", placement, offset, showArrow = false, content, isOpen, defaultOpen, onOpenChange, isDisabled,
|
|
15020
17307
|
// v3 cambia el default de 0 (v2) a 700 — se mantiene 0 para no regresionar el
|
|
15021
17308
|
// comportamiento instantáneo que ya tenía v2 (ver Tooltip.types.ts).
|
|
15022
|
-
delay = 0, closeDelay, trigger, className, children, }) => {
|
|
17309
|
+
delay = 0, closeDelay, trigger, className, classNames, color = "default", children, }) => {
|
|
17310
|
+
// v3 tiene un solo nodo donde v2 tenía `base` + `content` (ver `TooltipClassNames`). Orden de
|
|
17311
|
+
// v2: color → `classNames` → `className`; `cn` fusiona para que la clase de la app gane.
|
|
17312
|
+
const contentClassName = react.cn(COLOR_CLASSES[color] ?? "", classNames?.base, classNames?.content, className);
|
|
17313
|
+
const arrow = showArrow && (jsxRuntime.jsx(react.Tooltip.Arrow, { className: classNames?.arrow }));
|
|
15023
17314
|
// Detección táctil: NO basta con `(hover: none)`. Muchos móviles (p.ej.
|
|
15024
17315
|
// Android) reportan `(hover: hover)` y `(hover: none)` da false, dejando el
|
|
15025
17316
|
// tap sin activar. Se combina con `(pointer: coarse)` (coincide si cualquiera
|
|
@@ -15057,7 +17348,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
|
|
|
15057
17348
|
}, [tapMode, open, setOpen]);
|
|
15058
17349
|
// Desktop (o tap deshabilitado) → comportamiento nativo de HeroUI.
|
|
15059
17350
|
if (!tapMode) {
|
|
15060
|
-
return (jsxRuntime.jsxs(react.Tooltip, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, isDisabled: isDisabled, delay: delay, closeDelay: closeDelay, trigger: trigger, children: [jsxRuntime.jsx(react.Tooltip.Trigger, {
|
|
17351
|
+
return (jsxRuntime.jsxs(react.Tooltip, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, isDisabled: isDisabled, delay: delay, closeDelay: closeDelay, trigger: trigger, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { render: renderTooltipTrigger(children) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: placement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
|
|
15061
17352
|
}
|
|
15062
17353
|
// Táctil → controlado, el tap sobre el trigger alterna la visibilidad.
|
|
15063
17354
|
// El placement cambia a `touchPlacement` (arriba por defecto) para que no
|
|
@@ -15069,7 +17360,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
|
|
|
15069
17360
|
return (jsxRuntime.jsxs(react.Tooltip, { isOpen: open, onOpenChange: setOpen, isDisabled: isDisabled, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { children: jsxRuntime.jsx("span", { ref: triggerRef, className: "inline-flex", onPointerUp: (event) => {
|
|
15070
17361
|
event.stopPropagation();
|
|
15071
17362
|
setOpen(!open);
|
|
15072
|
-
}, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className:
|
|
17363
|
+
}, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
|
|
15073
17364
|
};
|
|
15074
17365
|
|
|
15075
17366
|
/**
|
|
@@ -15836,26 +18127,6 @@ Object.defineProperty(exports, "CloseButton", {
|
|
|
15836
18127
|
enumerable: true,
|
|
15837
18128
|
get: function () { return react.CloseButton; }
|
|
15838
18129
|
});
|
|
15839
|
-
Object.defineProperty(exports, "Drawer", {
|
|
15840
|
-
enumerable: true,
|
|
15841
|
-
get: function () { return react.Drawer; }
|
|
15842
|
-
});
|
|
15843
|
-
Object.defineProperty(exports, "DrawerBody", {
|
|
15844
|
-
enumerable: true,
|
|
15845
|
-
get: function () { return react.DrawerBody; }
|
|
15846
|
-
});
|
|
15847
|
-
Object.defineProperty(exports, "DrawerContent", {
|
|
15848
|
-
enumerable: true,
|
|
15849
|
-
get: function () { return react.DrawerContent; }
|
|
15850
|
-
});
|
|
15851
|
-
Object.defineProperty(exports, "DrawerFooter", {
|
|
15852
|
-
enumerable: true,
|
|
15853
|
-
get: function () { return react.DrawerFooter; }
|
|
15854
|
-
});
|
|
15855
|
-
Object.defineProperty(exports, "DrawerHeader", {
|
|
15856
|
-
enumerable: true,
|
|
15857
|
-
get: function () { return react.DrawerHeader; }
|
|
15858
|
-
});
|
|
15859
18130
|
Object.defineProperty(exports, "DropdownItem", {
|
|
15860
18131
|
enumerable: true,
|
|
15861
18132
|
get: function () { return react.DropdownItem; }
|
|
@@ -15968,7 +18239,12 @@ exports.DateRangePicker = DateRangePicker;
|
|
|
15968
18239
|
exports.DateSelector = DateSelector;
|
|
15969
18240
|
exports.Divider = Divider;
|
|
15970
18241
|
exports.DonutChart = DonutChart;
|
|
18242
|
+
exports.Drawer = Drawer;
|
|
18243
|
+
exports.DrawerBody = DrawerBody;
|
|
18244
|
+
exports.DrawerContent = DrawerContent;
|
|
15971
18245
|
exports.DrawerFilters = DrawerFilters;
|
|
18246
|
+
exports.DrawerFooter = DrawerFooter;
|
|
18247
|
+
exports.DrawerHeader = DrawerHeader;
|
|
15972
18248
|
exports.Dropdown = Dropdown;
|
|
15973
18249
|
exports.DropdownSection = DropdownSection;
|
|
15974
18250
|
exports.EmailPreview = EmailPreview;
|
|
@@ -16034,7 +18310,7 @@ exports.ThemeContext = ThemeContext;
|
|
|
16034
18310
|
exports.ThemePicker = ThemePicker;
|
|
16035
18311
|
exports.ThemeProvider = ThemeProvider;
|
|
16036
18312
|
exports.TimeInput = TimeInput;
|
|
16037
|
-
exports.TimeInputComponent =
|
|
18313
|
+
exports.TimeInputComponent = TimeInputComponent;
|
|
16038
18314
|
exports.ToastContext = ToastContext;
|
|
16039
18315
|
exports.Tooltip = Tooltip;
|
|
16040
18316
|
exports.TwoColumnLayoutAgent = TwoColumnLayoutAgent;
|