@beweco/aurora-ui 1.0.0-beta.140 → 1.0.0-beta.142
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 +34 -3
- package/dist/index.cjs.js +910 -207
- package/dist/index.esm.js +911 -208
- package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
- package/dist/types/components/autocomplete/AutoComplete.types.d.ts +39 -1
- 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/card/Card.d.ts +2 -1
- package/dist/types/components/card/Card.d.ts.map +1 -1
- package/dist/types/components/card/Card.types.d.ts +6 -5
- package/dist/types/components/card/Card.types.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.types.d.ts +7 -3
- package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
- package/dist/types/components/color-picker/ColorPicker.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
- package/dist/types/components/drawer/Drawer.d.ts.map +1 -1
- package/dist/types/components/drawer/_internal/DrawerFooter.d.ts +3 -1
- package/dist/types/components/drawer/_internal/DrawerFooter.d.ts.map +1 -1
- package/dist/types/components/drawer-filters/DrawerFilters.d.ts.map +1 -1
- package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts +2 -0
- package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts.map +1 -1
- package/dist/types/components/drawer-filters/_internal/DayMonthPicker.d.ts +6 -0
- package/dist/types/components/drawer-filters/_internal/DayMonthPicker.d.ts.map +1 -1
- package/dist/types/components/drawer-filters/_internal/DrawerFiltersHeader.d.ts.map +1 -1
- package/dist/types/components/drawer-filters/_internal/FilterSection.d.ts.map +1 -1
- package/dist/types/components/icon/Icon.d.ts.map +1 -1
- package/dist/types/components/icon/Icon.types.d.ts +9 -1
- package/dist/types/components/icon/Icon.types.d.ts.map +1 -1
- package/dist/types/components/input/Input.d.ts.map +1 -1
- package/dist/types/components/menu/_internal/menu-nav-list/MenuNavList.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalFooter.d.ts +6 -0
- package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/range-filter/RangeFilter.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 +109 -6
- package/dist/types/components/select/Select.types.d.ts.map +1 -1
- 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/switch/Switch.d.ts.map +1 -1
- package/dist/types/components/switch/Switch.types.d.ts +22 -0
- package/dist/types/components/switch/Switch.types.d.ts.map +1 -1
- package/dist/types/components/switch/index.d.ts +1 -1
- package/dist/types/components/switch/index.d.ts.map +1 -1
- package/dist/types/components/table/Table.d.ts.map +1 -1
- package/dist/types/components/table/TableContent.d.ts.map +1 -1
- package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
- package/dist/types/components/tabs/Tabs.types.d.ts +3 -2
- 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 +24 -0
- package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -756,9 +756,44 @@ const SHAPE_BY_VARIANT = {
|
|
|
756
756
|
light: "transparent",
|
|
757
757
|
flat: "soft",
|
|
758
758
|
bordered: "outline",
|
|
759
|
-
faded: "
|
|
759
|
+
faded: "faded",
|
|
760
760
|
// `shadow` de v2 es `solid` + sombra teñida (ver `SHADOW_CLASS`).
|
|
761
761
|
shadow: "solid",
|
|
762
|
+
// `dot` de v2: chip neutro con un punto de color (ver `DOT_BASE_CLASS`).
|
|
763
|
+
dot: "dot",
|
|
764
|
+
};
|
|
765
|
+
// ── `variant="dot"` (TECH-2459, reportado por beweossmbs sobre la .140) ─────────────────────
|
|
766
|
+
//
|
|
767
|
+
// Literal de `@heroui/theme@2.4.15` (`chip.js`) y `@heroui/chip@2.2.25` (`chip.js`):
|
|
768
|
+
//
|
|
769
|
+
// variant.dot.base = "border-medium border-default text-foreground bg-transparent"
|
|
770
|
+
// slots.dot = "w-2 h-2 ml-1 rounded-full" + color.<c>.dot = "bg-<c>" (default: "bg-default-400")
|
|
771
|
+
// render = `isDot && !startContent` → `<span class={slots.dot}/>` en el hueco de `startContent`
|
|
772
|
+
//
|
|
773
|
+
// O sea: el `color` NO tiñe el chip (no hay ningún `compoundVariant` de `dot` × color), solo el
|
|
774
|
+
// punto; el chip queda neutro con el borde de 2px de `default`. Y el punto NO cuenta como
|
|
775
|
+
// `hasStartContent` (`!!avatar || !!startContent`), así que el texto conserva su sangrado izquierdo
|
|
776
|
+
// normal. El compound `{ isOneChar, variant: "dot" }` (base "w-auto h-7 px-1", content "px-2")
|
|
777
|
+
// anula el cuadrado de un carácter: aquí basta con no aplicarlo.
|
|
778
|
+
//
|
|
779
|
+
// Uso real: `shared/ui/components/page-header` de beweossmbs reenvía un `variant` de su propio tipo
|
|
780
|
+
// que incluye `"dot"` (hoy ningún sitio lo pasa, pero el tipo no compilaba).
|
|
781
|
+
const DOT_BASE_CLASS = "border-2 border-default bg-transparent text-foreground";
|
|
782
|
+
const DOT_SLOT_CLASS = "h-2 w-2 ml-1 shrink-0 rounded-full";
|
|
783
|
+
// Clases literales por color (Tailwind 4 solo genera lo que encuentra escrito). Los 5 colores fijos
|
|
784
|
+
// de Aurora no existían en v2: su punto toma el tono del texto que ya les da `CUSTOM_COLOR_CLASSES`.
|
|
785
|
+
const DOT_COLOR_CLASS = {
|
|
786
|
+
default: "bg-default-400",
|
|
787
|
+
primary: "bg-primary",
|
|
788
|
+
secondary: "bg-secondary",
|
|
789
|
+
success: "bg-success",
|
|
790
|
+
warning: "bg-warning",
|
|
791
|
+
danger: "bg-danger",
|
|
792
|
+
red: "bg-red-500",
|
|
793
|
+
purple: "bg-purple-500",
|
|
794
|
+
blue: "bg-blue-500",
|
|
795
|
+
green: "bg-green-500",
|
|
796
|
+
orange: "bg-orange-500",
|
|
762
797
|
};
|
|
763
798
|
// `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
|
|
764
799
|
// `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
|
|
@@ -876,6 +911,8 @@ const V3_VARIANT_BY_SHAPE = {
|
|
|
876
911
|
soft: "soft",
|
|
877
912
|
transparent: "tertiary",
|
|
878
913
|
outline: "tertiary",
|
|
914
|
+
faded: "tertiary",
|
|
915
|
+
dot: "tertiary",
|
|
879
916
|
};
|
|
880
917
|
/**
|
|
881
918
|
* TECH-2459 · F5 · P-08d — La tabla `flat` de v2, medida contra los PNG congelados y no deducida.
|
|
@@ -982,7 +1019,56 @@ const CLOSE_ICON_SIZE_CLASS = {
|
|
|
982
1019
|
md: "size-[18px]",
|
|
983
1020
|
lg: "size-5",
|
|
984
1021
|
};
|
|
1022
|
+
// ── `bordered` y `faded` (TECH-2459, 24-sep: el usuario ve «muy negro» el `bordered default`) ──
|
|
1023
|
+
//
|
|
1024
|
+
// Antes las dos variantes pintaban borde y texto con `tintFg` (`default-700` = rgb(63,63,70) para
|
|
1025
|
+
// `default`): en el selector de tallas del `addon-card` de beweossmbs el chip sin elegir se leía
|
|
1026
|
+
// como un bloque casi negro al lado del `solid primary` elegido. Y el mismo `tintFg` dejaba el
|
|
1027
|
+
// texto por debajo de AA en otros colores. Medido en `Custom Components/Chip → BorderedColors`
|
|
1028
|
+
// (texto contra el fondo real, blanco / tarjeta `content1` oscura):
|
|
1029
|
+
//
|
|
1030
|
+
// | color | antes claro | antes oscuro | ahora claro | ahora oscuro |
|
|
1031
|
+
// |-----------|-------------|--------------|-------------|--------------|
|
|
1032
|
+
// | default | 10.44 (borde casi negro) | 13.96 (borde casi blanco) | 17.93 | 15.11 |
|
|
1033
|
+
// | primary | 4.66 | 3.80 ✗ | 6.38 | 7.33 |
|
|
1034
|
+
// | secondary | 9.50 | 5.77 | 9.50 | 5.77 |
|
|
1035
|
+
// | success | 2.19 ✗ | 8.08 | 5.50 | 8.08 |
|
|
1036
|
+
// | warning | 2.04 ✗ | 8.68 | 5.20 | 8.68 |
|
|
1037
|
+
// | danger | 4.14 ✗ | 4.27 ✗ | 6.06 | 5.02 |
|
|
1038
|
+
//
|
|
1039
|
+
// Criterio (no es volver a v2 por volver, es que se vea bien dentro de los tokens de Aurora):
|
|
1040
|
+
//
|
|
1041
|
+
// - **Borde**: el color a secas (`border-<color>`). Para `default`, el gris neutro que ya usa el
|
|
1042
|
+
// resto de la familia (`dot` lleva `border-default`, y v2 también en `bordered`/`faded`):
|
|
1043
|
+
// `--color-default`, rgb(212,212,216) en claro y rgb(63,63,70) en oscuro. Se distingue del fondo
|
|
1044
|
+
// (medido: 1.48:1 sobre blanco, 1.70:1 sobre la tarjeta oscura, 2.01:1 sobre el fondo negro) sin
|
|
1045
|
+
// competir con el texto. Input/Select reposan en `default-200`, un paso más claro, porque su borde
|
|
1046
|
+
// enmarca un campo; en un chip el borde ES la forma, y `default-200` (≈1.27:1 sobre blanco,
|
|
1047
|
+
// calculado) la dejaba casi invisible. En oscuro los dos pasos son el mismo rgb(63,63,70).
|
|
1048
|
+
// - **Texto**: el mismo paso legible que ya usa `flat` (`flatFg` + su `dark:`), que es el que la
|
|
1049
|
+
// librería ya validó para texto tintado; `default` usa `foreground`.
|
|
1050
|
+
// - Van como CLASES y no como `borderColor` inline: un estilo inline le ganaba a cualquier
|
|
1051
|
+
// `border-*` de la app, y así `cn` (tailwind-merge) deja ganar la clase del consumidor.
|
|
1052
|
+
// - `faded` conserva lo que lo distinguía en v2 (borde neutro en todos los colores + relleno
|
|
1053
|
+
// `default-100`); hasta ahora era idéntico a `bordered`. Su texto es el `flatFg` SIN el `dark:`
|
|
1054
|
+
// de `flat` (ver `flatDarkFgClass`). Medido: ≥ 4.73:1 en claro y ≥ 4.85:1 en oscuro en los 6
|
|
1055
|
+
// colores. 0 usos en las apps.
|
|
1056
|
+
const OUTLINE_BORDER_CLASS = {
|
|
1057
|
+
default: "border-default",
|
|
1058
|
+
primary: "border-primary",
|
|
1059
|
+
secondary: "border-secondary",
|
|
1060
|
+
success: "border-success",
|
|
1061
|
+
warning: "border-warning",
|
|
1062
|
+
danger: "border-danger",
|
|
1063
|
+
};
|
|
985
1064
|
function colorStyle(color, shape) {
|
|
1065
|
+
if (shape === "dot") {
|
|
1066
|
+
// `dot`: el chip es neutro para cualquier color (el color va solo en el punto).
|
|
1067
|
+
return {
|
|
1068
|
+
"--chip-bg": "transparent",
|
|
1069
|
+
"--chip-fg": "var(--color-foreground)",
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
986
1072
|
const t = COLOR_TOKENS[color];
|
|
987
1073
|
if (shape === "solid") {
|
|
988
1074
|
return { "--chip-bg": t.bg, "--chip-fg": t.fg };
|
|
@@ -991,7 +1077,14 @@ function colorStyle(color, shape) {
|
|
|
991
1077
|
// `flat` de v2: pastilla translúcida + texto del paso -600/-700 (P-08d).
|
|
992
1078
|
return { "--chip-bg": t.flatBg, "--chip-fg": t.flatFg };
|
|
993
1079
|
}
|
|
994
|
-
|
|
1080
|
+
if (shape === "outline" || shape === "faded") {
|
|
1081
|
+
// `bordered`/`faded`: texto del paso legible de `flat`; `default` en `foreground`.
|
|
1082
|
+
return {
|
|
1083
|
+
"--chip-bg": shape === "faded" ? "var(--color-default-100)" : "transparent",
|
|
1084
|
+
"--chip-fg": color === "default" ? "var(--color-foreground)" : t.flatFg,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
// transparent (light): sin relleno, texto tinte del color.
|
|
995
1088
|
return { "--chip-bg": "transparent", "--chip-fg": t.tintFg };
|
|
996
1089
|
}
|
|
997
1090
|
/**
|
|
@@ -1018,26 +1111,32 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
1018
1111
|
const hasEndContent = Boolean(endContent) || Boolean(onClose);
|
|
1019
1112
|
// `isOneChar` de v2 (`use-chip.js:58`): SOLO si el children es un string de un carácter — un
|
|
1020
1113
|
// número no cuenta. El cuadrado además exige no llevar nada a los lados (ver `ONE_CHAR_CLASS`).
|
|
1021
|
-
const
|
|
1114
|
+
const isDot = shape === "dot";
|
|
1115
|
+
const isSquare = !isDot &&
|
|
1116
|
+
typeof children === "string" &&
|
|
1022
1117
|
children.length === 1 &&
|
|
1023
1118
|
!startContent &&
|
|
1024
1119
|
!hasEndContent;
|
|
1025
1120
|
const vars = isStandard
|
|
1026
1121
|
? colorStyle(color, shape)
|
|
1027
1122
|
: undefined;
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1123
|
+
// Con `dot` el color no tiñe el chip, tampoco el de los colores fijos.
|
|
1124
|
+
const customClass = isStandard || isDot ? "" : CUSTOM_COLOR_CLASSES[color];
|
|
1125
|
+
const hasBorder = shape === "outline" || shape === "faded";
|
|
1126
|
+
const outlineClass = hasBorder
|
|
1127
|
+
? react.cn("border-2", isStandard
|
|
1128
|
+
? OUTLINE_BORDER_CLASS[shape === "faded" ? "default" : color]
|
|
1129
|
+
: "")
|
|
1130
|
+
: "";
|
|
1131
|
+
// El `dark:text-*` que v2 declaraba para el texto de `flat` en success/warning/danger (P-08d),
|
|
1132
|
+
// reutilizado por `bordered`, que toma el mismo texto. `faded` NO: sobre su relleno
|
|
1133
|
+
// `default-100` oscuro el `danger-500` se quedaba en 4.22:1; con el paso `-600`/`-700` del
|
|
1134
|
+
// tema oscuro (más claro) todos los colores pasan AA. Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
|
|
1032
1135
|
// `utilities` gana al `color: var(--chip-fg)` de `.chip`, que vive en `components`.
|
|
1033
|
-
const flatDarkFgClass = isStandard && shape === "soft"
|
|
1136
|
+
const flatDarkFgClass = isStandard && (shape === "soft" || shape === "outline")
|
|
1034
1137
|
? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
|
|
1035
1138
|
: "";
|
|
1036
|
-
const baseStyle = vars
|
|
1037
|
-
? shape === "outline"
|
|
1038
|
-
? { ...vars, borderColor: vars["--chip-fg"] }
|
|
1039
|
-
: vars
|
|
1040
|
-
: undefined;
|
|
1139
|
+
const baseStyle = vars;
|
|
1041
1140
|
// El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
|
|
1042
1141
|
// beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
|
|
1043
1142
|
const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
|
|
@@ -1062,7 +1161,7 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
1062
1161
|
className: react.cn(SIZE_CLASS$6[size],
|
|
1063
1162
|
// Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
|
|
1064
1163
|
// respecto al tamaño normal y tiene que ganar en tailwind-merge.
|
|
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
|
|
1164
|
+
isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, isDot ? DOT_BASE_CLASS : "", shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, isDot && !startContent && (jsxRuntime.jsx("span", { "aria-hidden": "true", "data-slot": "dot", className: react.cn(DOT_SLOT_CLASS, DOT_COLOR_CLASS[color]) })), jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
|
|
1066
1165
|
? CONTENT_PADDING[size].leftWithContent
|
|
1067
1166
|
: CONTENT_PADDING[size].left, hasEndContent
|
|
1068
1167
|
? CONTENT_PADDING[size].rightWithContent
|
|
@@ -1087,7 +1186,11 @@ const sizeMap = {
|
|
|
1087
1186
|
const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFlip, flip, rotate, ...nativeProps }) => {
|
|
1088
1187
|
// Un número son píxeles tal cual (p. ej. 64): la escala con nombre se queda en 32 y
|
|
1089
1188
|
// `width`/`height` no son props del componente (caerían en el <span>, sin efecto).
|
|
1090
|
-
|
|
1189
|
+
// Un nombre fuera de la escala (`"xs"`, el alias v2 del tipo) cae al default, como en v2
|
|
1190
|
+
// (`sizeMap[size] ?? sizeMap.md`, `@beweco/aurora-ui@0.6.85`): 20px.
|
|
1191
|
+
const iconSize = typeof size === "number"
|
|
1192
|
+
? `${size}px`
|
|
1193
|
+
: (sizeMap[size] ?? sizeMap.md);
|
|
1091
1194
|
return (jsxRuntime.jsx("span", { ...nativeProps, children: jsxRuntime.jsx(react$1.Icon, { icon: icon, width: iconSize, height: iconSize, className: className, style: style, color: color, hFlip: hFlip, vFlip: vFlip, flip: flip, rotate: rotate }) }));
|
|
1092
1195
|
};
|
|
1093
1196
|
|
|
@@ -1152,6 +1255,20 @@ const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
|
|
|
1152
1255
|
const STRIP_LOOSE_CLASS = "w-fit gap-1";
|
|
1153
1256
|
/** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
|
|
1154
1257
|
const NAV_CLASS = "w-9 px-0";
|
|
1258
|
+
/**
|
|
1259
|
+
* TECH-2459 (beweossmbs, 24-sep) — anterior/siguiente deshabilitados por estar en un EXTREMO
|
|
1260
|
+
* (página 1 / última). v3 los apaga con `status-disabled` (`opacity: var(--disabled-opacity)`,
|
|
1261
|
+
* `@heroui/styles/dist/components/pagination.css:57`), que aquí deja el galón en el
|
|
1262
|
+
* `text-default-foreground` de `CELL_CLASS` al 50 %: medido `rgb(0,0,0)` a opacidad 0.5 sobre
|
|
1263
|
+
* `default-100`, que se lee como un control activo. Se pinta el galón en `default-300` y a
|
|
1264
|
+
* opacidad plena, el gris de «apagado» de la paleta (el mismo que usaba v2,
|
|
1265
|
+
* `data-[disabled=true]:text-default-300` de `@heroui/theme@2.4.15` `pagination.js:489`); el
|
|
1266
|
+
* cursor y el `pointer-events` de v3 se conservan.
|
|
1267
|
+
*
|
|
1268
|
+
* Solo en los extremos: con `isDisabled` en toda la paginación manda el apagado uniforme de v3
|
|
1269
|
+
* (todas las celdas al 50 %), y las flechas no deben verse distintas del resto.
|
|
1270
|
+
*/
|
|
1271
|
+
const NAV_AT_EDGE_CLASS = "disabled:text-default-300 disabled:opacity-100";
|
|
1155
1272
|
/**
|
|
1156
1273
|
* Radio de cada pieza según su posición en la tira.
|
|
1157
1274
|
*
|
|
@@ -1226,6 +1343,33 @@ const DEFAULT_TRANSLATIONS$g = {
|
|
|
1226
1343
|
jumpBackwardAriaLabel: "Retroceder {count} páginas",
|
|
1227
1344
|
jumpForwardAriaLabel: "Avanzar {count} páginas",
|
|
1228
1345
|
};
|
|
1346
|
+
/**
|
|
1347
|
+
* TECH-2459 (beweossmbs, 24-sep) — los mismos textos en inglés, para cuando el idioma de la app es
|
|
1348
|
+
* inglés. El defecto medido: con la interfaz en inglés, «anterior» se anunciaba «Página anterior».
|
|
1349
|
+
* La app SÍ comunica su idioma (`<HeroUIProvider locale="en-US">` → `I18nProvider` de react-aria,
|
|
1350
|
+
* `src/compat/heroui-v2-shims.tsx`), y es el mismo que ya siguen los textos propios de react-aria
|
|
1351
|
+
* (la casilla «Select all» de la tabla, los calendarios…); la fachada lo ignoraba porque solo
|
|
1352
|
+
* miraba `translations`. Ahora el idioma elige la base y `translations` sigue ganando encima.
|
|
1353
|
+
*/
|
|
1354
|
+
const EN_TRANSLATIONS = {
|
|
1355
|
+
previousPageAriaLabel: "Previous page",
|
|
1356
|
+
nextPageAriaLabel: "Next page",
|
|
1357
|
+
jumpBackwardAriaLabel: "Jump back {count} pages",
|
|
1358
|
+
jumpForwardAriaLabel: "Jump forward {count} pages",
|
|
1359
|
+
};
|
|
1360
|
+
/**
|
|
1361
|
+
* Base de textos según el idioma de react-aria (`useLocale`): inglés si el locale es `en*`,
|
|
1362
|
+
* español en cualquier otro caso (el default de la librería, regla `auraui-i18n`).
|
|
1363
|
+
*
|
|
1364
|
+
* Sin provider, react-aria resuelve el idioma del NAVEGADOR (`navigator.language`), igual que
|
|
1365
|
+
* para sus propios textos: una app que no declare `locale` sigue al navegador. Para fijarlo, la
|
|
1366
|
+
* app pasa `locale` a `HeroUIProvider` o los textos por `translations`.
|
|
1367
|
+
*/
|
|
1368
|
+
function baseTranslations(locale) {
|
|
1369
|
+
return locale.toLowerCase().startsWith("en")
|
|
1370
|
+
? EN_TRANSLATIONS
|
|
1371
|
+
: DEFAULT_TRANSLATIONS$g;
|
|
1372
|
+
}
|
|
1229
1373
|
/**
|
|
1230
1374
|
* TECH-2459 · elipsis navegable — páginas que salta la elipsis. Literal de v2:
|
|
1231
1375
|
* `dotsJump = 5`, default desestructurado en `@heroui/pagination@2.2.20`
|
|
@@ -1345,7 +1489,8 @@ function getPaginationRange(total, current) {
|
|
|
1345
1489
|
const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
|
|
1346
1490
|
const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
|
|
1347
1491
|
const currentPage = page ?? internalPage;
|
|
1348
|
-
const
|
|
1492
|
+
const { locale } = react.useLocale();
|
|
1493
|
+
const t = { ...baseTranslations(locale), ...translations };
|
|
1349
1494
|
const goTo = (next) => {
|
|
1350
1495
|
const clamped = Math.min(Math.max(next, 1), total);
|
|
1351
1496
|
if (page === undefined) {
|
|
@@ -1378,7 +1523,7 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
1378
1523
|
// lo nombraba ("previous page button", `use-pagination.js:213`), pero en
|
|
1379
1524
|
// inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
|
|
1380
1525
|
// i18n del repo, skill `auraui-i18n`).
|
|
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) => {
|
|
1526
|
+
, { "aria-label": t.previousPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, isDisabled ? "" : NAV_AT_EDGE_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) => {
|
|
1382
1527
|
if (item === "ellipsis") {
|
|
1383
1528
|
const isBefore = index < activeIndex;
|
|
1384
1529
|
const label = isBefore
|
|
@@ -1393,7 +1538,7 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
1393
1538
|
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
1539
|
}), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
|
|
1395
1540
|
/* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
|
|
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, {}) }) }))] }) }));
|
|
1541
|
+
, { "aria-label": t.nextPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, isDisabled ? "" : NAV_AT_EDGE_CLASS, cellRadius(isCompact, { isFirst: false, isLast: true })), isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) }))] }) }));
|
|
1397
1542
|
};
|
|
1398
1543
|
|
|
1399
1544
|
// TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
|
|
@@ -1669,7 +1814,12 @@ const ROOT_HELPER_SPACE_CLASS = {
|
|
|
1669
1814
|
};
|
|
1670
1815
|
const GROUP_PLACEMENT_CLASS = {
|
|
1671
1816
|
outside: undefined,
|
|
1672
|
-
|
|
1817
|
+
// `min-w-min` (TECH-2459, beweossmbs QA 24-sep, barra de la agenda por debajo de 1440px):
|
|
1818
|
+
// `.date-input-group` de v3 lleva `overflow-hidden`, y un ítem flex con overflow distinto de
|
|
1819
|
+
// `visible` tiene `min-width: auto` = 0. Con `flex-1` el grupo se quedaba con el hueco sobrante
|
|
1820
|
+
// y recortaba su propio botón de calendario (104px con 140 de contenido a 1280px). En v2 el
|
|
1821
|
+
// overflow era visible: el grupo conservaba su contenido y era la etiqueta la que se partía.
|
|
1822
|
+
"outside-left": "relative flex-1 min-w-min",
|
|
1673
1823
|
};
|
|
1674
1824
|
const HELPER_PLACEMENT_CLASS = {
|
|
1675
1825
|
outside: undefined,
|
|
@@ -2171,7 +2321,9 @@ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabl
|
|
|
2171
2321
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
2172
2322
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
2173
2323
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
2174
|
-
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown,
|
|
2324
|
+
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown,
|
|
2325
|
+
// Nombre y descripción accesibles: van al `TextField`, no al `<input>` (ver abajo).
|
|
2326
|
+
"aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest }, ref) => {
|
|
2175
2327
|
// El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
|
|
2176
2328
|
// (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
|
|
2177
2329
|
const inputRef = $8WNJc$react.useRef(null);
|
|
@@ -2214,6 +2366,19 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
2214
2366
|
// Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
|
|
2215
2367
|
// arriba desde el primer render (ver el bloque de arriba).
|
|
2216
2368
|
const labelFlotante = Boolean(label) && !placeholder;
|
|
2369
|
+
// ── Nombre accesible sin `label` visible ─────────────────────────────────────────────────
|
|
2370
|
+
//
|
|
2371
|
+
// Quien llama a `useLabel` (y avisa «If you do not provide a visible label, you must specify
|
|
2372
|
+
// an aria-label…») es el `TextField` de react-aria, que se los pasa al `<input>` por contexto.
|
|
2373
|
+
// Dejarlos en el spread del `<input>` hacía que avisara AUNQUE la app diera nombre (medido en
|
|
2374
|
+
// beweossmbs: 14/16 avisos al cargar `/clients/table`, del buscador con solo placeholder).
|
|
2375
|
+
// Y `aria-describedby` en el `<input>` pisaba el id de la `Description` propia. v2 no avisaba
|
|
2376
|
+
// nunca: `"aria-label": label ? aria-label : safeAriaLabel(aria-label, placeholder)`
|
|
2377
|
+
// (`@heroui/input@2.4.33`, `dist/use-input.js:119`). Con `aria-labelledby` no se añade el
|
|
2378
|
+
// placeholder: react-aria lo concatenaría al nombre del elemento referido.
|
|
2379
|
+
const accessibleLabel = label || ariaLabelledBy
|
|
2380
|
+
? ariaLabel
|
|
2381
|
+
: ariaLabel?.trim() || placeholder?.trim() || undefined;
|
|
2217
2382
|
const handleChange = (event) => {
|
|
2218
2383
|
onChange?.(event);
|
|
2219
2384
|
onValueChange?.(event.target.value);
|
|
@@ -2246,7 +2411,7 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
2246
2411
|
clearValue();
|
|
2247
2412
|
}
|
|
2248
2413
|
};
|
|
2249
|
-
return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
|
|
2414
|
+
return (jsxRuntime.jsxs(react.TextField, { id: inputId, "aria-label": accessibleLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, value: currentValue,
|
|
2250
2415
|
// Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
|
|
2251
2416
|
// (que se entera por `onChange`/`onValueChange` del `<input>`).
|
|
2252
2417
|
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" : "",
|
|
@@ -2263,7 +2428,15 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
2263
2428
|
// pase el consumidor.
|
|
2264
2429
|
"w-full",
|
|
2265
2430
|
// 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(
|
|
2431
|
+
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(
|
|
2432
|
+
// `w-full min-w-0` (TECH-2459, beweossmbs QA 24-sep): `.input-group__input`
|
|
2433
|
+
// de v3 es `flex-1` sin ancho, así que el `<input>` aportaba su ancho
|
|
2434
|
+
// intrínseco (~151px) como min-content y el campo no encogía en una fila
|
|
2435
|
+
// flex estrecha (hex del color picker de Agenda → Colors: 180px y 35px
|
|
2436
|
+
// fuera de su fila; en v2, 132px). v2 ponía `w-full` en este slot
|
|
2437
|
+
// (`@heroui/theme@2.4.15`, `dist/components/input.js:111`), y un ancho
|
|
2438
|
+
// en porcentaje no cuenta para el min-content de un `<input>`.
|
|
2439
|
+
"w-full min-w-0 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 }))] }));
|
|
2267
2440
|
});
|
|
2268
2441
|
Input.displayName = "Input";
|
|
2269
2442
|
|
|
@@ -2385,21 +2558,73 @@ const SHADOW_CLASS = {
|
|
|
2385
2558
|
md: "shadow-[shadow:var(--aurora-shadow-medium)]",
|
|
2386
2559
|
lg: "shadow-[shadow:var(--aurora-shadow-large)]",
|
|
2387
2560
|
};
|
|
2561
|
+
// Geometría v2 de la raíz (slot `base` de `@heroui/theme@2.4.15`, `dist/components/card.js`):
|
|
2562
|
+
// `flex flex-col relative overflow-hidden`, SIN `gap`. v3 (`@heroui/styles/…/card.css:5`) pone
|
|
2563
|
+
// `gap-3 overflow-visible`, y eso ROMPE layouts de v2 (medido por beweossmbs con la .141): con
|
|
2564
|
+
// tres hijos la card crece 24px (Notificaciones automáticas: 220px frente a 192, y la última card
|
|
2565
|
+
// queda cortada por su contenedor), y el texto que no cabe se sale de la card en vez de
|
|
2566
|
+
// recortarse (cabecera de recurso de la agenda, vista Semana: 80px frente a 56 y los nombres
|
|
2567
|
+
// fuera). Va primero en el `cn`: un `gap-*`/`overflow-*` de la app lo retira por fusión.
|
|
2568
|
+
// `overflow-hidden` no quita nada que se viera: la onda ya se recorta en su propia capa
|
|
2569
|
+
// (`Card.css`), el anillo de foco es un `ring` (`box-shadow`, no lo recorta el `overflow` de su
|
|
2570
|
+
// propio nodo) y la sombra de la card se pinta fuera de su caja. Medido en la story `V2Geometry`: tres
|
|
2571
|
+
// hijos de 20px dan 108px (132 con el gap de v3) y el texto que desborda ya no asoma.
|
|
2572
|
+
const ROOT_V2_CLASS = "gap-0 overflow-hidden";
|
|
2388
2573
|
// Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
|
|
2389
2574
|
// traducidos a los que existen en Tailwind 4 + Aurora:
|
|
2390
2575
|
// isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
|
|
2391
2576
|
// tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
|
|
2392
2577
|
// marcaba `data-pressed` en un botón nativo)
|
|
2393
2578
|
// disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
|
|
2394
|
-
// foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
|
|
2579
|
+
// foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`). Aquí va con el
|
|
2580
|
+
// MISMO anillo que los botones de v3/Aurora (`status-focused` → `focus-ring`:
|
|
2581
|
+
// `ring-2 ring-focus ring-offset-background` + offset 2px), no con `outline`: en
|
|
2582
|
+
// Tailwind 4 `outline-none` fija `--tw-outline-style: none` y `focus-visible:outline-2`
|
|
2583
|
+
// hereda ese estilo, así que el anillo NO se pintaba (regresión de la .141, al pasar de
|
|
2584
|
+
// `<button>` a `<div role="button">`). El `ring` es `box-shadow` fuera de la caja: no lo
|
|
2585
|
+
// recorta el `overflow-hidden` de la raíz y se compone con la sombra de la card.
|
|
2395
2586
|
// isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
|
|
2396
2587
|
// `data-hover` — trampa 3 de la skill `auraui-components`)
|
|
2397
2588
|
// isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
|
|
2398
2589
|
// fullWidth → `w-full`
|
|
2399
|
-
const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:
|
|
2590
|
+
const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background [-webkit-tap-highlight-color:transparent]";
|
|
2400
2591
|
const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
|
|
2401
2592
|
const HOVERABLE_CLASS = "hover:bg-content2";
|
|
2402
2593
|
const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
2594
|
+
// Controles que tienen su propia pulsación. Un clic que nace dentro de uno de ellos es SUYO, no de
|
|
2595
|
+
// la card: los `Button` de Aurora (react-aria) ya paran la propagación del `click` (medido en
|
|
2596
|
+
// jsdom con el `Button` de la librería), pero un `<button>`/`<a>` nativo o un `<input>` no.
|
|
2597
|
+
const CONTROL_INTERNO = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="tab"], [role="option"]';
|
|
2598
|
+
function desdeControlInterno(event) {
|
|
2599
|
+
const target = event.target;
|
|
2600
|
+
if (!target || target === event.currentTarget) {
|
|
2601
|
+
return false;
|
|
2602
|
+
}
|
|
2603
|
+
const control = target.closest?.(CONTROL_INTERNO);
|
|
2604
|
+
return (!!control &&
|
|
2605
|
+
control !== event.currentTarget &&
|
|
2606
|
+
event.currentTarget.contains(control));
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Tecla → `PressEvent` con la forma del de react-aria (`pointerType: "keyboard"`, punto en el
|
|
2610
|
+
* centro de la card, como hace `pressEventFromClick` con un click sintético de `detail === 0`).
|
|
2611
|
+
*/
|
|
2612
|
+
function pressEventFromKey(event) {
|
|
2613
|
+
const target = event.currentTarget;
|
|
2614
|
+
const rect = target.getBoundingClientRect();
|
|
2615
|
+
return {
|
|
2616
|
+
type: "press",
|
|
2617
|
+
pointerType: "keyboard",
|
|
2618
|
+
target,
|
|
2619
|
+
shiftKey: event.shiftKey,
|
|
2620
|
+
ctrlKey: event.ctrlKey,
|
|
2621
|
+
metaKey: event.metaKey,
|
|
2622
|
+
altKey: event.altKey,
|
|
2623
|
+
x: rect.width / 2,
|
|
2624
|
+
y: rect.height / 2,
|
|
2625
|
+
continuePropagation: () => undefined,
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2403
2628
|
/**
|
|
2404
2629
|
* Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
|
|
2405
2630
|
*
|
|
@@ -2422,39 +2647,83 @@ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
|
2422
2647
|
* `isHoverable`/`isDisabled`/`fullWidth`.
|
|
2423
2648
|
*
|
|
2424
2649
|
* `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
|
|
2650
|
+
* `Input.tsx`) y apunta al nodo raíz, que es siempre un `<div>` (con `isPressable`,
|
|
2651
|
+
* `<div role="button">`).
|
|
2426
2652
|
*/
|
|
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) => {
|
|
2653
|
+
const Card = $8WNJc$react.forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, onKeyDown, onKeyUp, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
|
|
2428
2654
|
// Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
|
|
2429
2655
|
const sinRipple = disableRipple || disableAnimation;
|
|
2430
2656
|
const { ondas, lanzar, quitar } = useRipple();
|
|
2657
|
+
const pulsable = isPressable && !isDisabled;
|
|
2658
|
+
const press = $8WNJc$react.useCallback((pressEvent) => {
|
|
2659
|
+
onPress?.(pressEvent);
|
|
2660
|
+
if (!sinRipple) {
|
|
2661
|
+
lanzar(pressEvent);
|
|
2662
|
+
}
|
|
2663
|
+
}, [onPress, sinRipple, lanzar]);
|
|
2431
2664
|
const handleClick = $8WNJc$react.useCallback((event) => {
|
|
2665
|
+
// Card pulsable deshabilitada: ni `onClick` de la app, como el `disabled` nativo del
|
|
2666
|
+
// `<button>` que era hasta la .140.
|
|
2667
|
+
if (isPressable && isDisabled) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2432
2670
|
onClick?.(event);
|
|
2433
|
-
if (!
|
|
2671
|
+
if (!pulsable || desdeControlInterno(event)) {
|
|
2434
2672
|
return;
|
|
2435
2673
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2674
|
+
press(pressEventFromClick(event));
|
|
2675
|
+
}, [onClick, isPressable, isDisabled, pulsable, press]);
|
|
2676
|
+
// Teclado de un `<button>` nativo, que el `<div role="button">` no trae: Enter pulsa al
|
|
2677
|
+
// bajar la tecla y Espacio al soltarla. `preventDefault` en ambos: Espacio haría scroll de
|
|
2678
|
+
// la página y Enter podría enviar un `<form>` que envuelva la card. Solo cuando el foco está
|
|
2679
|
+
// EN la card: una tecla sobre un botón interno es de ese botón (react-aria ya para su
|
|
2680
|
+
// propagación, y esta guarda cubre también a los controles nativos).
|
|
2681
|
+
const handleKeyDown = $8WNJc$react.useCallback((event) => {
|
|
2682
|
+
onKeyDown?.(event);
|
|
2683
|
+
if (!pulsable || event.target !== event.currentTarget) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
if (event.key === " ") {
|
|
2687
|
+
event.preventDefault();
|
|
2688
|
+
}
|
|
2689
|
+
else if (event.key === "Enter") {
|
|
2690
|
+
event.preventDefault();
|
|
2691
|
+
if (!event.repeat) {
|
|
2692
|
+
press(pressEventFromKey(event));
|
|
2693
|
+
}
|
|
2440
2694
|
}
|
|
2441
|
-
}, [
|
|
2442
|
-
const
|
|
2695
|
+
}, [onKeyDown, pulsable, press]);
|
|
2696
|
+
const handleKeyUp = $8WNJc$react.useCallback((event) => {
|
|
2697
|
+
onKeyUp?.(event);
|
|
2698
|
+
if (!pulsable ||
|
|
2699
|
+
event.target !== event.currentTarget ||
|
|
2700
|
+
event.key !== " ") {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
event.preventDefault();
|
|
2704
|
+
press(pressEventFromKey(event));
|
|
2705
|
+
}, [onKeyUp, pulsable, press]);
|
|
2706
|
+
const combinedClassName = react.cn(ROOT_V2_CLASS, 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
2707
|
// El `className` de la app, último: gana por fusión (ver JSDoc).
|
|
2444
2708
|
className);
|
|
2445
|
-
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable
|
|
2446
|
-
//
|
|
2447
|
-
//
|
|
2448
|
-
//
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2709
|
+
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable`, y hasta la .140 esta fachada lo
|
|
2710
|
+
// imitaba con el `render` de v3. Se quitó porque las apps meten botones DENTRO de la card
|
|
2711
|
+
// pulsable (`ChannelCard` de smbs, Linda → Compartir: «Compartir»/«Integrar»), y
|
|
2712
|
+
// `<button>` dentro de `<button>` es HTML inválido: React avisa en consola
|
|
2713
|
+
// («cannot be a descendant of <button>», medido en jsdom) y el parser del navegador lo
|
|
2714
|
+
// reparenta. La card se queda en el `<div>` de v3 con la semántica de botón de v2
|
|
2715
|
+
// (`useCard`: `role: "button"` + `tabIndex: isPressable ? 0 : -1`) y el teclado a mano
|
|
2716
|
+
// (ver `handleKeyDown`). El `render` de la app pasa tal cual.
|
|
2717
|
+
const pressableProps = isPressable
|
|
2718
|
+
? {
|
|
2719
|
+
role: "button",
|
|
2720
|
+
tabIndex: isDisabled ? undefined : 0,
|
|
2721
|
+
"aria-disabled": isDisabled || undefined,
|
|
2722
|
+
onKeyDown: handleKeyDown,
|
|
2723
|
+
onKeyUp: handleKeyUp,
|
|
2724
|
+
}
|
|
2725
|
+
: {};
|
|
2726
|
+
return (jsxRuntime.jsxs(react.Card, { ...props, ...pressableProps, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: render, children: [children, isPressable && ondas.length > 0 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
|
|
2458
2727
|
});
|
|
2459
2728
|
Card.displayName = "Card";
|
|
2460
2729
|
|
|
@@ -3184,7 +3453,7 @@ const CLEAR_GAP_WITH_TRIGGER_UNDERLINED_CLASS = "me-9";
|
|
|
3184
3453
|
* ⚠️ `h-4 w-4 p-0` deja el glifo de 16px ocupando la caja entera; el `[&_svg]:m-0` quita los
|
|
3185
3454
|
* márgenes negativos de v3 para que el centro del glifo coincida con el centro de la caja.
|
|
3186
3455
|
*/
|
|
3187
|
-
const CLEAR_BUTTON_CLASS = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
|
|
3456
|
+
const CLEAR_BUTTON_CLASS$2 = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
|
|
3188
3457
|
const DEFAULT_ICON = (jsxRuntime.jsx(IconComponent, { icon: "solar:magnifer-outline", className: "text-default-400 pointer-events-none shrink-0", size: "sm", "aria-hidden": true }));
|
|
3189
3458
|
/**
|
|
3190
3459
|
* AuraAutocomplete genérico reutilizable, migrado al `ComboBox` compound de HeroUI v3 (el
|
|
@@ -3209,7 +3478,9 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
|
|
|
3209
3478
|
// limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
|
|
3210
3479
|
// pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
|
|
3211
3480
|
// controlado con `!== undefined`).
|
|
3212
|
-
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection,
|
|
3481
|
+
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, isOpen, onOpenChange, popoverProps,
|
|
3482
|
+
// Aceptada y descartada: ver `AuraAutocompleteProps.labelPlacement`.
|
|
3483
|
+
labelPlacement: _labelPlacement, scrollShadowProps, ...props }) => {
|
|
3213
3484
|
const generatedId = $8WNJc$react.useId();
|
|
3214
3485
|
const autoId = id || generatedId;
|
|
3215
3486
|
// Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
|
|
@@ -3225,6 +3496,26 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3225
3496
|
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
3226
3497
|
/** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
|
|
3227
3498
|
const inputRef = $8WNJc$react.useRef(null);
|
|
3499
|
+
/*
|
|
3500
|
+
* `isOpen` controlado (ver `AuraAutocompleteProps.isOpen`). La fachada sigue el estado de
|
|
3501
|
+
* apertura de react-aria por su `onOpenChange` y decide con él si el popover se ve: visible solo
|
|
3502
|
+
* si react-aria lo tiene abierto Y la app no lo ha cerrado. `ComboBox.Popover` es el `Popover`
|
|
3503
|
+
* de react-aria-components, que con `isOpen` propio usa ese valor en vez del estado del
|
|
3504
|
+
* `ComboBox` (`Popover.mjs`: `props.isOpen != null ? localState : contextState`).
|
|
3505
|
+
*/
|
|
3506
|
+
const [ariaOpen, setAriaOpen] = $8WNJc$react.useState(false);
|
|
3507
|
+
const handleOpenChange = (open, menuTrigger) => {
|
|
3508
|
+
setAriaOpen(open);
|
|
3509
|
+
onOpenChange?.(open, menuTrigger);
|
|
3510
|
+
};
|
|
3511
|
+
const handleInputChange = (value) => {
|
|
3512
|
+
// La lista está abierta para react-aria pero la app la cerró: react-aria no volverá a avisar
|
|
3513
|
+
// (para él ya está abierta), así que la fachada pide abrir como hacía v2 al teclear.
|
|
3514
|
+
if (isOpen === false && ariaOpen) {
|
|
3515
|
+
onOpenChange?.(true, "input");
|
|
3516
|
+
}
|
|
3517
|
+
onInputChange?.(value);
|
|
3518
|
+
};
|
|
3228
3519
|
// `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
|
|
3229
3520
|
// state.inputValue?.length > 0` (`chunk-NJJHRP6D.mjs:273`), y apagado por `isDisabled`/
|
|
3230
3521
|
// `isReadOnly` como en el cálculo de `isClearable` del hook (línea 20 del mismo fichero).
|
|
@@ -3258,7 +3549,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3258
3549
|
onInputChange?.("");
|
|
3259
3550
|
inputRef.current?.focus();
|
|
3260
3551
|
};
|
|
3261
|
-
return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange:
|
|
3552
|
+
return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: handleInputChange, onSelectionChange: onSelectionChange, onOpenChange: handleOpenChange, 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: [
|
|
3262
3553
|
SIZE_CLASS$2[size],
|
|
3263
3554
|
PADDING_CLASS$1[size],
|
|
3264
3555
|
variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
|
|
@@ -3307,7 +3598,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3307
3598
|
* su `aria-label` traducido, y alcanzable con teclado como la ✕ de `Input`.
|
|
3308
3599
|
*/
|
|
3309
3600
|
slot: null, "aria-label": t.clearButtonAriaLabel, className: [
|
|
3310
|
-
CLEAR_BUTTON_CLASS,
|
|
3601
|
+
CLEAR_BUTTON_CLASS$2,
|
|
3311
3602
|
resolvedIcon != null
|
|
3312
3603
|
? CLEAR_GAP_WITH_ICON_CLASS
|
|
3313
3604
|
: variant === "underlined"
|
|
@@ -3321,7 +3612,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3321
3612
|
isDisabled ? ICON_DISABLED_CLASS : "",
|
|
3322
3613
|
]
|
|
3323
3614
|
.filter(Boolean)
|
|
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",
|
|
3615
|
+
.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, { ...popoverProps, isOpen: isOpen === undefined ? undefined : isOpen && ariaOpen, children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first",
|
|
3616
|
+
// `scrollShadowProps` de v2 al contenedor con scroll (el `ListBox`): la clase
|
|
3617
|
+
// de la app va al final y gana por `cn`, y `overflow-y-auto` para que un
|
|
3618
|
+
// `max-h-*` recorte desplazando y no desbordando.
|
|
3619
|
+
className: scrollShadowProps?.className
|
|
3620
|
+
? react.cn(OPCIONES_CLASS, "overflow-y-auto", scrollShadowProps.className)
|
|
3621
|
+
: OPCIONES_CLASS, style: scrollShadowProps?.style, renderEmptyState: hideEmptyContent
|
|
3325
3622
|
? undefined
|
|
3326
3623
|
: () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
|
|
3327
3624
|
// El normalizador de `utils/` está tipado solo para `ReactNode`; con una
|
|
@@ -3572,7 +3869,12 @@ const ColorPicker = $8WNJc$react.forwardRef(({ value, onChange, label, required
|
|
|
3572
3869
|
${error
|
|
3573
3870
|
? "border-danger dark:border-danger-400"
|
|
3574
3871
|
: "border-default-300 dark:border-default-700"}
|
|
3575
|
-
`, "aria-label": t.customColorLabel, title: t.customColorLabel }), jsxRuntime.jsx(Input, { type: "text", value: textValue, onValueChange: handleTextChange, onBlur: handleTextBlur, placeholder: t.placeholder, disabled: disabled, isDisabled: disabled, isInvalid: error || isInvalidFormat, size: "sm", variant: "bordered",
|
|
3872
|
+
`, "aria-label": t.customColorLabel, title: t.customColorLabel }), jsxRuntime.jsx(Input, { type: "text", value: textValue, onValueChange: handleTextChange, onBlur: handleTextBlur, placeholder: t.placeholder, disabled: disabled, isDisabled: disabled, isInvalid: error || isInvalidFormat, size: "sm", variant: "bordered",
|
|
3873
|
+
// `min-w-0` (TECH-2459, beweossmbs QA 24-sep): el campo es un ítem de la fila
|
|
3874
|
+
// flex de la muestra de color y tiene que poder encoger en huecos estrechos
|
|
3875
|
+
// (Agenda → Colors: 180px). El `w-full min-w-0` del `<input>` en la fachada
|
|
3876
|
+
// ya lo arregla; esto es el suelo explícito en la raíz del campo.
|
|
3877
|
+
className: "min-w-0", classNames: {
|
|
3576
3878
|
input: "uppercase font-mono text-sm",
|
|
3577
3879
|
inputWrapper: "min-h-8 h-8 focus-within:!border-primary-500",
|
|
3578
3880
|
}, startContent: textValue.startsWith("#") ? null : (jsxRuntime.jsx("span", { className: "text-default-400 text-sm", children: "#" })) })] }), (error || isInvalidFormat) && (jsxRuntime.jsx("p", { className: "text-xs text-danger", children: isInvalidFormat ? t.invalidColorFormat : errorText }))] }), showPredefinedColors && predefinedColors.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "text-xs font-medium text-default-600 mb-2", children: t.predefinedColorsLabel }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: predefinedColors.map((color) => renderColorCircle(color.value, color.name, normalizeHexColor(value) === normalizeHexColor(color.value))) })] })), enableRecentColors && recentColors.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "text-xs font-medium text-default-600 mb-2", children: t.recentColorsLabel }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: recentColors.map((color) => renderColorCircle(color, color, normalizeHexColor(value) === color)) })] }))] }));
|
|
@@ -6588,7 +6890,16 @@ const ITEM_COLLAPSED_CLASS = "w-11 h-11 gap-0 p-0 justify-center";
|
|
|
6588
6890
|
// `w-full flex-1`: el slot `title` de v2 era `flex-1 text-small font-normal w-full`, y su slot
|
|
6589
6891
|
// `wrapper` `w-full` — el título se comía el espacio libre y empujaba el `endContent` al borde.
|
|
6590
6892
|
// El `[data-slot="label"]` de v3 es `w-fit`, que es la otra mitad de P-14.
|
|
6591
|
-
|
|
6893
|
+
//
|
|
6894
|
+
// `min-w-0 truncate`: v2 le ponía `truncate` al slot `title` siempre que el título fuera texto
|
|
6895
|
+
// (`@heroui/theme@2.4.15`, `dist/components/listbox.js`, variante `hasTitleTextChild: { true:
|
|
6896
|
+
// { title: "truncate" } }`; en el menú el título es siempre un string). La migración copió el slot
|
|
6897
|
+
// base y se dejó la variante. Sin ella, una etiqueta que no cabe no se corta:
|
|
6898
|
+
// medido en beweossmbs y en la story `Menu › LongLabelTruncation`, «menu_automations» ocupaba sus
|
|
6899
|
+
// 126,8px enteros y empujaba la chip «AI» de x=204,7 a x=222,8, 6px FUERA del ítem. `truncate` solo
|
|
6900
|
+
// no basta: un flex item parte de `min-width: auto` (su ancho de contenido) y no encoge por debajo;
|
|
6901
|
+
// `min-w-0` es lo que le deja ceder el hueco a la chip, y entonces sí corta con elipsis.
|
|
6902
|
+
const ITEM_TITLE_CLASS = "w-full min-w-0 flex-1 truncate text-sm font-medium text-default-500 group-data-[selected=true]:text-default-600";
|
|
6592
6903
|
// TECH-2459 · G2 · fix de navegación — resolución de un ítem por su clave.
|
|
6593
6904
|
//
|
|
6594
6905
|
// `onSelectionChange` del `ListBox` entrega la CLAVE, no el ítem. v2 no necesitaba esto:
|
|
@@ -8930,52 +9241,23 @@ const COLOR_VAR = {
|
|
|
8930
9241
|
warning: "var(--warning)",
|
|
8931
9242
|
danger: "var(--danger)",
|
|
8932
9243
|
};
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
* pista v2 (`@heroui/theme@2.4.15`, `dist/components/toggle.js`, slot `wrapper`)
|
|
8951
|
-
* sm 40×24 · md 48×28 · lg 56×32, `rounded-full`, `bg-default-200`
|
|
8952
|
-
* v3 (`@heroui/styles/dist/components/switch.css`)
|
|
8953
|
-
* sm 32×16 · md 40×20 · lg 48×24, `rounded-lg`/`rounded-xl`, `--default`
|
|
8954
|
-
* thumb v2 círculo `bg-white` con `shadow-small`; sm 16 · md 20 · lg 24
|
|
8955
|
-
* v3 rectángulo redondeado; sm 16,5×12 · md 22×16 · lg 27,5×20
|
|
8956
|
-
*
|
|
8957
|
-
* Ya no se pisa NADA de eso: la pista, el thumb, su radio, su color y su desplazamiento salen de
|
|
8958
|
-
* v3. Por eso desaparecieron `TRACK_CLASS`, `THUMB_CLASS`, el `bg-white`/`shadow-small` del thumb
|
|
8959
|
-
* y el `--switch-control-bg` que forzaba el gris apagado de v2 — y con ellos la clase `group`,
|
|
8960
|
-
* que solo existía para que el thumb pudiera leer el `data-selected` de esta raíz.
|
|
8961
|
-
*
|
|
8962
|
-
* Lo que SÍ se conserva, y no es aspecto:
|
|
8963
|
-
*
|
|
8964
|
-
* · `--switch-control-bg-checked` (ver `COLOR_VAR`): sostiene la prop `color` de la API v2, que
|
|
8965
|
-
* v3 eliminó. Sin ella `color="success"` y compañía dejarían de pintar. El valor de
|
|
8966
|
-
* `primary` coincide con el de v3 (`--accent` = `--color-primary`), así que el aspecto por
|
|
8967
|
-
* defecto es el de v3 igualmente.
|
|
8968
|
-
* · `inline-flex flex-row items-center` en la raíz: es COLOCACIÓN, no aspecto del control. v3
|
|
8969
|
-
* usa `flex flex-col items-start` para poder apilar `description`/`field-error`, slots que
|
|
8970
|
-
* esta fachada no renderiza; al ser **inline**, la caja participa de la línea de texto, y eso
|
|
8971
|
-
* es lo que la sitúa donde la esperan los consumidores (medido: 1px de diferencia en un
|
|
8972
|
-
* contenedor de bloque, y `schedule-row` se apoya en ello). Dentro de un contenedor flex da
|
|
8973
|
-
* igual: un ítem flex se blockifica.
|
|
8974
|
-
*
|
|
8975
|
-
* Consecuencia asumida: la referencia v2 de las stories con Switch deja de coincidir. Declarado
|
|
8976
|
-
* como CAMBIO-V3 en `vrt/classification-overrides.json`, con las cifras en
|
|
8977
|
-
* `.bewe/epics/TECH-2459/findings/p6-switch-aspecto-v3.md`.
|
|
8978
|
-
*/
|
|
9244
|
+
// Literales COMPLETOS (sin interpolar): Tailwind de las apps los descubre escaneando `dist`.
|
|
9245
|
+
const SIZE_CLASSES = {
|
|
9246
|
+
sm: {
|
|
9247
|
+
control: "h-6 w-10 rounded-full",
|
|
9248
|
+
thumb: "h-4 w-5 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-1.5rem)]",
|
|
9249
|
+
},
|
|
9250
|
+
md: {
|
|
9251
|
+
control: "h-7 w-12 rounded-full",
|
|
9252
|
+
thumb: "h-5 w-6 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-1.75rem)]",
|
|
9253
|
+
},
|
|
9254
|
+
lg: {
|
|
9255
|
+
control: "h-8 w-14 rounded-full",
|
|
9256
|
+
thumb: "h-6 w-7 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-2rem)]",
|
|
9257
|
+
},
|
|
9258
|
+
};
|
|
9259
|
+
// Thumb ENCENDIDO blanco en cualquier pack y con cualquier `color` (defecto 1 del bloque de arriba).
|
|
9260
|
+
const THUMB_ON_CLASS = "group-data-[selected=true]/switch:bg-white";
|
|
8979
9261
|
// Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando a
|
|
8980
9262
|
// través de la fachada (`<Switch><Switch.Content><Switch.Control><Switch.Thumb/>…`). `Root` se deja
|
|
8981
9263
|
// FUERA a propósito (mismo criterio que `Alert` y `Checkbox`): la raíz de v3 es justo la pieza que
|
|
@@ -9002,12 +9284,32 @@ function hasCompoundChildren$2(children) {
|
|
|
9002
9284
|
*
|
|
9003
9285
|
* Dos modos, decididos por los `children`:
|
|
9004
9286
|
* - **API v2** (`children` como label, `isSelected`/`defaultSelected`, `onValueChange`, `color`,
|
|
9005
|
-
* `size`): la fachada compone `Switch.Content`/`Switch.Control`/`Switch.Thumb` por
|
|
9006
|
-
* repone `color` pisando `--switch-control-bg-checked
|
|
9287
|
+
* `size`, `classNames`): la fachada compone `Switch.Content`/`Switch.Control`/`Switch.Thumb` por
|
|
9288
|
+
* dentro, repone `color` pisando `--switch-control-bg-checked`, aplica su escala de tamaños y el
|
|
9289
|
+
* thumb encendido blanco (ver el bloque de la cabecera).
|
|
9007
9290
|
* - **API compound v3** (`<Switch><Switch.Content>…`): pasa los children tal cual a la raíz de
|
|
9008
|
-
* HeroUI, sin geometría ni colocación propias.
|
|
9009
|
-
|
|
9010
|
-
|
|
9291
|
+
* HeroUI, sin geometría ni colocación propias: ahí manda el JSX del consumidor.
|
|
9292
|
+
*
|
|
9293
|
+
* `classNames` (slots de v2 → nodo de v3; las clases de la app van al FINAL y ganan por `cn`):
|
|
9294
|
+
*
|
|
9295
|
+
* base → `Switch.Content`, la `<label>` que contiene la pista y la etiqueta: es el MISMO papel
|
|
9296
|
+
* que tenía `base` en v2 (una `<label>` con esos dos hijos directos, que la app
|
|
9297
|
+
* coloca con `flex-row-reverse justify-between`, y toda ella clicable). Pero la raíz
|
|
9298
|
+
* de v3 (`SwitchField`) la envuelve con una caja `inline-flex`, y un `w-full` de la
|
|
9299
|
+
* app se resolvería contra esa caja encogida. Así que con `base` la raíz pasa a
|
|
9300
|
+
* `display: contents` (se aparta de la maqueta) y `className` viaja con `base` a la
|
|
9301
|
+
* `<label>`, como en v2 (`cn(classNames.base, className)`). Lo único de la raíz que
|
|
9302
|
+
* `contents` pierde es la opacidad de `status-disabled` (el cursor y el
|
|
9303
|
+
* `pointer-events` se heredan; los tokens de color, también): se repone en la
|
|
9304
|
+
* `<label>` con su `data-disabled`. Medido en el navegador con el JSX de smbs: la
|
|
9305
|
+
* fila entera (390×52) conmuta, no solo el texto y la pista. Sin `base` no cambia
|
|
9306
|
+
* nada.
|
|
9307
|
+
* wrapper → la pista (`Switch.Control`).
|
|
9308
|
+
* thumb → el thumb (`Switch.Thumb`).
|
|
9309
|
+
* label → un `<span data-slot="label">` que envuelve los `children`; solo existe si se pasa
|
|
9310
|
+
* `classNames.label` (sin él los children siguen PELADOS, como hasta ahora).
|
|
9311
|
+
*/
|
|
9312
|
+
const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChange, className, classNames, style, ...props }) => {
|
|
9011
9313
|
const generatedId = $8WNJc$react.useId();
|
|
9012
9314
|
const autoId = id || generatedId;
|
|
9013
9315
|
const handleChange = (isSelected) => {
|
|
@@ -9027,6 +9329,10 @@ const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChang
|
|
|
9027
9329
|
// a `status` también en compound). Sin `color`, el encendido es el `--accent` de v3.
|
|
9028
9330
|
style: color === undefined ? style : colorStyle, onChange: handleChange, children: children }));
|
|
9029
9331
|
}
|
|
9332
|
+
const sizeClasses = SIZE_CLASSES[size] ?? SIZE_CLASSES.md;
|
|
9333
|
+
// Con `classNames.base`, la `<label>` (`Switch.Content`) hace de `base` de v2 y la raíz se
|
|
9334
|
+
// aparta de la maqueta (ver `classNames` en el JSDoc de arriba).
|
|
9335
|
+
const hasBase = Boolean(classNames?.base);
|
|
9030
9336
|
return (jsxRuntime.jsx(react.Switch, { ...props, id: autoId, size: size,
|
|
9031
9337
|
// COLOCACIÓN, no aspecto (ver el bloque de arriba): v3 usa `flex flex-col items-start`
|
|
9032
9338
|
// para poder apilar `description`/`field-error`, slots que esta fachada no renderiza.
|
|
@@ -9034,10 +9340,12 @@ const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChang
|
|
|
9034
9340
|
// (Storybook centra con `#storybook-root` en `display: block`): con `flex` el widget cae
|
|
9035
9341
|
// en y=346 y con `inline-flex` en y=345, donde lo esperan los consumidores. Dentro de un
|
|
9036
9342
|
// contenedor flex (el uso real, p. ej. `schedule-row`) da igual: un ítem flex se
|
|
9037
|
-
// blockifica.
|
|
9038
|
-
className:
|
|
9039
|
-
|
|
9040
|
-
.
|
|
9343
|
+
// blockifica. `group/switch`: de aquí lee el thumb el `data-selected`.
|
|
9344
|
+
className: hasBase
|
|
9345
|
+
? "group/switch contents"
|
|
9346
|
+
: react.cn("group/switch inline-flex flex-row items-center", className), style: colorStyle, onChange: handleChange, children: jsxRuntime.jsxs(react.Switch.Content, { className: hasBase
|
|
9347
|
+
? react.cn("data-[disabled=true]:opacity-(--disabled-opacity)", classNames?.base, className)
|
|
9348
|
+
: undefined, children: [jsxRuntime.jsx(react.Switch.Control, { className: react.cn(sizeClasses.control, classNames?.wrapper), children: jsxRuntime.jsx(react.Switch.Thumb, { className: react.cn(sizeClasses.thumb, THUMB_ON_CLASS, classNames?.thumb) }) }), classNames?.label && children != null ? (jsxRuntime.jsx("span", { "data-slot": "label", className: classNames.label, children: children })) : (children)] }) }));
|
|
9041
9349
|
};
|
|
9042
9350
|
SwitchFacade.displayName = "Aurora.Switch";
|
|
9043
9351
|
/**
|
|
@@ -9662,7 +9970,98 @@ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text
|
|
|
9662
9970
|
*/
|
|
9663
9971
|
const DEFAULT_TRANSLATIONS$8 = {
|
|
9664
9972
|
emptyContent: "No hay opciones",
|
|
9973
|
+
searchPlaceholder: "Buscar…",
|
|
9974
|
+
// Mismo literal que la lista vacía de `AuraAutocomplete`: es el mismo caso (un filtro por texto
|
|
9975
|
+
// que no encuentra nada), así que las dos fachadas lo dicen igual.
|
|
9976
|
+
noResults: "No se encontraron resultados",
|
|
9977
|
+
// Mismo literal que la ✕ de `Input` y de `AuraAutocomplete`.
|
|
9978
|
+
clearButtonAriaLabel: "Limpiar",
|
|
9665
9979
|
};
|
|
9980
|
+
/*
|
|
9981
|
+
* EL BUSCADOR Y LA ✕ (TECH-2459, hallazgo beweossmbs 23-sep): `isSearchable` e `isClearable`.
|
|
9982
|
+
*
|
|
9983
|
+
* Con cualquiera de los dos la fachada cambia de RAÍZ: monta el compound `Autocomplete` de v3 en
|
|
9984
|
+
* vez de `Select`. No es un compound distinto por dentro —`AutocompleteRoot` ES el `Select` de
|
|
9985
|
+
* react-aria (`@heroui/react/dist/components/autocomplete/autocomplete.js:50`, `jsx(Select, …)`),
|
|
9986
|
+
* así que `value`/`onChange`/`selectionMode`/`disabledKeys` y la colección funcionan igual y la
|
|
9987
|
+
* traducción de la API v2 de abajo no cambia ni una línea—, pero su disparador sí:
|
|
9988
|
+
*
|
|
9989
|
+
* - `Autocomplete.Trigger` es un `Group` (un `<div>`), no un `<button>`. Es lo que permite meter la
|
|
9990
|
+
* ✕ DENTRO del campo sin anidar dos botones (trampa nº 4 de `auraui-components`). El botón que
|
|
9991
|
+
* recibe el foco es el del `Autocomplete.Indicator`, que consume el `ButtonContext` del Select
|
|
9992
|
+
* (`autocomplete.js`, `jsx(Button, { children: indicator })`): Tab llega al chevron y desde ahí
|
|
9993
|
+
* las flechas/Enter abren la lista, igual que en el `Select` de siempre.
|
|
9994
|
+
* - `Autocomplete.Filter` (el `Autocomplete` de react-aria) conecta un `SearchField` con el
|
|
9995
|
+
* `ListBox`: el texto filtra la colección y las flechas mueven el foco virtual de la lista sin
|
|
9996
|
+
* salir del buscador. Es la receta oficial «Migrating to v3 Autocomplete (searchable select)»
|
|
9997
|
+
* de la guía de migración, con `useFilter({ sensitivity: "base" })`: sin distinguir mayúsculas
|
|
9998
|
+
* ni acentos («cancun» encuentra «Cancún»).
|
|
9999
|
+
*
|
|
10000
|
+
* Sin ninguna de las dos props la fachada sigue montando `Select`, exactamente como antes: los
|
|
10001
|
+
* 107 `Select` de beweossmbs que no las pasan no cambian ni un nodo.
|
|
10002
|
+
*
|
|
10003
|
+
* Por qué la ✕ va también por aquí y no sobre el `Select`: el disparador de `Select` es un
|
|
10004
|
+
* `<button>` (no admite otro dentro) y la fachada no alcanza el estado de react-aria para vaciar la
|
|
10005
|
+
* selección de un campo no controlado (`SelectStateContext` no lo reexporta `@heroui/react`, e
|
|
10006
|
+
* importarlo de `react-aria-components` metería una SEGUNDA copia del contexto en el bundle, que no
|
|
10007
|
+
* casaría con la de HeroUI). `Autocomplete.ClearButton` sí lo alcanza: vacía la selección con
|
|
10008
|
+
* `state.selectionManager.setSelectedKeys(new Set())` y llama a `onClear`.
|
|
10009
|
+
*/
|
|
10010
|
+
/** Hueco del valor cuando la ✕ va a su derecha: el hueco del chevron lo reserva la ✕. */
|
|
10011
|
+
const VALUE_ROOM_CLEARABLE_CLASS = "me-1 min-w-0 truncate";
|
|
10012
|
+
/*
|
|
10013
|
+
* La ✕. `.autocomplete__clear-button` de v3 ya la pinta como v2 (glifo sin caja, `bg-transparent`,
|
|
10014
|
+
* gris `text-muted`) y la esconde con `opacity-0` cuando no hay nada elegido. v2 no la montaba sin
|
|
10015
|
+
* selección (`@heroui/select@2.4.33`, `chunk-BJWIL32D.mjs:64`: `isClearable &&
|
|
10016
|
+
* state.selectedItems?.length`), así que aquí se retira del flujo en ese estado en vez de dejar un
|
|
10017
|
+
* hueco invisible de 20px.
|
|
10018
|
+
*
|
|
10019
|
+
* `me-5`: el chevron es `absolute end-3` (12px del borde, 16px de ancho) y la ✕ va en flujo, que
|
|
10020
|
+
* acaba en el relleno del trigger (`px-3`, 12px). Sin margen caería ENCIMA del chevron; con 20px su
|
|
10021
|
+
* canto derecho queda a 4px del izquierdo del chevron.
|
|
10022
|
+
*/
|
|
10023
|
+
const CLEAR_BUTTON_CLASS$1 = "z-10 me-5 data-[empty=true]:hidden";
|
|
10024
|
+
/*
|
|
10025
|
+
* EL FOCO DEL TECLADO (hallazgo del coordinador con Playwright, 23-sep). En la raíz `Autocomplete`
|
|
10026
|
+
* el único elemento tabulable es el botón del chevron —el `Button` de react-aria que monta
|
|
10027
|
+
* `Autocomplete.Indicator`, sin clase propia ni forma de pasarle una—, y en el flujo mide **0×0**:
|
|
10028
|
+
* su único hijo es el SVG, que es `absolute`. El foco caía en una caja vacía (el borde del disparador
|
|
10029
|
+
* sí cambiaba, por `focus-within`, pero el elemento enfocado no tenía caja para un lector de
|
|
10030
|
+
* pantalla, un magnificador o un anillo propio).
|
|
10031
|
+
*
|
|
10032
|
+
* Se estira ese botón sobre el disparador entero (`absolute inset-0`, mismo radio): el elemento
|
|
10033
|
+
* enfocado pasa a medir lo mismo que el disparador, como el `<button>` del `Select` normal, y el
|
|
10034
|
+
* SVG sigue en su sitio porque su contenedor posicionado cubre la misma caja. Pulsar en cualquier
|
|
10035
|
+
* punto sigue abriendo (el `Group` ya abría al pulsar, y el botón ya estaba DENTRO del grupo).
|
|
10036
|
+
* La ✕ va por encima (`z-10` en `CLEAR_BUTTON_CLASS`) para seguir recibiendo su clic.
|
|
10037
|
+
*
|
|
10038
|
+
* Se apunta por estructura porque es la única vía: `button:not([data-slot])` es ese botón, ya que
|
|
10039
|
+
* la ✕ lleva `data-slot="autocomplete-clear-button"`.
|
|
10040
|
+
*/
|
|
10041
|
+
const INDICATOR_BUTTON_OVERLAY_CLASS = [
|
|
10042
|
+
"[&>button:not([data-slot])]:absolute",
|
|
10043
|
+
"[&>button:not([data-slot])]:inset-0",
|
|
10044
|
+
"[&>button:not([data-slot])]:rounded-[inherit]",
|
|
10045
|
+
"[&>button:not([data-slot])]:outline-none",
|
|
10046
|
+
"[&>button:not([data-slot])]:cursor-[inherit]",
|
|
10047
|
+
].join(" ");
|
|
10048
|
+
/*
|
|
10049
|
+
* El foco en el disparador `Group`. El `FOCUS_BORDER_CLASS` del Select casa `aria-expanded` y
|
|
10050
|
+
* `:focus` del `<button>`; aquí el disparador es un `<div>` que no lleva ninguno de los dos: el foco
|
|
10051
|
+
* lo tiene el botón del chevron, DENTRO (`focus-within`), y el estado abierto lo escribe v3 en la
|
|
10052
|
+
* RAÍZ (`data-open="true"` en `div.autocomplete`), así que va por un `group` con nombre.
|
|
10053
|
+
*/
|
|
10054
|
+
const FOCUS_BORDER_GROUP_CLASS = "focus-within:border-primary-500! group-data-[open=true]/aura-select:border-primary-500!";
|
|
10055
|
+
/*
|
|
10056
|
+
* El buscador del desplegable. `.autocomplete__popover` de v3 ya lo coloca (`[data-slot=
|
|
10057
|
+
* "search-field"] { px-3 py-1 }`, `pt-2` en el popover, `overflow-hidden` + lista con su propio
|
|
10058
|
+
* scroll, de modo que el buscador no se va al desplazar la lista). Solo se retira el anillo de
|
|
10059
|
+
* foco de v3 (`status-focused-field` → `ring-2`), por lo mismo que en todos los campos de la
|
|
10060
|
+
* librería (`FIELD_FOCUS_CLASS`): el foco lo marca el borde, en primario.
|
|
10061
|
+
*/
|
|
10062
|
+
const SEARCH_GROUP_CLASS = "w-full ring-0 ring-offset-0 outline-0 focus-within:border-primary-500";
|
|
10063
|
+
/** El `ListBox` es el contenedor que se desplaza cuando el consumidor le fija alto. */
|
|
10064
|
+
const LISTBOX_SCROLL_CLASS = "overflow-y-auto";
|
|
9666
10065
|
// Medidas del estado vacío. Se toman las del ITEM de v3 (`@heroui/styles`,
|
|
9667
10066
|
// `dist/components/list-box-item.css:6`: `min-h-9 … px-2 py-1.5`) y no las del slot `emptyContent`
|
|
9668
10067
|
// de v2, y es una decisión medida: el literal de v2 era
|
|
@@ -9934,7 +10333,9 @@ size = "md", variant = "bordered", radius = "md",
|
|
|
9934
10333
|
//
|
|
9935
10334
|
// Por eso este componente sigue sin `SelectTranslations`: la corrección consiste precisamente en
|
|
9936
10335
|
// que la librería no aporte NINGÚN texto propio, no en traducir el que había.
|
|
9937
|
-
placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
|
|
10336
|
+
placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
|
|
10337
|
+
// Alias nativo de `isRequired` (ver `SelectProps.required`).
|
|
10338
|
+
required = false, isDisabled = false,
|
|
9938
10339
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
9939
10340
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
9940
10341
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -9944,14 +10345,35 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
|
|
|
9944
10345
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
9945
10346
|
fullWidth = true, isLoading = false,
|
|
9946
10347
|
// Aceptada y descartada: ver `SelectProps.isVirtualized`.
|
|
9947
|
-
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps,
|
|
10348
|
+
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, onChange,
|
|
10349
|
+
// Aceptada y descartada: ver `SelectProps.labelPlacement`.
|
|
10350
|
+
labelPlacement: _labelPlacement, scrollShadowProps, items, isSearchable = false, searchPlaceholder, isClearable = false, onClear, closeOnSelect, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
|
|
9948
10351
|
const generatedId = $8WNJc$react.useId();
|
|
9949
10352
|
const selectId = id || generatedId;
|
|
9950
|
-
const requiredField = isRequired;
|
|
10353
|
+
const requiredField = isRequired || required;
|
|
9951
10354
|
const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
|
|
10355
|
+
// Raíz `Autocomplete` (buscador y/o ✕) o `Select` de siempre: ver el bloque «EL BUSCADOR Y LA ✕».
|
|
10356
|
+
const usesAutocompleteShell = isSearchable || isClearable;
|
|
10357
|
+
// Filtro de react-aria sin mayúsculas ni acentos. El hook va fuera de la condición (reglas de
|
|
10358
|
+
// hooks); sin `isSearchable` no se usa.
|
|
10359
|
+
const { contains } = react.useFilter({ sensitivity: "base" });
|
|
10360
|
+
// Texto del buscador, en la fachada para saber qué mensaje poner en la lista vacía y para
|
|
10361
|
+
// vaciarlo al cerrar (abrir de nuevo = lista completa, que es lo que hacía el buscador manual
|
|
10362
|
+
// de `timezone-search-select` con su `onOpenChange`).
|
|
10363
|
+
const [searchText, setSearchText] = $8WNJc$react.useState("");
|
|
9952
10364
|
// 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
|
-
|
|
10365
|
+
// que es el default de la fachada (el de v2 era el literal inglés "No items."). Con el buscador
|
|
10366
|
+
// escrito, la lista vacía significa «sin resultados», no «sin opciones».
|
|
10367
|
+
const emptyContent = isSearchable && searchText.trim() !== ""
|
|
10368
|
+
? t.noResults
|
|
10369
|
+
: (listboxProps?.emptyContent ?? t.emptyContent);
|
|
10370
|
+
const resolvedSearchPlaceholder = searchPlaceholder ?? t.searchPlaceholder;
|
|
10371
|
+
const handleOpenChange = (open) => {
|
|
10372
|
+
if (!open) {
|
|
10373
|
+
setSearchText("");
|
|
10374
|
+
}
|
|
10375
|
+
onOpenChange?.(open);
|
|
10376
|
+
};
|
|
9955
10377
|
// Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
|
|
9956
10378
|
// cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
|
|
9957
10379
|
// no se lee (el hook va fuera de la condición por las reglas de hooks).
|
|
@@ -9963,55 +10385,97 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
9963
10385
|
// En v2 el estado inválido MANDABA también con el campo enfocado
|
|
9964
10386
|
// (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
|
|
9965
10387
|
// el campo está en error: se queda el `danger`.
|
|
9966
|
-
isInvalid
|
|
10388
|
+
isInvalid
|
|
10389
|
+
? INVALID_VARIANT_CLASS[variant]
|
|
10390
|
+
: usesAutocompleteShell
|
|
10391
|
+
? FOCUS_BORDER_GROUP_CLASS
|
|
10392
|
+
: FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1, usesAutocompleteShell ? INDICATOR_BUTTON_OVERLAY_CLASS : "",
|
|
9967
10393
|
// Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
|
|
9968
10394
|
// sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
|
|
9969
10395
|
// responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
|
|
9970
10396
|
// con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
|
|
9971
10397
|
// cualquier utilidad.
|
|
9972
10398
|
"w-full", classNames?.trigger);
|
|
10399
|
+
// `id`/`textValue` desde la `key` de React: ver `utils/collection-items.ts` — sin esto un select
|
|
10400
|
+
// múltiple no pinta lo elegido y uno controlado arranca vacío. `withV2ItemContent` traduce
|
|
10401
|
+
// `description`/`startContent`/`endContent`/`classNames` del item y `title`/`showDivider` de la
|
|
10402
|
+
// sección a la anatomía de v3 (ver `_internal/v2-collection.tsx` para el orden), y
|
|
10403
|
+
// `withItemIndicator` monta el checkmark del seleccionado, que en v3 es un subcomponente aparte
|
|
10404
|
+
// (ver `_internal/with-item-indicator.tsx`, y ahí por qué va en este orden).
|
|
10405
|
+
const decorate = (nodes) => withItemIndicator(withV2ItemContent(withCollectionItemIds(nodes)));
|
|
10406
|
+
// Colección dinámica (`items` + función, la forma de `Profile.tsx`). La función se envuelve para
|
|
10407
|
+
// decorar lo que devuelva, y el paso de `withCollectionItemIds` importa aquí MÁS que en la forma
|
|
10408
|
+
// estática: react-aria toma la clave de una colección dinámica de `rendered.props.id ??
|
|
10409
|
+
// item.key ?? item.id`, nunca de la `key` de React. Sin reponer el `id` desde la `key`, las
|
|
10410
|
+
// opciones de `Profile.tsx` (objetos `{ value: "es", id: 1 }` con `<SelectItem key={value}>`)
|
|
10411
|
+
// se llamarían `1`/`2` y ni `defaultSelectedKeys={["es"]}` casaría ni `onSelectionChange`
|
|
10412
|
+
// devolvería la clave de la app.
|
|
10413
|
+
const collection = typeof children === "function"
|
|
10414
|
+
? (item) => decorate(children(item))
|
|
10415
|
+
: decorate(children);
|
|
10416
|
+
const listBox = (jsxRuntime.jsx(react.ListBox
|
|
10417
|
+
// v2 `items`: react-aria recorre la colección y llama a la función de `children`.
|
|
10418
|
+
, {
|
|
10419
|
+
// v2 `items`: react-aria recorre la colección y llama a la función de `children`.
|
|
10420
|
+
items: items,
|
|
10421
|
+
// v2: `classNames.listboxWrapper` y `scrollShadowProps.className` iban al mismo slot
|
|
10422
|
+
// (`cn(classNames.listboxWrapper, scrollShadowProps.className)`, `chunk-YFRCT47S.mjs:
|
|
10423
|
+
// 321-331`), en ese orden.
|
|
10424
|
+
className: classNames?.listboxWrapper || scrollShadowProps?.className
|
|
10425
|
+
? react.cn(LISTBOX_SCROLL_CLASS, classNames?.listboxWrapper, scrollShadowProps?.className)
|
|
10426
|
+
: undefined, style: scrollShadowProps?.style,
|
|
10427
|
+
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
10428
|
+
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
10429
|
+
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve el
|
|
10430
|
+
// resultado en un `<div role="option" style="display:contents">`
|
|
10431
|
+
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja que se
|
|
10432
|
+
// ve es la nuestra.
|
|
10433
|
+
renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: collection }));
|
|
10434
|
+
// Las piezas del disparador son las mismas en las dos raíces; solo cambia de qué compound salen
|
|
10435
|
+
// (ver el bloque «EL BUSCADOR Y LA ✕»). `Autocomplete.Value` y `Select.Value` son el mismo
|
|
10436
|
+
// `SelectValue` de react-aria con otra clase base, así que reciben las mismas props.
|
|
10437
|
+
const ValuePart = usesAutocompleteShell
|
|
10438
|
+
? react.Autocomplete.Value
|
|
10439
|
+
: react.Select.Value;
|
|
10440
|
+
const triggerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(ValuePart, { className: react.cn(SIZE_TEXT_CLASS$1[size], isMultiline
|
|
10441
|
+
? MULTILINE_VALUE_ROOM_CLASS
|
|
10442
|
+
: isClearable
|
|
10443
|
+
? VALUE_ROOM_CLEARABLE_CLASS
|
|
10444
|
+
: VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
|
|
10445
|
+
? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
|
|
10446
|
+
// con selección, la función con los items en la forma de v2. El nodo de la
|
|
10447
|
+
// colección de v3 trae los mismos campos que el de v2, así que el mapeo es
|
|
10448
|
+
// 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
|
|
10449
|
+
({ state, isPlaceholder, defaultChildren }) => {
|
|
10450
|
+
if (isPlaceholder) {
|
|
10451
|
+
return defaultChildren;
|
|
10452
|
+
}
|
|
10453
|
+
const items = state.selectedItems.map((item) => ({
|
|
10454
|
+
key: item.key,
|
|
10455
|
+
data: item.value,
|
|
10456
|
+
type: item.type,
|
|
10457
|
+
props: item.props,
|
|
10458
|
+
textValue: item.textValue,
|
|
10459
|
+
rendered: item.rendered,
|
|
10460
|
+
"aria-label": item["aria-label"],
|
|
10461
|
+
}));
|
|
10462
|
+
return renderValue(items);
|
|
10463
|
+
}
|
|
10464
|
+
: undefined }), isClearable && (jsxRuntime.jsx(react.Autocomplete.ClearButton, { "aria-label": t.clearButtonAriaLabel, className: CLEAR_BUTTON_CLASS$1 })), usesAutocompleteShell ? (
|
|
10465
|
+
/* En el `Autocomplete` el chevron es el BOTÓN que recibe el foco (ver el bloque
|
|
10466
|
+
«EL BUSCADOR Y LA ✕»), así que el spinner va DENTRO de él en vez de sustituirlo:
|
|
10467
|
+
sin el botón el campo no se alcanzaría con el teclado. `Autocomplete.Indicator` clona
|
|
10468
|
+
su hijo con la clase del indicador (`absolute inset-y-0 end-2 my-auto`), así que el
|
|
10469
|
+
spinner cae en el sitio del chevron sin clase propia. */
|
|
10470
|
+
jsxRuntime.jsx(react.Autocomplete.Indicator, { className: "end-3", children: isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current" })) : undefined })) : isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
|
|
10471
|
+
/* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
|
|
10472
|
+
`selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
|
|
10473
|
+
acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
|
|
10474
|
+
de más. */
|
|
10475
|
+
jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }));
|
|
9973
10476
|
const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
|
|
9974
10477
|
? LABEL_OFFSET_HELPER_CLASS[size]
|
|
9975
|
-
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.
|
|
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
|
|
10001
|
-
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
10002
|
-
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
10003
|
-
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
|
|
10004
|
-
// el resultado en un `<div role="option" style="display:contents">`
|
|
10005
|
-
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
|
|
10006
|
-
// que se ve es la nuestra.
|
|
10007
|
-
, {
|
|
10008
|
-
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
10009
|
-
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
10010
|
-
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
|
|
10011
|
-
// el resultado en un `<div role="option" style="display:contents">`
|
|
10012
|
-
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
|
|
10013
|
-
// que se ve es la nuestra.
|
|
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 }))] }));
|
|
10478
|
+
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Trigger, { className: triggerClassName, children: triggerContent })) : (jsxRuntime.jsx(react.Select.Trigger, { className: triggerClassName, children: triggerContent })), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Popover, { className: classNames?.popoverContent, children: isSearchable ? (jsxRuntime.jsxs(react.Autocomplete.Filter, { filter: contains, inputValue: searchText, onInputChange: setSearchText, children: [jsxRuntime.jsx(react.SearchField, { autoFocus: true, "aria-label": resolvedSearchPlaceholder, fullWidth: true, children: jsxRuntime.jsxs(react.SearchField.Group, { className: SEARCH_GROUP_CLASS, children: [jsxRuntime.jsx(react.SearchField.SearchIcon, {}), jsxRuntime.jsx(react.SearchField.Input, { placeholder: resolvedSearchPlaceholder })] }) }), listBox] })) : (listBox) })) : (jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: listBox })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
|
|
10015
10479
|
const commonProps = {
|
|
10016
10480
|
id: selectId,
|
|
10017
10481
|
name,
|
|
@@ -10037,7 +10501,14 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10037
10501
|
isDisabled,
|
|
10038
10502
|
isOpen,
|
|
10039
10503
|
defaultOpen,
|
|
10040
|
-
onOpenChange,
|
|
10504
|
+
onOpenChange: handleOpenChange,
|
|
10505
|
+
// v2 `closeOnSelect` → `shouldCloseOnSelect` de react-stately, que ya trae el mismo default
|
|
10506
|
+
// por modo (`useSelectState.mjs:23`: cierra en `single`, se queda abierto en `multiple`).
|
|
10507
|
+
// `undefined` deja ese default intacto.
|
|
10508
|
+
...(closeOnSelect === undefined
|
|
10509
|
+
? {}
|
|
10510
|
+
: { shouldCloseOnSelect: closeOnSelect }),
|
|
10511
|
+
onClear,
|
|
10041
10512
|
fullWidth,
|
|
10042
10513
|
// `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
|
|
10043
10514
|
// de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
|
|
@@ -10050,7 +10521,13 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10050
10521
|
className: react.cn(FIELD_STACK_CLASS$1[size],
|
|
10051
10522
|
// `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
|
|
10052
10523
|
// (ver `ROOT_MIN_WIDTH_CLASS`).
|
|
10053
|
-
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
|
|
10524
|
+
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
|
|
10525
|
+
// Grupo con nombre para el borde de foco del disparador `Group` (ver
|
|
10526
|
+
// `FOCUS_BORDER_GROUP_CLASS`). Sin la raíz `Autocomplete` no hace falta.
|
|
10527
|
+
usesAutocompleteShell ? "group/aura-select" : "",
|
|
10528
|
+
// v2 `classNames.base`: la raíz, igual que `className`, y por eso delante de él (en v2
|
|
10529
|
+
// los dos acababan en `slots.base({ class: cn(classNames?.base, className) })`).
|
|
10530
|
+
classNames?.base, className),
|
|
10054
10531
|
};
|
|
10055
10532
|
if (selectionMode === "multiple") {
|
|
10056
10533
|
// `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
|
|
@@ -10058,7 +10535,12 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10058
10535
|
// la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
|
|
10059
10536
|
// no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
|
|
10060
10537
|
const ownsValue = disallowEmptySelection && selectedKeys === undefined;
|
|
10061
|
-
|
|
10538
|
+
const multipleProps = {
|
|
10539
|
+
...commonProps,
|
|
10540
|
+
selectionMode: "multiple",
|
|
10541
|
+
value: ownsValue ? ownMultiple : toArray(selectedKeys),
|
|
10542
|
+
defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys),
|
|
10543
|
+
onChange: (keys) => {
|
|
10062
10544
|
if (disallowEmptySelection && keys.length === 0) {
|
|
10063
10545
|
return;
|
|
10064
10546
|
}
|
|
@@ -10066,7 +10548,10 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10066
10548
|
setOwnMultiple(keys);
|
|
10067
10549
|
}
|
|
10068
10550
|
onSelectionChange?.(new Set(keys));
|
|
10069
|
-
|
|
10551
|
+
onChange?.({ target: { name, value: keys.join(",") } });
|
|
10552
|
+
},
|
|
10553
|
+
};
|
|
10554
|
+
return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...multipleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...multipleProps, children: inner }));
|
|
10070
10555
|
}
|
|
10071
10556
|
// `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
|
|
10072
10557
|
// cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
|
|
@@ -10081,7 +10566,17 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10081
10566
|
: defaultSelectedKeys === "all"
|
|
10082
10567
|
? null
|
|
10083
10568
|
: (Array.from(defaultSelectedKeys)[0] ?? null);
|
|
10084
|
-
|
|
10569
|
+
const singleProps = {
|
|
10570
|
+
...commonProps,
|
|
10571
|
+
value: singleValue,
|
|
10572
|
+
defaultValue: singleDefault,
|
|
10573
|
+
onChange: (key) => {
|
|
10574
|
+
onSelectionChange?.(key == null ? new Set() : new Set([key]));
|
|
10575
|
+
// v2 llamaba a `onChange` después de `onSelectionChange` (ver `SelectChangeEvent`).
|
|
10576
|
+
onChange?.({ target: { name, value: key == null ? "" : String(key) } });
|
|
10577
|
+
},
|
|
10578
|
+
};
|
|
10579
|
+
return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...singleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...singleProps, children: inner }));
|
|
10085
10580
|
};
|
|
10086
10581
|
|
|
10087
10582
|
const defaultTranslations$4 = {
|
|
@@ -10576,6 +11071,46 @@ function transformBody(body) {
|
|
|
10576
11071
|
return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
|
|
10577
11072
|
}
|
|
10578
11073
|
|
|
11074
|
+
/**
|
|
11075
|
+
* TECH-2459 (beweossmbs, 24-sep: «Sessions history» de finanzas) — la celda del ESTADO VACÍO.
|
|
11076
|
+
*
|
|
11077
|
+
* En v3 `renderEmptyState` es un render prop de react-aria que pinta lo que devuelva dentro de
|
|
11078
|
+
* `<tbody data-empty="true"><tr role="row"><td role="rowheader" colspan>`
|
|
11079
|
+
* (`react-aria-components/dist/private/Table.mjs:886-915`), y ese `<td>` NO lleva `.table__cell`:
|
|
11080
|
+
* se queda sin el `bg-surface`, el padding ni el color de las celdas. Medido en la story
|
|
11081
|
+
* `EmptyState` con la cadena pelada que devuelven 12 de las 13 tablas de beweossmbs: 20 px de
|
|
11082
|
+
* alto, `padding: 0`, `text-align: start` (el texto pegado arriba a la izquierda) y fondo
|
|
11083
|
+
* transparente, o sea el gris de la raíz `table-root--primary` asomando.
|
|
11084
|
+
*
|
|
11085
|
+
* No es una regresión de la librería: ni la fachada ni `Table.Content` pintaron nunca ese estado
|
|
11086
|
+
* (`git log -S renderEmptyState|data-empty|emptyContent -- src/components/table` → 0 commits); lo
|
|
11087
|
+
* que se había arreglado era backoffice, en su propia app (TECH-2298, `49f2b35`: cada
|
|
11088
|
+
* `renderEmptyState` devuelve `<div className="flex h-40 items-center justify-center
|
|
11089
|
+
* text-default-500">`). Aquí se resuelve para todas las apps, en la única pieza por la que pasan
|
|
11090
|
+
* todas las tablas.
|
|
11091
|
+
*
|
|
11092
|
+
* Qué se le da a la celda, con tokens de v3 (no se busca la métrica de v2):
|
|
11093
|
+
* - `h-40`: aire. En una celda de tabla `height` actúa de alto MÍNIMO, así que un nodo propio más
|
|
11094
|
+
* alto sigue mandando, y uno de `h-40` como el de backoffice mide lo mismo (no se suma: por
|
|
11095
|
+
* eso es alto y no padding vertical).
|
|
11096
|
+
* - `px-4 text-center align-middle`: centrado en los dos ejes; `px-4` es el lateral de `.table__cell`.
|
|
11097
|
+
* - `text-sm text-default-500`: el cuerpo de las celdas de v3 (`text-sm`) en el gris atenuado que
|
|
11098
|
+
* ya usan las apps para este texto (backoffice), legible sobre el blanco (contraste AA).
|
|
11099
|
+
* - `bg-surface`: el fondo del cuerpo (`.table__cell` de `table.css`), no el gris de la raíz. Las
|
|
11100
|
+
* esquinas redondeadas ya las pone `table.css` (primera/última celda de la primera/última fila).
|
|
11101
|
+
*
|
|
11102
|
+
* Selector por `data-empty`, que react-aria solo escribe con el cuerpo vacío. Cadenas literales
|
|
11103
|
+
* completas: Tailwind solo emite lo que encuentra escrito entero.
|
|
11104
|
+
*/
|
|
11105
|
+
const EMPTY_STATE_CLASS = [
|
|
11106
|
+
"[&>tbody[data-empty]>tr>td]:h-40",
|
|
11107
|
+
"[&>tbody[data-empty]>tr>td]:px-4",
|
|
11108
|
+
"[&>tbody[data-empty]>tr>td]:text-center",
|
|
11109
|
+
"[&>tbody[data-empty]>tr>td]:align-middle",
|
|
11110
|
+
"[&>tbody[data-empty]>tr>td]:text-sm",
|
|
11111
|
+
"[&>tbody[data-empty]>tr>td]:text-default-500",
|
|
11112
|
+
"[&>tbody[data-empty]>tr>td]:bg-surface",
|
|
11113
|
+
].join(" ");
|
|
10579
11114
|
/** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
|
|
10580
11115
|
function defaultShowCheckboxes(props) {
|
|
10581
11116
|
const behavior = props.selectionBehavior ?? "toggle";
|
|
@@ -10618,7 +11153,8 @@ function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...pr
|
|
|
10618
11153
|
return child;
|
|
10619
11154
|
});
|
|
10620
11155
|
// Esparcido y no `{content}`: ver `spread` (un array como hijo único exigiría claves).
|
|
10621
|
-
|
|
11156
|
+
// La clase del estado vacío va delante: la de la app, detrás, gana si pisa el mismo grupo.
|
|
11157
|
+
return $8WNJc$react.createElement(react.Table.Content, { ...props, className: react.cn(EMPTY_STATE_CLASS, props.className) }, ...spread(content));
|
|
10622
11158
|
}
|
|
10623
11159
|
TableContent.displayName = "Aurora.Table.Content";
|
|
10624
11160
|
|
|
@@ -10649,6 +11185,24 @@ function isPart$2(node, parts) {
|
|
|
10649
11185
|
function withSticky(isHeaderSticky, root) {
|
|
10650
11186
|
return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
|
|
10651
11187
|
}
|
|
11188
|
+
/**
|
|
11189
|
+
* TECH-2459 (beweossmbs, 24-sep) — barra de scroll NATIVA en el contenedor que inserta la fachada.
|
|
11190
|
+
*
|
|
11191
|
+
* `table.css:20` le aplica a `.table__scroll-container` la utilidad `scrollbar` de v3, que con los
|
|
11192
|
+
* tokens de `@heroui/styles` resuelve a `scrollbar-width: thin` y un pulgar al 15 % de opacidad
|
|
11193
|
+
* sobre carril transparente (medido en la story `Overflow`:
|
|
11194
|
+
* `oklch(0.2103 0.0059 285.89 / 0.15) rgba(0,0,0,0)`). Con barras clásicas (Windows, o macOS con
|
|
11195
|
+
* «mostrar barras: siempre») casi no se ve, y el usuario concluye que la tabla no tiene scroll
|
|
11196
|
+
* horizontal aunque lo tenga: es affordance, no estética. `scrollbar-default` (utilidad de la
|
|
11197
|
+
* propia v3, `utilities/index.css:71`) devuelve la barra del sistema; al ser capa `utilities`
|
|
11198
|
+
* gana a la regla de `components` sin `!important`.
|
|
11199
|
+
*
|
|
11200
|
+
* Solo en el contenedor que monta la fachada. Si la app monta su propio `Table.ScrollContainer`
|
|
11201
|
+
* (o un `ResizableContainer`), la fachada no toca nada y manda lo que la app escriba; y una clase
|
|
11202
|
+
* de la app sobre el root dirigida al contenedor (`[&_.table__scroll-container]:scrollbar-thin`)
|
|
11203
|
+
* gana por especificidad.
|
|
11204
|
+
*/
|
|
11205
|
+
const SCROLL_CONTAINER_CLASS = "scrollbar-default";
|
|
10652
11206
|
const TableFacade = ({ children, isHeaderSticky, ...props }) => {
|
|
10653
11207
|
const nodes = $8WNJc$react.Children.toArray(children);
|
|
10654
11208
|
// Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
|
|
@@ -10657,7 +11211,7 @@ const TableFacade = ({ children, isHeaderSticky, ...props }) => {
|
|
|
10657
11211
|
}
|
|
10658
11212
|
const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
|
|
10659
11213
|
const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
|
|
10660
|
-
return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
|
|
11214
|
+
return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { className: SCROLL_CONTAINER_CLASS, children: scrollable }), outsideScroll] }));
|
|
10661
11215
|
};
|
|
10662
11216
|
TableFacade.displayName = "Aurora.Table";
|
|
10663
11217
|
/**
|
|
@@ -11122,6 +11676,28 @@ const VARIANT_CLASS = {
|
|
|
11122
11676
|
// placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
|
|
11123
11677
|
const INVALID_LABEL_CLASS = "text-danger";
|
|
11124
11678
|
const INVALID_TEXT_CLASS = "text-danger";
|
|
11679
|
+
// ── `isClearable` / `onClear` / `classNames.clearButton` (API v2, repuesta) ──────────────────
|
|
11680
|
+
//
|
|
11681
|
+
// El `Textarea` de v2 ES el `Input` con `isMultiline` (mismo `useInput`), así que heredaba su botón
|
|
11682
|
+
// de limpiar: `isClearable = !!onClear || originalProps.isClearable` y `handleClear` pone el valor
|
|
11683
|
+
// a "" (lo que dispara `onValueChange("")`), llama a `onClear` y devuelve el foco
|
|
11684
|
+
// (`@heroui/input@2.4.33`, `dist/chunk-KQW2TNLT.mjs`); Escape con valor también limpia. Uso real:
|
|
11685
|
+
// `campaigns/**/AudienceReachStep.tsx` en beweossmbs (`isClearable` + `onClear` +
|
|
11686
|
+
// `classNames.clearButton`), el único de los 33 `Textarea` de la app que lo pide.
|
|
11687
|
+
//
|
|
11688
|
+
// Posición, literal del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/input.js`):
|
|
11689
|
+
// · `isMultiline.clearButton` → "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10": la X va
|
|
11690
|
+
// ARRIBA a la derecha, no centrada como en el campo de una línea (aquí `end-2` = right/left
|
|
11691
|
+
// según la dirección, lo mismo que el par `right-2 rtl:left-2`);
|
|
11692
|
+
// · `isClearable.input` → "pe-6": el texto deja sitio a la X aunque todavía no se vea,
|
|
11693
|
+
// para que no salte al aparecer.
|
|
11694
|
+
// Lo relativo en v2 era el `inputWrapper`, que aquí no existe (el `<textarea>` es la caja), así que
|
|
11695
|
+
// se añade un envoltorio `relative` SOLO cuando el campo es limpiable: sin la prop el DOM no cambia.
|
|
11696
|
+
// El glifo es el `CloseButton` de v3, el mismo que ya usa la fachada `Input` (§9.1: verse como v3),
|
|
11697
|
+
// y `classNames.clearButton` va el último para que las clases del consumidor ganen.
|
|
11698
|
+
const CLEAR_WRAPPER_CLASS = "relative w-full";
|
|
11699
|
+
const CLEAR_BUTTON_CLASS = "absolute top-2 end-2 z-10 h-4 w-4 min-h-0 p-0.5";
|
|
11700
|
+
const CLEARABLE_FIELD_CLASS = "pe-6";
|
|
11125
11701
|
// ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
|
|
11126
11702
|
//
|
|
11127
11703
|
// Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
|
|
@@ -11164,7 +11740,10 @@ minRows, rows,
|
|
|
11164
11740
|
// llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
|
|
11165
11741
|
maxRows = DEFAULT_MAX_ROWS,
|
|
11166
11742
|
// Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
|
|
11167
|
-
disableAutosize = false, placeholder,
|
|
11743
|
+
disableAutosize = false, placeholder, isClearable: isClearableProp = false, onClear,
|
|
11744
|
+
// Sin efecto (ver `TextareaProps.labelPlacement`): fuera del spread para que no llegue
|
|
11745
|
+
// al `<textarea>` como atributo desconocido.
|
|
11746
|
+
labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
11168
11747
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
11169
11748
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
11170
11749
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -11172,7 +11751,7 @@ disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isD
|
|
|
11172
11751
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
11173
11752
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
11174
11753
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
11175
|
-
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
|
|
11754
|
+
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, onKeyDown, ...rest }, ref) => {
|
|
11176
11755
|
const generatedId = $8WNJc$react.useId();
|
|
11177
11756
|
const textareaId = id || generatedId;
|
|
11178
11757
|
const requestedRows = resolveRows(minRows, rows);
|
|
@@ -11209,6 +11788,46 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11209
11788
|
minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
|
|
11210
11789
|
...style,
|
|
11211
11790
|
};
|
|
11791
|
+
// v2: `isClearable = !!onClear || originalProps.isClearable`.
|
|
11792
|
+
const isClearable = isClearableProp || Boolean(onClear);
|
|
11793
|
+
// Limpiar en modo NO controlado tiene que poder vaciar el valor, y el estado interno del
|
|
11794
|
+
// `TextField` no se puede escribir desde fuera: en ese caso (y solo en ese) la fachada lleva
|
|
11795
|
+
// el valor en su propio estado, igual que `Input.tsx`. Sin `isClearable` nada cambia: el
|
|
11796
|
+
// valor no controlado sigue siendo del `TextField` (y su `reset`, de `useFormReset`).
|
|
11797
|
+
const isControlled = value !== undefined;
|
|
11798
|
+
const ownsValue = isClearable && !isControlled;
|
|
11799
|
+
const [ownValue, setOwnValue] = $8WNJc$react.useState(defaultValue ?? "");
|
|
11800
|
+
const currentValue = isControlled ? value : ownsValue ? ownValue : "";
|
|
11801
|
+
const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
|
|
11802
|
+
// Orden de v2 (`handleClear`): el valor a "" —que en v2 disparaba `onValueChange`—, luego
|
|
11803
|
+
// `onClear`. El `onChange` recibe un evento de valor vacío, como en `Input.tsx`, para que un
|
|
11804
|
+
// consumidor que solo escucha `onChange` (p. ej. `field.onChange` de react-hook-form) se entere.
|
|
11805
|
+
const clearValue = () => {
|
|
11806
|
+
if (ownsValue) {
|
|
11807
|
+
setOwnValue("");
|
|
11808
|
+
}
|
|
11809
|
+
onValueChange?.("");
|
|
11810
|
+
if (onChange) {
|
|
11811
|
+
const emptyEvent = {
|
|
11812
|
+
target: { value: "" },
|
|
11813
|
+
currentTarget: { value: "" },
|
|
11814
|
+
};
|
|
11815
|
+
onChange(emptyEvent);
|
|
11816
|
+
}
|
|
11817
|
+
onClear?.();
|
|
11818
|
+
};
|
|
11819
|
+
const handleClear = () => {
|
|
11820
|
+
clearValue();
|
|
11821
|
+
innerRef.current?.focus();
|
|
11822
|
+
};
|
|
11823
|
+
// Escape limpia, igual que v2 (con valor, limpiable y no de solo lectura). El `onKeyDown` del
|
|
11824
|
+
// consumidor se llama primero.
|
|
11825
|
+
const handleKeyDown = (event) => {
|
|
11826
|
+
onKeyDown?.(event);
|
|
11827
|
+
if (event.key === "Escape" && showClear) {
|
|
11828
|
+
clearValue();
|
|
11829
|
+
}
|
|
11830
|
+
};
|
|
11212
11831
|
// El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
|
|
11213
11832
|
// `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
|
|
11214
11833
|
// el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
|
|
@@ -11217,6 +11836,10 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11217
11836
|
onChange?.(event);
|
|
11218
11837
|
onValueChange?.(event.target.value);
|
|
11219
11838
|
};
|
|
11839
|
+
const field = (jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, onKeyDown: handleKeyDown, 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,
|
|
11840
|
+
// Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
|
|
11841
|
+
// y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
|
|
11842
|
+
"w-full", isClearable ? CLEARABLE_FIELD_CLASS : "", classNames?.input ?? classNames?.inputWrapper) }));
|
|
11220
11843
|
return (
|
|
11221
11844
|
// ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
|
|
11222
11845
|
//
|
|
@@ -11231,13 +11854,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11231
11854
|
// pero el estado interno del `TextField` seguía en "".
|
|
11232
11855
|
// Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
|
|
11233
11856
|
// es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
|
|
11234
|
-
jsxRuntime.jsxs(react.TextField, { id: textareaId, value:
|
|
11857
|
+
jsxRuntime.jsxs(react.TextField, { id: textareaId, value: ownsValue ? ownValue : value, defaultValue: ownsValue ? undefined : defaultValue,
|
|
11858
|
+
// Solo cuando la fachada es dueña del valor (limpiable y no controlado).
|
|
11859
|
+
onChange: ownsValue ? setOwnValue : undefined, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
|
|
11235
11860
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
11236
11861
|
// v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
|
|
11237
|
-
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsxRuntime.
|
|
11238
|
-
// Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
|
|
11239
|
-
// y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
|
|
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 }))] }));
|
|
11862
|
+
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), isClearable ? (jsxRuntime.jsxs("div", { className: CLEAR_WRAPPER_CLASS, children: [field, showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn(CLEAR_BUTTON_CLASS, classNames?.clearButton), onPress: handleClear }))] })) : (field), 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 }))] }));
|
|
11241
11863
|
});
|
|
11242
11864
|
Textarea.displayName = "Textarea";
|
|
11243
11865
|
|
|
@@ -12990,8 +13612,8 @@ const DrawerFiltersFooter = ({ onClearFilters, onCancel, onApplyFilters, transla
|
|
|
12990
13612
|
/**
|
|
12991
13613
|
* Header of the DrawerFilters with title and description.
|
|
12992
13614
|
*/
|
|
12993
|
-
const DrawerFiltersHeader = ({ title, description, }) => {
|
|
12994
|
-
return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
|
|
13615
|
+
const DrawerFiltersHeader = ({ titleId, title, description, }) => {
|
|
13616
|
+
return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { id: titleId, className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
|
|
12995
13617
|
};
|
|
12996
13618
|
|
|
12997
13619
|
const NO_YEAR_CLASS = "[&_[data-type='year']]:hidden [&_[data-type='literal']:has(+[data-type='year'])]:hidden";
|
|
@@ -13020,7 +13642,7 @@ function readConfirmedMonthDay(container, monthSelector = '[data-type="month"]',
|
|
|
13020
13642
|
return { month, day };
|
|
13021
13643
|
return null;
|
|
13022
13644
|
}
|
|
13023
|
-
const DayMonthPicker = ({ value, onChange, translations, }) => {
|
|
13645
|
+
const DayMonthPicker = ({ value, onChange, translations, ariaLabelledBy, }) => {
|
|
13024
13646
|
const t = {
|
|
13025
13647
|
dayMonthAriaLabel: "Seleccionar mes y día",
|
|
13026
13648
|
dayMonthRangeAriaLabel: "Seleccionar rango de mes y día",
|
|
@@ -13096,7 +13718,7 @@ const DayMonthPicker = ({ value, onChange, translations, }) => {
|
|
|
13096
13718
|
});
|
|
13097
13719
|
}
|
|
13098
13720
|
};
|
|
13099
|
-
return (jsxRuntime.jsxs("div", { className: "w-full flex flex-wrap xs:gap-5 justify-between items-center", children: [jsxRuntime.jsxs(react.RadioGroup, { orientation: "horizontal", value: mode, onChange: handleModeChange, children: [jsxRuntime.jsx(react.Radio, { value: "single", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthSingleOption] }) }), jsxRuntime.jsx(react.Radio, { value: "range", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthRangeOption] }) })] }), mode === "single" ? (jsxRuntime.jsx("div", { ref: singleRef, children: jsxRuntime.jsx(DatePicker, { value: pickerValue, onChange: handleSingleChange, onFocusChange: handleSingleFocusChange, "aria-label": t.dayMonthAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-40 ${NO_YEAR_CLASS}` }, "dmp") })) : (jsxRuntime.jsx("div", { ref: rangeRef, children: jsxRuntime.jsx(DateRangePicker, { value: rangeValue, onChange: handleRangeChange, onFocusChange: handleRangeFocusChange, "aria-label": t.dayMonthRangeAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-xs ${NO_YEAR_CLASS}` }, "dmpr") }))] }));
|
|
13721
|
+
return (jsxRuntime.jsxs("div", { className: "w-full flex flex-wrap xs:gap-5 justify-between items-center", children: [jsxRuntime.jsxs(react.RadioGroup, { orientation: "horizontal", value: mode, onChange: handleModeChange, "aria-labelledby": ariaLabelledBy, children: [jsxRuntime.jsx(react.Radio, { value: "single", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthSingleOption] }) }), jsxRuntime.jsx(react.Radio, { value: "range", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthRangeOption] }) })] }), mode === "single" ? (jsxRuntime.jsx("div", { ref: singleRef, children: jsxRuntime.jsx(DatePicker, { value: pickerValue, onChange: handleSingleChange, onFocusChange: handleSingleFocusChange, "aria-label": t.dayMonthAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-40 ${NO_YEAR_CLASS}` }, "dmp") })) : (jsxRuntime.jsx("div", { ref: rangeRef, children: jsxRuntime.jsx(DateRangePicker, { value: rangeValue, onChange: handleRangeChange, onFocusChange: handleRangeFocusChange, "aria-label": t.dayMonthRangeAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-xs ${NO_YEAR_CLASS}` }, "dmpr") }))] }));
|
|
13100
13722
|
};
|
|
13101
13723
|
|
|
13102
13724
|
/**
|
|
@@ -13232,7 +13854,8 @@ const RangeFilter = ({ title, description, value, onChange, minValue, maxValue,
|
|
|
13232
13854
|
const [min, max] = newValue;
|
|
13233
13855
|
setCurrentValue([min, max]);
|
|
13234
13856
|
onChange([min, max]);
|
|
13235
|
-
}, className: "w-full", isDisabled: false,
|
|
13857
|
+
}, className: "w-full", isDisabled: false, "aria-label": restSliderProps["aria-label"] ??
|
|
13858
|
+
(restSliderProps["aria-labelledby"] ? undefined : t.title), ...restSliderProps, children: jsxRuntime.jsxs(react.Slider.Track, { children: [jsxRuntime.jsx(react.Slider.Fill, { className: fillClassName }), stepMarks.length > 0 ? (jsxRuntime.jsx(react.Slider.Marks, { children: stepMarks.map((left) => (jsxRuntime.jsx("span", { className: SLIDER_STEP_CLASS, style: { left: `${left}%` } }, left))) })) : null, jsxRuntime.jsx(react.Slider.Thumb, { index: 0, "aria-label": t.minLabel, className: thumbClassName }), jsxRuntime.jsx(react.Slider.Thumb, { index: 1, "aria-label": t.maxLabel, className: thumbClassName })] }) }) }), jsxRuntime.jsxs("div", { className: "flex justify-between items-center text-sm", children: [jsxRuntime.jsxs("div", { className: "flex flex-col items-start", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.minLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[0]) })] }), jsxRuntime.jsxs("div", { className: "flex flex-col items-end", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.maxLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[1]) })] })] })] }));
|
|
13236
13859
|
};
|
|
13237
13860
|
|
|
13238
13861
|
/**
|
|
@@ -13271,6 +13894,13 @@ const resolveSelectPlaceholder = (translations, title) => (translations?.selectP
|
|
|
13271
13894
|
// =========================================================================
|
|
13272
13895
|
const FilterSection = ({ filter, translations, value, onChange, isExpanded = false, onToggleExpanded, }) => {
|
|
13273
13896
|
const isDisabled = filter.disabled === true;
|
|
13897
|
+
// TECH-2459 · El título visible de la sección es el NOMBRE ACCESIBLE de su control. Sin él,
|
|
13898
|
+
// react-aria (`useLabel`, vía `useSelect`/`useSlider`/`useListBox`) avisaba en dev «If you do
|
|
13899
|
+
// not provide a visible label, you must specify an aria-label or aria-labelledby» cada vez que
|
|
13900
|
+
// se desplegaba una sección (medido por beweossmbs en el drawer del catálogo con la .141), y el
|
|
13901
|
+
// lector de pantalla anunciaba solo el placeholder («Seleccionar categoría»). El control se
|
|
13902
|
+
// asocia por `aria-labelledby` a este id: sin cambio visual.
|
|
13903
|
+
const titleId = $8WNJc$react.useId();
|
|
13274
13904
|
const [internalExpanded, setInternalExpanded] = $8WNJc$react.useState(isDisabled ? false : isExpanded);
|
|
13275
13905
|
const handleToggleExpanded = () => {
|
|
13276
13906
|
if (isDisabled)
|
|
@@ -13367,17 +13997,17 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
|
|
|
13367
13997
|
const renderFilterContent = () => {
|
|
13368
13998
|
switch (filter.type) {
|
|
13369
13999
|
case "select":
|
|
13370
|
-
return (jsxRuntime.jsx(SelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
|
|
14000
|
+
return (jsxRuntime.jsx(SelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
|
|
13371
14001
|
case "multiselect":
|
|
13372
|
-
return (jsxRuntime.jsx(MultiSelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
|
|
14002
|
+
return (jsxRuntime.jsx(MultiSelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
|
|
13373
14003
|
case "range":
|
|
13374
|
-
return (jsxRuntime.jsx(RangeFilterComponent, { filter: filter, value: value, onChange: onChange }));
|
|
14004
|
+
return (jsxRuntime.jsx(RangeFilterComponent, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
|
|
13375
14005
|
case "date":
|
|
13376
|
-
return (jsxRuntime.jsx(DateFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
|
|
14006
|
+
return (jsxRuntime.jsx(DateFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
|
|
13377
14007
|
case "text":
|
|
13378
|
-
return (jsxRuntime.jsx(TextFilter, { filter: filter, value: value, onChange: onChange }));
|
|
14008
|
+
return (jsxRuntime.jsx(TextFilter, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
|
|
13379
14009
|
case "number":
|
|
13380
|
-
return (jsxRuntime.jsx(NumberFilter, { filter: filter, value: value, onChange: onChange }));
|
|
14010
|
+
return (jsxRuntime.jsx(NumberFilter, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
|
|
13381
14011
|
default:
|
|
13382
14012
|
return null;
|
|
13383
14013
|
}
|
|
@@ -13387,7 +14017,7 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
|
|
|
13387
14017
|
boxShadow: "var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
|
|
13388
14018
|
"--tw-shadow": "var(--aurora-shadow-medium)",
|
|
13389
14019
|
"--tw-shadow-colored": "var(--aurora-shadow-medium)",
|
|
13390
|
-
}, children: [jsxRuntime.jsxs("button", { type: "button", className: react.cn("flex items-center justify-between w-full text-left p-4", isDisabled && "cursor-not-allowed"), onClick: handleToggleExpanded, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [jsxRuntime.jsx(H2, { className: "text-base font-normal text-foreground", children: filter.title }), !isDisabled && hasActiveFilter() && (jsxRuntime.jsx("span", { className: "text-xs px-2 py-1 rounded-full text-black dark:text-white", style: {
|
|
14020
|
+
}, children: [jsxRuntime.jsxs("button", { type: "button", className: react.cn("flex items-center justify-between w-full text-left p-4", isDisabled && "cursor-not-allowed"), onClick: handleToggleExpanded, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [jsxRuntime.jsx(H2, { id: titleId, className: "text-base font-normal text-foreground", children: filter.title }), !isDisabled && hasActiveFilter() && (jsxRuntime.jsx("span", { className: "text-xs px-2 py-1 rounded-full text-black dark:text-white", style: {
|
|
13391
14021
|
backgroundColor: "var(--color-default)",
|
|
13392
14022
|
}, children: getActiveFilterCount() }))] }), jsxRuntime.jsx(IconComponent, { icon: internalExpanded
|
|
13393
14023
|
? "solar:alt-arrow-down-outline"
|
|
@@ -13396,10 +14026,10 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
|
|
|
13396
14026
|
// =========================================================================
|
|
13397
14027
|
// SELECT FILTER
|
|
13398
14028
|
// =========================================================================
|
|
13399
|
-
const SelectFilter = ({ filter, value, onChange, translations }) => {
|
|
14029
|
+
const SelectFilter = ({ filter, value, onChange, translations, titleId }) => {
|
|
13400
14030
|
const currentValue = value?.value || "";
|
|
13401
14031
|
const triggerPlaceholder = resolveSelectPlaceholder(translations, filter.title);
|
|
13402
|
-
return (jsxRuntime.jsx(Select, { placeholder: triggerPlaceholder, selectedKeys: new Set(currentValue ? [currentValue] : []), onSelectionChange: (keys) => {
|
|
14032
|
+
return (jsxRuntime.jsx(Select, { "aria-labelledby": titleId, placeholder: triggerPlaceholder, selectedKeys: new Set(currentValue ? [currentValue] : []), onSelectionChange: (keys) => {
|
|
13403
14033
|
const selectedKey = Array.from(keys)[0];
|
|
13404
14034
|
onChange({ value: selectedKey || "" });
|
|
13405
14035
|
}, className: "w-full", children: filter.data.map((item) => (
|
|
@@ -13413,7 +14043,8 @@ const SelectFilter = ({ filter, value, onChange, translations }) => {
|
|
|
13413
14043
|
// =========================================================================
|
|
13414
14044
|
// MULTISELECT FILTER
|
|
13415
14045
|
// =========================================================================
|
|
13416
|
-
const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
|
|
14046
|
+
const MultiSelectFilter = ({ filter, value, onChange, translations, titleId }) => {
|
|
14047
|
+
const valueId = $8WNJc$react.useId();
|
|
13417
14048
|
const triggerRef = $8WNJc$react.useRef(null);
|
|
13418
14049
|
const [triggerWidthPx, setTriggerWidthPx] = $8WNJc$react.useState(null);
|
|
13419
14050
|
const [isOpen, setIsOpen] = $8WNJc$react.useState(false);
|
|
@@ -13507,11 +14138,11 @@ const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
|
|
|
13507
14138
|
maxWidth: Math.max(triggerWidthPx, 220),
|
|
13508
14139
|
}
|
|
13509
14140
|
: undefined;
|
|
13510
|
-
return (jsxRuntime.jsxs(react.Popover, { isOpen: isOpen, onOpenChange: handleOpenChange, children: [jsxRuntime.jsx(react.PopoverTrigger, { children: jsxRuntime.jsxs("button", { ref: triggerRef, type: "button", className: react.cn(
|
|
14141
|
+
return (jsxRuntime.jsxs(react.Popover, { isOpen: isOpen, onOpenChange: handleOpenChange, children: [jsxRuntime.jsx(react.PopoverTrigger, { "aria-labelledby": titleId, "aria-describedby": valueId, children: jsxRuntime.jsxs("button", { ref: triggerRef, type: "button", "aria-labelledby": titleId, "aria-describedby": valueId, className: react.cn(
|
|
13511
14142
|
// TECH-2459 · `bg-transparent` y no `bg-background`: este disparador tiene pinta de campo y vive
|
|
13512
14143
|
// DENTRO de un drawer, o sea el escenario del relleno negro sobre tarjeta gris. En oscuro
|
|
13513
14144
|
// `--color-background` es negro puro, y el `bordered` de v2 no llevaba ninguna clase `bg-*`.
|
|
13514
|
-
"flex w-full min-h-12 items-center justify-between gap-2 rounded-[12px] border border-default-200 bg-transparent px-3 py-2.5 text-left text-sm transition-colors", "hover:border-default-300 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background", isOpen && "border-primary"), "aria-expanded": isOpen, "aria-haspopup": "dialog", children: [jsxRuntime.jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-sm font-medium leading-snug text-foreground", title: triggerSelectionTitle, children: triggerSelection.visible.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [triggerSelection.visible.map((item, index) => (jsxRuntime.jsxs($8WNJc$react.Fragment, { children: [index > 0 ? (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: ", " })) : null, jsxRuntime.jsx("span", { children: item.label })] }, item.value))), triggerSelection.overflow > 0 ? (jsxRuntime.jsxs("span", { className: "font-normal text-default-500", children: [" ", moreLabelTemplate.replace("{more}", String(triggerSelection.overflow))] })) : null] })) : (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: triggerPlaceholder })) }), jsxRuntime.jsx(IconComponent, { icon: isOpen
|
|
14145
|
+
"flex w-full min-h-12 items-center justify-between gap-2 rounded-[12px] border border-default-200 bg-transparent px-3 py-2.5 text-left text-sm transition-colors", "hover:border-default-300 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background", isOpen && "border-primary"), "aria-expanded": isOpen, "aria-haspopup": "dialog", children: [jsxRuntime.jsx("span", { id: valueId, className: "line-clamp-2 min-w-0 flex-1 text-sm font-medium leading-snug text-foreground", title: triggerSelectionTitle, children: triggerSelection.visible.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [triggerSelection.visible.map((item, index) => (jsxRuntime.jsxs($8WNJc$react.Fragment, { children: [index > 0 ? (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: ", " })) : null, jsxRuntime.jsx("span", { children: item.label })] }, item.value))), triggerSelection.overflow > 0 ? (jsxRuntime.jsxs("span", { className: "font-normal text-default-500", children: [" ", moreLabelTemplate.replace("{more}", String(triggerSelection.overflow))] })) : null] })) : (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: triggerPlaceholder })) }), jsxRuntime.jsx(IconComponent, { icon: isOpen
|
|
13515
14146
|
? "solar:alt-arrow-up-outline"
|
|
13516
14147
|
: "solar:alt-arrow-down-outline", className: "shrink-0 text-default-400", size: "sm", "aria-hidden": true })] }) }), jsxRuntime.jsx(react.PopoverContent, { placement: "bottom start", offset: 6, className: "w-full min-w-0 max-w-none box-border border border-default-200 bg-content1 p-0 shadow-medium", style: popoverWidthStyle, children: jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-0 p-2", children: [searchable && (jsxRuntime.jsx("div", { className: "border-b border-default-100 px-1 pb-2", children: jsxRuntime.jsx(Input, { type: "search", placeholder: searchPlaceholder, value: searchQuery, onValueChange: setSearchQuery, className: "w-full", size: "sm", variant: "flat", radius: "full",
|
|
13517
14148
|
// `InputClassNames` de G1-forms ya no tiene slot `base` (v3 no
|
|
@@ -13525,7 +14156,7 @@ const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
|
|
|
13525
14156
|
// =========================================================================
|
|
13526
14157
|
// RANGE FILTER
|
|
13527
14158
|
// =========================================================================
|
|
13528
|
-
const RangeFilterComponent = ({ filter, value, onChange }) => {
|
|
14159
|
+
const RangeFilterComponent = ({ filter, value, onChange, titleId }) => {
|
|
13529
14160
|
const currentValue = value || { min: filter.data.min, max: filter.data.max };
|
|
13530
14161
|
const rangeValue = [currentValue.min, currentValue.max];
|
|
13531
14162
|
const handleChange = (newValue) => {
|
|
@@ -13540,12 +14171,12 @@ const RangeFilterComponent = ({ filter, value, onChange }) => {
|
|
|
13540
14171
|
const data = Array.from({
|
|
13541
14172
|
length: Math.ceil((filter.data.max - filter.data.min) / (filter.data.step || 1)) + 1,
|
|
13542
14173
|
}, (_, i) => filter.data.min + i * (filter.data.step || 1));
|
|
13543
|
-
return (jsxRuntime.jsx(RangeFilter, { title: `Rango de ${filter.title.toLowerCase()}`, minValue: filter.data.min, maxValue: filter.data.max, step: filter.data.step || 1, data: data, value: rangeValue, onChange: handleChange, formatValue: formatter, className: "w-full", showHistogramCount: filter.data.showHistogramCount }));
|
|
14174
|
+
return (jsxRuntime.jsx(RangeFilter, { title: `Rango de ${filter.title.toLowerCase()}`, minValue: filter.data.min, maxValue: filter.data.max, step: filter.data.step || 1, data: data, value: rangeValue, onChange: handleChange, formatValue: formatter, className: "w-full", showHistogramCount: filter.data.showHistogramCount, sliderProps: { "aria-labelledby": titleId } }));
|
|
13544
14175
|
};
|
|
13545
14176
|
// =========================================================================
|
|
13546
14177
|
// DATE FILTER
|
|
13547
14178
|
// =========================================================================
|
|
13548
|
-
const DateFilter = ({ filter, value, onChange, translations }) => {
|
|
14179
|
+
const DateFilter = ({ filter, value, onChange, translations, titleId }) => {
|
|
13549
14180
|
if (filter.granularity === "dayMonth") {
|
|
13550
14181
|
const rawDayMonth = value?.type === "dayMonth" && value.dayMonth && typeof value.dayMonth !== "string"
|
|
13551
14182
|
? value.dayMonth
|
|
@@ -13564,7 +14195,7 @@ const DateFilter = ({ filter, value, onChange, translations }) => {
|
|
|
13564
14195
|
dayMonthRange: value.type === "dayMonthRange" ? rawDayMonthRange : null,
|
|
13565
14196
|
}
|
|
13566
14197
|
: null;
|
|
13567
|
-
return (jsxRuntime.jsx(DayMonthPicker, { value: dayMonthValue, onChange: (v) => onChange({ date: null, dateRange: null, ...v }), translations: translations }));
|
|
14198
|
+
return (jsxRuntime.jsx(DayMonthPicker, { value: dayMonthValue, onChange: (v) => onChange({ date: null, dateRange: null, ...v }), translations: translations, ariaLabelledBy: titleId }));
|
|
13568
14199
|
}
|
|
13569
14200
|
const handleDateSelectorChange = (dateSelectorValue) => {
|
|
13570
14201
|
onChange({
|
|
@@ -13636,23 +14267,25 @@ const DateFilter = ({ filter, value, onChange, translations }) => {
|
|
|
13636
14267
|
dateSelectorTranslations.selectDateRangeAriaLabel =
|
|
13637
14268
|
translations.selectDateRangeAriaLabel;
|
|
13638
14269
|
}
|
|
13639
|
-
return (jsxRuntime.jsx(DateSelector, { initialType: getInitialType(), initialDate: getInitialDate(), initialDateRange: getInitialDateRange(), onChange: handleDateSelectorChange, translations: dateSelectorTranslations,
|
|
14270
|
+
return (jsxRuntime.jsx(DateSelector, { initialType: getInitialType(), initialDate: getInitialDate(), initialDateRange: getInitialDateRange(), onChange: handleDateSelectorChange, translations: dateSelectorTranslations,
|
|
14271
|
+
// El grupo «Día / Rango» se nombra con el título de la sección (ver `titleId`).
|
|
14272
|
+
radioGroupProps: { "aria-labelledby": titleId }, className: "w-full" }));
|
|
13640
14273
|
};
|
|
13641
14274
|
// =========================================================================
|
|
13642
14275
|
// TEXT FILTER
|
|
13643
14276
|
// =========================================================================
|
|
13644
|
-
const TextFilter = ({ filter, value, onChange }) => {
|
|
14277
|
+
const TextFilter = ({ filter, value, onChange, titleId }) => {
|
|
13645
14278
|
const currentValue = value?.value || "";
|
|
13646
|
-
return (jsxRuntime.jsx(Input, { type: "text", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
|
|
14279
|
+
return (jsxRuntime.jsx(Input, { "aria-labelledby": titleId, type: "text", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
|
|
13647
14280
|
onChange({ value: newValue });
|
|
13648
14281
|
}, className: "w-full" }));
|
|
13649
14282
|
};
|
|
13650
14283
|
// =========================================================================
|
|
13651
14284
|
// NUMBER FILTER
|
|
13652
14285
|
// =========================================================================
|
|
13653
|
-
const NumberFilter = ({ filter, value, onChange }) => {
|
|
14286
|
+
const NumberFilter = ({ filter, value, onChange, titleId }) => {
|
|
13654
14287
|
const currentValue = value?.value?.toString() || "";
|
|
13655
|
-
return (jsxRuntime.jsx(Input, { type: "number", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
|
|
14288
|
+
return (jsxRuntime.jsx(Input, { "aria-labelledby": titleId, type: "number", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
|
|
13656
14289
|
const numValue = newValue === "" ? null : Number(newValue);
|
|
13657
14290
|
onChange({ value: numValue });
|
|
13658
14291
|
}, min: filter.data.min, max: filter.data.max, step: filter.data.step, className: "w-full" }));
|
|
@@ -13670,6 +14303,11 @@ const NumberFilter = ({ filter, value, onChange }) => {
|
|
|
13670
14303
|
*/
|
|
13671
14304
|
const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFilters, onCancel, className, translations, initialValues, }) => {
|
|
13672
14305
|
const [filterValues, setFilterValues] = $8WNJc$react.useState(initialValues ?? {});
|
|
14306
|
+
// Nombre accesible del panel: su título. Con `Drawer.Root`, el `DialogTrigger` nombraba el
|
|
14307
|
+
// diálogo con el id de su disparador (que aquí no existe); sin root nadie lo nombra y
|
|
14308
|
+
// react-aria avisa en cada apertura («A dialog must have a title for accessibility», medido en
|
|
14309
|
+
// jsdom). Mismo arreglo que `headerId` en la fachada `Drawer`.
|
|
14310
|
+
const titleId = $8WNJc$react.useId();
|
|
13673
14311
|
// Handle changes in individual filters
|
|
13674
14312
|
const handleFilterChange = $8WNJc$react.useCallback((filterKey, value) => {
|
|
13675
14313
|
setFilterValues((prev) => ({
|
|
@@ -13782,9 +14420,16 @@ const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFi
|
|
|
13782
14420
|
onApplyFilters(processedFilters);
|
|
13783
14421
|
onOpenChange(false);
|
|
13784
14422
|
}, [filterValues, config.data, onApplyFilters, onOpenChange]);
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
14423
|
+
// Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito, igual que la fachada
|
|
14424
|
+
// `Drawer` (`src/components/drawer/Drawer.tsx`): aquí no hay disparador —lo abre el `isOpen`
|
|
14425
|
+
// de la app— y un `DialogTrigger` sin hijo pulsable avisa en consola en CADA montaje, abierto o
|
|
14426
|
+
// cerrado («A PressResponder was rendered without a pressable child», medido en jsdom y por
|
|
14427
|
+
// beweossmbs con la .140). `Drawer.Backdrop` (`ModalOverlay`) ya crea su propio estado de
|
|
14428
|
+
// overlay con `isOpen`/`onOpenChange` y calcula todos los slots de `drawerVariants()`, así que
|
|
14429
|
+
// el root no aportaba nada.
|
|
14430
|
+
return (jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsxRuntime.jsx(react.Drawer.Content, { placement: "right", className: react.cn("drawer-filters", className), children: jsxRuntime.jsxs(react.Drawer.Dialog, { "aria-labelledby": titleId, className: "w-full p-0 sm:max-w-md", children: [jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), jsxRuntime.jsx(DrawerFiltersHeader, { titleId: titleId, title: config.title, description: config.description }), jsxRuntime.jsx(react.Drawer.Body, { className: "px-6 py-10", children: jsxRuntime.jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
|
|
14431
|
+
return (jsxRuntime.jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
|
|
14432
|
+
}) }) }), jsxRuntime.jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }));
|
|
13788
14433
|
};
|
|
13789
14434
|
DrawerFilters.displayName = "DrawerFilters";
|
|
13790
14435
|
|
|
@@ -15927,13 +16572,19 @@ ModalBody.displayName = "ModalBody";
|
|
|
15927
16572
|
* v3 conserva `flex-row justify-end gap-2` pero pierde el padding, y añade un
|
|
15928
16573
|
* `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
|
|
15929
16574
|
* con su propio padding) — de ahí el `mt-0`.
|
|
16575
|
+
*
|
|
16576
|
+
* `items-stretch`: v2 no declaraba `items-*` (`align-items: normal`, que en flex estira), y v3
|
|
16577
|
+
* pone `items-center`. En un footer en columna (`className="flex flex-col"`, «Register payment»
|
|
16578
|
+
* de beweossmbs) eso encogía cada fila a su contenido: medido con la .141, Total 83px frente a
|
|
16579
|
+
* 464 y botones `flex-1` 108px frente a 226. En la fila por defecto no cambia nada visible
|
|
16580
|
+
* (los `Button` tienen alto propio). Un `items-*` de la app lo retira por fusión.
|
|
15930
16581
|
*/
|
|
15931
16582
|
const ModalFooter = ({ className, ...props }) => {
|
|
15932
16583
|
const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15933
16584
|
return (jsxRuntime.jsx(react.ModalFooter, { ...props,
|
|
15934
16585
|
// v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
|
|
15935
16586
|
// gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
|
|
15936
|
-
className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
|
|
16587
|
+
className: react.cn("mt-0 items-stretch px-6 py-4", classNames?.footer, className) }));
|
|
15937
16588
|
};
|
|
15938
16589
|
ModalFooter.displayName = "ModalFooter";
|
|
15939
16590
|
|
|
@@ -16030,7 +16681,8 @@ const DrawerFacade = ({ isOpen, defaultOpen, onOpenChange, onClose, placement =
|
|
|
16030
16681
|
// Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito: la fachada no tiene
|
|
16031
16682
|
// disparador propio —lo abre el `isOpen` de la app— y un `DialogTrigger` sin hijo pulsable
|
|
16032
16683
|
// avisa en consola en CADA montaje («A PressResponder was rendered without a pressable
|
|
16033
|
-
// child», medido en jsdom
|
|
16684
|
+
// child», medido en jsdom). `Modal` hace lo mismo desde 27958d2 (ya no monta `Modal.Root`) y
|
|
16685
|
+
// `Modal.v2.test.tsx` vigila 0 avisos con modales cerrados. `Drawer.Backdrop` (`ModalOverlay`)
|
|
16034
16686
|
// ya crea y publica su propio estado de overlay con `isOpen`/`onOpenChange`, y trae todos los
|
|
16035
16687
|
// slots de `drawerVariants()`, así que el root no aportaba nada.
|
|
16036
16688
|
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 })) }) }) }));
|
|
@@ -16121,9 +16773,11 @@ DrawerBody.displayName = "Aurora.DrawerBody";
|
|
|
16121
16773
|
* Geometría v2 del slot `footer`: `flex flex-row gap-2 px-6 py-4 justify-end`
|
|
16122
16774
|
* (`@heroui/theme@2.4.15`, slot `footer`). v3 conserva `flex-row justify-end gap-2` pero pierde
|
|
16123
16775
|
* 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`).
|
|
16776
|
+
* `mt-0` (mismo arreglo que `ModalFooter`). Y `items-stretch` en lugar del `items-center` de
|
|
16777
|
+
* v3: v2 no declaraba `items-*`, así que un footer en columna estiraba sus filas (en «Register
|
|
16778
|
+
* payment» de beweossmbs, Total 83px frente a 464 con la .141; detalle en `ModalFooter`).
|
|
16125
16779
|
*/
|
|
16126
|
-
const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 px-6 py-4", className), children: children }));
|
|
16780
|
+
const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 items-stretch px-6 py-4", className), children: children }));
|
|
16127
16781
|
DrawerFooter.displayName = "Aurora.DrawerFooter";
|
|
16128
16782
|
|
|
16129
16783
|
// TECH-2459 — Fachada `Alert` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Alert` CRUDO de
|
|
@@ -16929,21 +17583,66 @@ function hasIndicator(children) {
|
|
|
16929
17583
|
return node.type === $8WNJc$react.Fragment && hasIndicator(node.props.children);
|
|
16930
17584
|
});
|
|
16931
17585
|
}
|
|
17586
|
+
// ---------------------------------------------------------------------------------------------
|
|
17587
|
+
// `size` de v2 → medidas de pestaña de v2
|
|
17588
|
+
// ---------------------------------------------------------------------------------------------
|
|
17589
|
+
/**
|
|
17590
|
+
* Medidas de pestaña por `size`, **copiadas del oráculo** `@heroui/theme@2.4.15`
|
|
17591
|
+
* `dist/components/tabs.js` (slot `tab`, `px-3` en la base y `size.{sm,md,lg}.tab` para la altura y
|
|
17592
|
+
* el cuerpo), traducidas a clases de Tailwind 4 con el MISMO valor. Las `text-tiny` /
|
|
17593
|
+
* `text-small` / `text-medium` de v2 son del plugin `heroui()` y en TW4 no existen
|
|
17594
|
+
* (`default-layout.js:32-40`):
|
|
17595
|
+
*
|
|
17596
|
+
* | size | v2 | aquí | cuerpo / interlineado |
|
|
17597
|
+
* |------|------------------------|----------------|-----------------------|
|
|
17598
|
+
* | sm | `h-7 text-tiny` + px-3 | `h-7 px-3 text-xs` | 0.75rem / 1rem |
|
|
17599
|
+
* | md | `h-8 text-small` + px-3 | `h-8 px-3 text-sm` | 0.875rem / 1.25rem |
|
|
17600
|
+
* | lg | `h-9 text-medium` + px-3 | `h-9 px-3 text-base` | 1rem / 1.5rem |
|
|
17601
|
+
*
|
|
17602
|
+
* Por qué se mapea, cuando `radius` y `color` se ignoran (§9.1): `size` no es estética, es
|
|
17603
|
+
* TAMAÑO, y descartarlo provoca un desbordamiento medido. linda-business embebido en el host v1 con
|
|
17604
|
+
* la `.140`: con `size="sm"` descartado, el texto pasaba de 12 px a 14 px y cada pestaña salía de 6
|
|
17605
|
+
* a 17 px más ancha. El carril medía 352 px en una columna de 287 px y el chevron del scroller
|
|
17606
|
+
* tapaba la última pestaña. smbs tiene 10 `size="sm"`.
|
|
17607
|
+
*
|
|
17608
|
+
* Lo que NO se copia: el peso (v3 pone `font-medium`, v2 no ponía nada) y el radio (`rounded-small`),
|
|
17609
|
+
* que siguen como v3 por §9.1. Tampoco el `py-1` de v2: con altura fija y `items-center` no
|
|
17610
|
+
* cambia nada.
|
|
17611
|
+
*
|
|
17612
|
+
* Las clases van ANTES de `classNames.tab` y del `className` del `Tab` en un `cn`
|
|
17613
|
+
* (tailwind-merge): lo de la app gana. El `h-7 px-2` de linda-business sigue mandando sobre el
|
|
17614
|
+
* `px-3`. Clases literales en el fuente (no construidas), para que Tailwind las genere.
|
|
17615
|
+
*/
|
|
17616
|
+
const TAB_SIZE_CLASS = {
|
|
17617
|
+
sm: "h-7 px-3 text-xs",
|
|
17618
|
+
md: "h-8 px-3 text-sm",
|
|
17619
|
+
lg: "h-9 px-3 text-base",
|
|
17620
|
+
};
|
|
17621
|
+
/**
|
|
17622
|
+
* El `size` del root llega a los `Tabs.Tab` de la forma v3 por contexto, porque esas pestañas las
|
|
17623
|
+
* escribe la app dentro de su `Tabs.List` y la fachada no las recorre. Sin `size` el valor es
|
|
17624
|
+
* `undefined` y la pestaña sale byte a byte como antes.
|
|
17625
|
+
*/
|
|
17626
|
+
const TabSizeContext = $8WNJc$react.createContext(undefined);
|
|
16932
17627
|
const TabFacade = ({ children: ownChildren, title,
|
|
16933
17628
|
// `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
|
|
16934
17629
|
titleValue: _titleValue, ...props }) => {
|
|
16935
17630
|
// En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
|
|
16936
17631
|
// `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
|
|
16937
17632
|
const children = ownChildren ?? title;
|
|
17633
|
+
const sizeClass = $8WNJc$react.useContext(TabSizeContext);
|
|
17634
|
+
const tabProps = sizeClass
|
|
17635
|
+
? { ...props, className: react.cn(sizeClass, props.className) }
|
|
17636
|
+
: props;
|
|
16938
17637
|
// `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
|
|
16939
17638
|
// puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
|
|
16940
17639
|
// hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
|
|
16941
17640
|
// indicador él (es la vía de escape de este caso). Exposición hoy: 0 usos en los 6 repos.
|
|
16942
17641
|
if (typeof children === "function") {
|
|
16943
|
-
return jsxRuntime.jsx(react.Tabs.Tab, { ...
|
|
17642
|
+
return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
|
|
16944
17643
|
}
|
|
16945
17644
|
if (hasIndicator(children)) {
|
|
16946
|
-
return jsxRuntime.jsx(react.Tabs.Tab, { ...
|
|
17645
|
+
return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
|
|
16947
17646
|
}
|
|
16948
17647
|
// El indicador va PRIMERO por legibilidad del marcado, no por efecto: es `position:absolute`
|
|
16949
17648
|
// con `z-index:-1`, o sea fuera del flujo y detrás del texto, así que el orden no cambia el
|
|
@@ -16953,7 +17652,7 @@ titleValue: _titleValue, ...props }) => {
|
|
|
16953
17652
|
// `{children}` se pasa SIN normalizar (nada de `Children.toArray`) para no reescribir las
|
|
16954
17653
|
// keys del consumidor: hacerlo cambiaría la reconciliación de una lista de pestañas
|
|
16955
17654
|
// condicional. Verificado en el navegador que la anidación no produce aviso de key.
|
|
16956
|
-
return (jsxRuntime.jsxs(react.Tabs.Tab, { ...
|
|
17655
|
+
return (jsxRuntime.jsxs(react.Tabs.Tab, { ...tabProps, children: [jsxRuntime.jsx(react.Tabs.Indicator, {}), children] }));
|
|
16957
17656
|
};
|
|
16958
17657
|
TabFacade.displayName = "Aurora.Tab";
|
|
16959
17658
|
/**
|
|
@@ -17064,8 +17763,10 @@ function hasPanelContent(children) {
|
|
|
17064
17763
|
}
|
|
17065
17764
|
const TabsFacade = (allProps) => {
|
|
17066
17765
|
const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
|
|
17766
|
+
// v2: medidas de pestaña (ver `TAB_SIZE_CLASS`).
|
|
17767
|
+
size,
|
|
17067
17768
|
// Sin equivalente en v3: se consumen para que no lleguen al DOM (ver README, «Props v2»).
|
|
17068
|
-
|
|
17769
|
+
radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
|
|
17069
17770
|
const v3Variant = toV3Variant(variant);
|
|
17070
17771
|
const v3Orientation = orientation ??
|
|
17071
17772
|
(isVertical || placement === "start" || placement === "end"
|
|
@@ -17126,7 +17827,7 @@ const TabsFacade = (allProps) => {
|
|
|
17126
17827
|
// `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
|
|
17127
17828
|
// neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
|
|
17128
17829
|
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 },
|
|
17830
|
+
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(size && TAB_SIZE_CLASS[size], classNames?.tab, tabClassName), id: id, key: id },
|
|
17130
17831
|
jsxRuntime.jsx(react.Tabs.Indicator, { className: react.cn(classNames?.cursor) }),
|
|
17131
17832
|
classNames?.tabContent ? (jsxRuntime.jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
|
|
17132
17833
|
.filter(({ panel }) => hasPanelContent(panel))
|
|
@@ -17145,7 +17846,9 @@ const TabsFacade = (allProps) => {
|
|
|
17145
17846
|
orientation: v3Orientation,
|
|
17146
17847
|
variant: v3Variant,
|
|
17147
17848
|
};
|
|
17148
|
-
|
|
17849
|
+
const v3Tabs = jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
|
|
17850
|
+
// Sin `size` no se monta el provider: el árbol queda idéntico al de antes.
|
|
17851
|
+
return size ? (jsxRuntime.jsx(TabSizeContext.Provider, { value: TAB_SIZE_CLASS[size], children: v3Tabs })) : (v3Tabs);
|
|
17149
17852
|
};
|
|
17150
17853
|
const TabsV3 = ({ children, ...props }) => {
|
|
17151
17854
|
const nodes = $8WNJc$react.Children.toArray(children);
|