@beweco/aurora-ui 1.0.0-beta.140 → 1.0.0-beta.141
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 +1 -1
- package/dist/index.cjs.js +571 -99
- package/dist/index.esm.js +572 -100
- 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/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/DrawerFiltersHeader.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/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/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.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Card as Card$1, Breadcrumbs, ProgressBar, cn, Label, ListBoxSection, ListBoxItem, Separator, RouterProvider, I18nProvider, useOverlayState, Spinner, buttonVariants, Button as Button$1, Chip as Chip$1, CloseButton, Pagination as Pagination$1, Skeleton, Accordion, DatePicker as DatePicker$1, DateField, Description, Calendar, FieldError, DateRangePicker as DateRangePicker$1, RangeCalendar, TextField, InputGroup, RadioGroup, Radio, Tabs as Tabs$1, Dropdown as Dropdown$1, Header, ComboBox, Input as Input$1, ListBox, Tooltip as Tooltip$2, DropdownTrigger, DropdownPopover, DropdownMenu, DropdownSection as DropdownSection$1, DropdownItem, Badge as Badge$1, Popover, PopoverTrigger, PopoverContent, Avatar, Link, Switch as Switch$1, TimeField, Select as Select$1, Checkbox as Checkbox$1, Table as Table$1, TextArea, toastVariants, WarningIcon, SuccessIcon, InfoIcon, DangerIcon, Modal as Modal$1, Slider, Drawer as Drawer$1, ModalHeader as ModalHeader$1, ModalBody as ModalBody$1, ModalFooter as ModalFooter$1, DrawerHeader as DrawerHeader$1, DrawerBody as DrawerBody$1, DrawerFooter as DrawerFooter$1, Alert as Alert$1 } from '@heroui/react';
|
|
1
|
+
import { Card as Card$1, Breadcrumbs, ProgressBar, cn, Label, ListBoxSection, ListBoxItem, Separator, RouterProvider, I18nProvider, useOverlayState, Spinner, buttonVariants, Button as Button$1, Chip as Chip$1, CloseButton, Pagination as Pagination$1, Skeleton, Accordion, DatePicker as DatePicker$1, DateField, Description, Calendar, FieldError, DateRangePicker as DateRangePicker$1, RangeCalendar, TextField, InputGroup, RadioGroup, Radio, Tabs as Tabs$1, Dropdown as Dropdown$1, Header, ComboBox, Input as Input$1, ListBox, Tooltip as Tooltip$2, DropdownTrigger, DropdownPopover, DropdownMenu, DropdownSection as DropdownSection$1, DropdownItem, Badge as Badge$1, Popover, PopoverTrigger, PopoverContent, Avatar, Link, Switch as Switch$1, TimeField, useFilter, Autocomplete, Select as Select$1, SearchField, Checkbox as Checkbox$1, Table as Table$1, TextArea, toastVariants, WarningIcon, SuccessIcon, InfoIcon, DangerIcon, Modal as Modal$1, Slider, Drawer as Drawer$1, ModalHeader as ModalHeader$1, ModalBody as ModalBody$1, ModalFooter as ModalFooter$1, DrawerHeader as DrawerHeader$1, DrawerBody as DrawerBody$1, DrawerFooter as DrawerFooter$1, Alert as Alert$1 } from '@heroui/react';
|
|
2
2
|
export { Accordion, AccordionItem, Avatar, Breadcrumbs, CardFooter, CardHeader, CheckboxGroup, CloseButton, DropdownItem, DropdownMenu, DropdownTrigger, Link, Popover, PopoverContent, PopoverTrigger, Radio, RadioGroup, Skeleton, Slider, Spinner, TableBody, TableCell, TableColumn, TableHeader, TableRow, cn } from '@heroui/react';
|
|
3
3
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
4
4
|
import $8WNJc$react, { Children, isValidElement, Fragment, useState, useRef, useCallback, useEffect, createElement, useId, forwardRef, useMemo, cloneElement, useImperativeHandle, createContext, useContext, useLayoutEffect, memo, useReducer, version } from 'react';
|
|
@@ -759,6 +759,41 @@ const SHAPE_BY_VARIANT = {
|
|
|
759
759
|
faded: "outline",
|
|
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,7 @@ const V3_VARIANT_BY_SHAPE = {
|
|
|
876
911
|
soft: "soft",
|
|
877
912
|
transparent: "tertiary",
|
|
878
913
|
outline: "tertiary",
|
|
914
|
+
dot: "tertiary",
|
|
879
915
|
};
|
|
880
916
|
/**
|
|
881
917
|
* TECH-2459 · F5 · P-08d — La tabla `flat` de v2, medida contra los PNG congelados y no deducida.
|
|
@@ -983,6 +1019,13 @@ const CLOSE_ICON_SIZE_CLASS = {
|
|
|
983
1019
|
lg: "size-5",
|
|
984
1020
|
};
|
|
985
1021
|
function colorStyle(color, shape) {
|
|
1022
|
+
if (shape === "dot") {
|
|
1023
|
+
// `dot`: el chip es neutro para cualquier color (el color va solo en el punto).
|
|
1024
|
+
return {
|
|
1025
|
+
"--chip-bg": "transparent",
|
|
1026
|
+
"--chip-fg": "var(--color-foreground)",
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
986
1029
|
const t = COLOR_TOKENS[color];
|
|
987
1030
|
if (shape === "solid") {
|
|
988
1031
|
return { "--chip-bg": t.bg, "--chip-fg": t.fg };
|
|
@@ -1018,14 +1061,17 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
1018
1061
|
const hasEndContent = Boolean(endContent) || Boolean(onClose);
|
|
1019
1062
|
// `isOneChar` de v2 (`use-chip.js:58`): SOLO si el children es un string de un carácter — un
|
|
1020
1063
|
// número no cuenta. El cuadrado además exige no llevar nada a los lados (ver `ONE_CHAR_CLASS`).
|
|
1021
|
-
const
|
|
1064
|
+
const isDot = shape === "dot";
|
|
1065
|
+
const isSquare = !isDot &&
|
|
1066
|
+
typeof children === "string" &&
|
|
1022
1067
|
children.length === 1 &&
|
|
1023
1068
|
!startContent &&
|
|
1024
1069
|
!hasEndContent;
|
|
1025
1070
|
const vars = isStandard
|
|
1026
1071
|
? colorStyle(color, shape)
|
|
1027
1072
|
: undefined;
|
|
1028
|
-
|
|
1073
|
+
// Con `dot` el color no tiñe el chip, tampoco el de los colores fijos.
|
|
1074
|
+
const customClass = isStandard || isDot ? "" : CUSTOM_COLOR_CLASSES[color];
|
|
1029
1075
|
const outlineClass = shape === "outline" ? "border-2" : "";
|
|
1030
1076
|
// El `dark:text-*` que v2 declaraba solo para el texto de `flat` en success/warning/danger
|
|
1031
1077
|
// (P-08d). Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
|
|
@@ -1062,7 +1108,7 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
1062
1108
|
className: cn(SIZE_CLASS$6[size],
|
|
1063
1109
|
// Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
|
|
1064
1110
|
// 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, jsx("span", { className: cn("flex-1 text-inherit font-normal", startContent
|
|
1111
|
+
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 && (jsx("span", { "aria-hidden": "true", "data-slot": "dot", className: cn(DOT_SLOT_CLASS, DOT_COLOR_CLASS[color]) })), jsx("span", { className: cn("flex-1 text-inherit font-normal", startContent
|
|
1066
1112
|
? CONTENT_PADDING[size].leftWithContent
|
|
1067
1113
|
: CONTENT_PADDING[size].left, hasEndContent
|
|
1068
1114
|
? CONTENT_PADDING[size].rightWithContent
|
|
@@ -1087,7 +1133,11 @@ const sizeMap = {
|
|
|
1087
1133
|
const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFlip, flip, rotate, ...nativeProps }) => {
|
|
1088
1134
|
// Un número son píxeles tal cual (p. ej. 64): la escala con nombre se queda en 32 y
|
|
1089
1135
|
// `width`/`height` no son props del componente (caerían en el <span>, sin efecto).
|
|
1090
|
-
|
|
1136
|
+
// Un nombre fuera de la escala (`"xs"`, el alias v2 del tipo) cae al default, como en v2
|
|
1137
|
+
// (`sizeMap[size] ?? sizeMap.md`, `@beweco/aurora-ui@0.6.85`): 20px.
|
|
1138
|
+
const iconSize = typeof size === "number"
|
|
1139
|
+
? `${size}px`
|
|
1140
|
+
: (sizeMap[size] ?? sizeMap.md);
|
|
1091
1141
|
return (jsx("span", { ...nativeProps, children: jsx(Icon, { icon: icon, width: iconSize, height: iconSize, className: className, style: style, color: color, hFlip: hFlip, vFlip: vFlip, flip: flip, rotate: rotate }) }));
|
|
1092
1142
|
};
|
|
1093
1143
|
|
|
@@ -2400,6 +2450,40 @@ const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none foc
|
|
|
2400
2450
|
const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
|
|
2401
2451
|
const HOVERABLE_CLASS = "hover:bg-content2";
|
|
2402
2452
|
const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
2453
|
+
// Controles que tienen su propia pulsación. Un clic que nace dentro de uno de ellos es SUYO, no de
|
|
2454
|
+
// la card: los `Button` de Aurora (react-aria) ya paran la propagación del `click` (medido en
|
|
2455
|
+
// jsdom con el `Button` de la librería), pero un `<button>`/`<a>` nativo o un `<input>` no.
|
|
2456
|
+
const CONTROL_INTERNO = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="tab"], [role="option"]';
|
|
2457
|
+
function desdeControlInterno(event) {
|
|
2458
|
+
const target = event.target;
|
|
2459
|
+
if (!target || target === event.currentTarget) {
|
|
2460
|
+
return false;
|
|
2461
|
+
}
|
|
2462
|
+
const control = target.closest?.(CONTROL_INTERNO);
|
|
2463
|
+
return (!!control &&
|
|
2464
|
+
control !== event.currentTarget &&
|
|
2465
|
+
event.currentTarget.contains(control));
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Tecla → `PressEvent` con la forma del de react-aria (`pointerType: "keyboard"`, punto en el
|
|
2469
|
+
* centro de la card, como hace `pressEventFromClick` con un click sintético de `detail === 0`).
|
|
2470
|
+
*/
|
|
2471
|
+
function pressEventFromKey(event) {
|
|
2472
|
+
const target = event.currentTarget;
|
|
2473
|
+
const rect = target.getBoundingClientRect();
|
|
2474
|
+
return {
|
|
2475
|
+
type: "press",
|
|
2476
|
+
pointerType: "keyboard",
|
|
2477
|
+
target,
|
|
2478
|
+
shiftKey: event.shiftKey,
|
|
2479
|
+
ctrlKey: event.ctrlKey,
|
|
2480
|
+
metaKey: event.metaKey,
|
|
2481
|
+
altKey: event.altKey,
|
|
2482
|
+
x: rect.width / 2,
|
|
2483
|
+
y: rect.height / 2,
|
|
2484
|
+
continuePropagation: () => undefined,
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2403
2487
|
/**
|
|
2404
2488
|
* Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
|
|
2405
2489
|
*
|
|
@@ -2422,39 +2506,83 @@ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
|
2422
2506
|
* `isHoverable`/`isDisabled`/`fullWidth`.
|
|
2423
2507
|
*
|
|
2424
2508
|
* `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
|
|
2509
|
+
* `Input.tsx`) y apunta al nodo raíz, que es siempre un `<div>` (con `isPressable`,
|
|
2510
|
+
* `<div role="button">`).
|
|
2426
2511
|
*/
|
|
2427
|
-
const Card = 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) => {
|
|
2512
|
+
const Card = 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
2513
|
// Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
|
|
2429
2514
|
const sinRipple = disableRipple || disableAnimation;
|
|
2430
2515
|
const { ondas, lanzar, quitar } = useRipple();
|
|
2516
|
+
const pulsable = isPressable && !isDisabled;
|
|
2517
|
+
const press = useCallback((pressEvent) => {
|
|
2518
|
+
onPress?.(pressEvent);
|
|
2519
|
+
if (!sinRipple) {
|
|
2520
|
+
lanzar(pressEvent);
|
|
2521
|
+
}
|
|
2522
|
+
}, [onPress, sinRipple, lanzar]);
|
|
2431
2523
|
const handleClick = useCallback((event) => {
|
|
2524
|
+
// Card pulsable deshabilitada: ni `onClick` de la app, como el `disabled` nativo del
|
|
2525
|
+
// `<button>` que era hasta la .140.
|
|
2526
|
+
if (isPressable && isDisabled) {
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2432
2529
|
onClick?.(event);
|
|
2433
|
-
if (!
|
|
2530
|
+
if (!pulsable || desdeControlInterno(event)) {
|
|
2434
2531
|
return;
|
|
2435
2532
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2533
|
+
press(pressEventFromClick(event));
|
|
2534
|
+
}, [onClick, isPressable, isDisabled, pulsable, press]);
|
|
2535
|
+
// Teclado de un `<button>` nativo, que el `<div role="button">` no trae: Enter pulsa al
|
|
2536
|
+
// bajar la tecla y Espacio al soltarla. `preventDefault` en ambos: Espacio haría scroll de
|
|
2537
|
+
// la página y Enter podría enviar un `<form>` que envuelva la card. Solo cuando el foco está
|
|
2538
|
+
// EN la card: una tecla sobre un botón interno es de ese botón (react-aria ya para su
|
|
2539
|
+
// propagación, y esta guarda cubre también a los controles nativos).
|
|
2540
|
+
const handleKeyDown = useCallback((event) => {
|
|
2541
|
+
onKeyDown?.(event);
|
|
2542
|
+
if (!pulsable || event.target !== event.currentTarget) {
|
|
2543
|
+
return;
|
|
2440
2544
|
}
|
|
2441
|
-
|
|
2545
|
+
if (event.key === " ") {
|
|
2546
|
+
event.preventDefault();
|
|
2547
|
+
}
|
|
2548
|
+
else if (event.key === "Enter") {
|
|
2549
|
+
event.preventDefault();
|
|
2550
|
+
if (!event.repeat) {
|
|
2551
|
+
press(pressEventFromKey(event));
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}, [onKeyDown, pulsable, press]);
|
|
2555
|
+
const handleKeyUp = useCallback((event) => {
|
|
2556
|
+
onKeyUp?.(event);
|
|
2557
|
+
if (!pulsable ||
|
|
2558
|
+
event.target !== event.currentTarget ||
|
|
2559
|
+
event.key !== " ") {
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
event.preventDefault();
|
|
2563
|
+
press(pressEventFromKey(event));
|
|
2564
|
+
}, [onKeyUp, pulsable, press]);
|
|
2442
2565
|
const combinedClassName = cn(paddingClasses[padding], RADIUS_CLASS$5[radius], SHADOW_CLASS[shadow], fullWidth ? "w-full" : "", isPressable ? PRESSABLE_CLASS : "", isPressable && !disableAnimation ? PRESS_ANIMATION_CLASS : "", isHoverable ? HOVERABLE_CLASS : "", isDisabled ? DISABLED_CLASS : "",
|
|
2443
2566
|
// El `className` de la app, último: gana por fusión (ver JSDoc).
|
|
2444
2567
|
className);
|
|
2445
|
-
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable
|
|
2446
|
-
//
|
|
2447
|
-
//
|
|
2448
|
-
//
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2568
|
+
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable`, y hasta la .140 esta fachada lo
|
|
2569
|
+
// imitaba con el `render` de v3. Se quitó porque las apps meten botones DENTRO de la card
|
|
2570
|
+
// pulsable (`ChannelCard` de smbs, Linda → Compartir: «Compartir»/«Integrar»), y
|
|
2571
|
+
// `<button>` dentro de `<button>` es HTML inválido: React avisa en consola
|
|
2572
|
+
// («cannot be a descendant of <button>», medido en jsdom) y el parser del navegador lo
|
|
2573
|
+
// reparenta. La card se queda en el `<div>` de v3 con la semántica de botón de v2
|
|
2574
|
+
// (`useCard`: `role: "button"` + `tabIndex: isPressable ? 0 : -1`) y el teclado a mano
|
|
2575
|
+
// (ver `handleKeyDown`). El `render` de la app pasa tal cual.
|
|
2576
|
+
const pressableProps = isPressable
|
|
2577
|
+
? {
|
|
2578
|
+
role: "button",
|
|
2579
|
+
tabIndex: isDisabled ? undefined : 0,
|
|
2580
|
+
"aria-disabled": isDisabled || undefined,
|
|
2581
|
+
onKeyDown: handleKeyDown,
|
|
2582
|
+
onKeyUp: handleKeyUp,
|
|
2583
|
+
}
|
|
2584
|
+
: {};
|
|
2585
|
+
return (jsxs(Card$1, { ...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 && (jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
|
|
2458
2586
|
});
|
|
2459
2587
|
Card.displayName = "Card";
|
|
2460
2588
|
|
|
@@ -3184,7 +3312,7 @@ const CLEAR_GAP_WITH_TRIGGER_UNDERLINED_CLASS = "me-9";
|
|
|
3184
3312
|
* ⚠️ `h-4 w-4 p-0` deja el glifo de 16px ocupando la caja entera; el `[&_svg]:m-0` quita los
|
|
3185
3313
|
* márgenes negativos de v3 para que el centro del glifo coincida con el centro de la caja.
|
|
3186
3314
|
*/
|
|
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";
|
|
3315
|
+
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
3316
|
const DEFAULT_ICON = (jsx(IconComponent, { icon: "solar:magnifer-outline", className: "text-default-400 pointer-events-none shrink-0", size: "sm", "aria-hidden": true }));
|
|
3189
3317
|
/**
|
|
3190
3318
|
* AuraAutocomplete genérico reutilizable, migrado al `ComboBox` compound de HeroUI v3 (el
|
|
@@ -3209,7 +3337,9 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
|
|
|
3209
3337
|
// limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
|
|
3210
3338
|
// pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
|
|
3211
3339
|
// controlado con `!== undefined`).
|
|
3212
|
-
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection,
|
|
3340
|
+
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, isOpen, onOpenChange, popoverProps,
|
|
3341
|
+
// Aceptada y descartada: ver `AuraAutocompleteProps.labelPlacement`.
|
|
3342
|
+
labelPlacement: _labelPlacement, scrollShadowProps, ...props }) => {
|
|
3213
3343
|
const generatedId = useId();
|
|
3214
3344
|
const autoId = id || generatedId;
|
|
3215
3345
|
// Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
|
|
@@ -3225,6 +3355,26 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3225
3355
|
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
3226
3356
|
/** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
|
|
3227
3357
|
const inputRef = useRef(null);
|
|
3358
|
+
/*
|
|
3359
|
+
* `isOpen` controlado (ver `AuraAutocompleteProps.isOpen`). La fachada sigue el estado de
|
|
3360
|
+
* apertura de react-aria por su `onOpenChange` y decide con él si el popover se ve: visible solo
|
|
3361
|
+
* si react-aria lo tiene abierto Y la app no lo ha cerrado. `ComboBox.Popover` es el `Popover`
|
|
3362
|
+
* de react-aria-components, que con `isOpen` propio usa ese valor en vez del estado del
|
|
3363
|
+
* `ComboBox` (`Popover.mjs`: `props.isOpen != null ? localState : contextState`).
|
|
3364
|
+
*/
|
|
3365
|
+
const [ariaOpen, setAriaOpen] = useState(false);
|
|
3366
|
+
const handleOpenChange = (open, menuTrigger) => {
|
|
3367
|
+
setAriaOpen(open);
|
|
3368
|
+
onOpenChange?.(open, menuTrigger);
|
|
3369
|
+
};
|
|
3370
|
+
const handleInputChange = (value) => {
|
|
3371
|
+
// La lista está abierta para react-aria pero la app la cerró: react-aria no volverá a avisar
|
|
3372
|
+
// (para él ya está abierta), así que la fachada pide abrir como hacía v2 al teclear.
|
|
3373
|
+
if (isOpen === false && ariaOpen) {
|
|
3374
|
+
onOpenChange?.(true, "input");
|
|
3375
|
+
}
|
|
3376
|
+
onInputChange?.(value);
|
|
3377
|
+
};
|
|
3228
3378
|
// `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
|
|
3229
3379
|
// state.inputValue?.length > 0` (`chunk-NJJHRP6D.mjs:273`), y apagado por `isDisabled`/
|
|
3230
3380
|
// `isReadOnly` como en el cálculo de `isClearable` del hook (línea 20 del mismo fichero).
|
|
@@ -3258,7 +3408,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3258
3408
|
onInputChange?.("");
|
|
3259
3409
|
inputRef.current?.focus();
|
|
3260
3410
|
};
|
|
3261
|
-
return (jsxs(ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange:
|
|
3411
|
+
return (jsxs(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 && jsx(Label, { className: LABEL_CLASS$3, children: label }), jsxs(ComboBox.InputGroup, { className: [
|
|
3262
3412
|
SIZE_CLASS$2[size],
|
|
3263
3413
|
PADDING_CLASS$1[size],
|
|
3264
3414
|
variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
|
|
@@ -3307,7 +3457,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3307
3457
|
* su `aria-label` traducido, y alcanzable con teclado como la ✕ de `Input`.
|
|
3308
3458
|
*/
|
|
3309
3459
|
slot: null, "aria-label": t.clearButtonAriaLabel, className: [
|
|
3310
|
-
CLEAR_BUTTON_CLASS,
|
|
3460
|
+
CLEAR_BUTTON_CLASS$2,
|
|
3311
3461
|
resolvedIcon != null
|
|
3312
3462
|
? CLEAR_GAP_WITH_ICON_CLASS
|
|
3313
3463
|
: variant === "underlined"
|
|
@@ -3321,7 +3471,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
3321
3471
|
isDisabled ? ICON_DISABLED_CLASS : "",
|
|
3322
3472
|
]
|
|
3323
3473
|
.filter(Boolean)
|
|
3324
|
-
.join(" "), children: resolvedIcon })), jsx(ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsx(FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsx(ComboBox.Popover, { children: jsx(ListBox, { autoFocus: "first",
|
|
3474
|
+
.join(" "), children: resolvedIcon })), jsx(ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsx(FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsx(ComboBox.Popover, { ...popoverProps, isOpen: isOpen === undefined ? undefined : isOpen && ariaOpen, children: jsx(ListBox, { autoFocus: "first",
|
|
3475
|
+
// `scrollShadowProps` de v2 al contenedor con scroll (el `ListBox`): la clase
|
|
3476
|
+
// de la app va al final y gana por `cn`, y `overflow-y-auto` para que un
|
|
3477
|
+
// `max-h-*` recorte desplazando y no desbordando.
|
|
3478
|
+
className: scrollShadowProps?.className
|
|
3479
|
+
? cn(OPCIONES_CLASS, "overflow-y-auto", scrollShadowProps.className)
|
|
3480
|
+
: OPCIONES_CLASS, style: scrollShadowProps?.style, renderEmptyState: hideEmptyContent
|
|
3325
3481
|
? undefined
|
|
3326
3482
|
: () => jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
|
|
3327
3483
|
// El normalizador de `utils/` está tipado solo para `ReactNode`; con una
|
|
@@ -9662,7 +9818,98 @@ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text
|
|
|
9662
9818
|
*/
|
|
9663
9819
|
const DEFAULT_TRANSLATIONS$8 = {
|
|
9664
9820
|
emptyContent: "No hay opciones",
|
|
9821
|
+
searchPlaceholder: "Buscar…",
|
|
9822
|
+
// Mismo literal que la lista vacía de `AuraAutocomplete`: es el mismo caso (un filtro por texto
|
|
9823
|
+
// que no encuentra nada), así que las dos fachadas lo dicen igual.
|
|
9824
|
+
noResults: "No se encontraron resultados",
|
|
9825
|
+
// Mismo literal que la ✕ de `Input` y de `AuraAutocomplete`.
|
|
9826
|
+
clearButtonAriaLabel: "Limpiar",
|
|
9665
9827
|
};
|
|
9828
|
+
/*
|
|
9829
|
+
* EL BUSCADOR Y LA ✕ (TECH-2459, hallazgo beweossmbs 23-sep): `isSearchable` e `isClearable`.
|
|
9830
|
+
*
|
|
9831
|
+
* Con cualquiera de los dos la fachada cambia de RAÍZ: monta el compound `Autocomplete` de v3 en
|
|
9832
|
+
* vez de `Select`. No es un compound distinto por dentro —`AutocompleteRoot` ES el `Select` de
|
|
9833
|
+
* react-aria (`@heroui/react/dist/components/autocomplete/autocomplete.js:50`, `jsx(Select, …)`),
|
|
9834
|
+
* así que `value`/`onChange`/`selectionMode`/`disabledKeys` y la colección funcionan igual y la
|
|
9835
|
+
* traducción de la API v2 de abajo no cambia ni una línea—, pero su disparador sí:
|
|
9836
|
+
*
|
|
9837
|
+
* - `Autocomplete.Trigger` es un `Group` (un `<div>`), no un `<button>`. Es lo que permite meter la
|
|
9838
|
+
* ✕ DENTRO del campo sin anidar dos botones (trampa nº 4 de `auraui-components`). El botón que
|
|
9839
|
+
* recibe el foco es el del `Autocomplete.Indicator`, que consume el `ButtonContext` del Select
|
|
9840
|
+
* (`autocomplete.js`, `jsx(Button, { children: indicator })`): Tab llega al chevron y desde ahí
|
|
9841
|
+
* las flechas/Enter abren la lista, igual que en el `Select` de siempre.
|
|
9842
|
+
* - `Autocomplete.Filter` (el `Autocomplete` de react-aria) conecta un `SearchField` con el
|
|
9843
|
+
* `ListBox`: el texto filtra la colección y las flechas mueven el foco virtual de la lista sin
|
|
9844
|
+
* salir del buscador. Es la receta oficial «Migrating to v3 Autocomplete (searchable select)»
|
|
9845
|
+
* de la guía de migración, con `useFilter({ sensitivity: "base" })`: sin distinguir mayúsculas
|
|
9846
|
+
* ni acentos («cancun» encuentra «Cancún»).
|
|
9847
|
+
*
|
|
9848
|
+
* Sin ninguna de las dos props la fachada sigue montando `Select`, exactamente como antes: los
|
|
9849
|
+
* 107 `Select` de beweossmbs que no las pasan no cambian ni un nodo.
|
|
9850
|
+
*
|
|
9851
|
+
* Por qué la ✕ va también por aquí y no sobre el `Select`: el disparador de `Select` es un
|
|
9852
|
+
* `<button>` (no admite otro dentro) y la fachada no alcanza el estado de react-aria para vaciar la
|
|
9853
|
+
* selección de un campo no controlado (`SelectStateContext` no lo reexporta `@heroui/react`, e
|
|
9854
|
+
* importarlo de `react-aria-components` metería una SEGUNDA copia del contexto en el bundle, que no
|
|
9855
|
+
* casaría con la de HeroUI). `Autocomplete.ClearButton` sí lo alcanza: vacía la selección con
|
|
9856
|
+
* `state.selectionManager.setSelectedKeys(new Set())` y llama a `onClear`.
|
|
9857
|
+
*/
|
|
9858
|
+
/** Hueco del valor cuando la ✕ va a su derecha: el hueco del chevron lo reserva la ✕. */
|
|
9859
|
+
const VALUE_ROOM_CLEARABLE_CLASS = "me-1 min-w-0 truncate";
|
|
9860
|
+
/*
|
|
9861
|
+
* La ✕. `.autocomplete__clear-button` de v3 ya la pinta como v2 (glifo sin caja, `bg-transparent`,
|
|
9862
|
+
* gris `text-muted`) y la esconde con `opacity-0` cuando no hay nada elegido. v2 no la montaba sin
|
|
9863
|
+
* selección (`@heroui/select@2.4.33`, `chunk-BJWIL32D.mjs:64`: `isClearable &&
|
|
9864
|
+
* state.selectedItems?.length`), así que aquí se retira del flujo en ese estado en vez de dejar un
|
|
9865
|
+
* hueco invisible de 20px.
|
|
9866
|
+
*
|
|
9867
|
+
* `me-5`: el chevron es `absolute end-3` (12px del borde, 16px de ancho) y la ✕ va en flujo, que
|
|
9868
|
+
* acaba en el relleno del trigger (`px-3`, 12px). Sin margen caería ENCIMA del chevron; con 20px su
|
|
9869
|
+
* canto derecho queda a 4px del izquierdo del chevron.
|
|
9870
|
+
*/
|
|
9871
|
+
const CLEAR_BUTTON_CLASS$1 = "z-10 me-5 data-[empty=true]:hidden";
|
|
9872
|
+
/*
|
|
9873
|
+
* EL FOCO DEL TECLADO (hallazgo del coordinador con Playwright, 23-sep). En la raíz `Autocomplete`
|
|
9874
|
+
* el único elemento tabulable es el botón del chevron —el `Button` de react-aria que monta
|
|
9875
|
+
* `Autocomplete.Indicator`, sin clase propia ni forma de pasarle una—, y en el flujo mide **0×0**:
|
|
9876
|
+
* su único hijo es el SVG, que es `absolute`. El foco caía en una caja vacía (el borde del disparador
|
|
9877
|
+
* sí cambiaba, por `focus-within`, pero el elemento enfocado no tenía caja para un lector de
|
|
9878
|
+
* pantalla, un magnificador o un anillo propio).
|
|
9879
|
+
*
|
|
9880
|
+
* Se estira ese botón sobre el disparador entero (`absolute inset-0`, mismo radio): el elemento
|
|
9881
|
+
* enfocado pasa a medir lo mismo que el disparador, como el `<button>` del `Select` normal, y el
|
|
9882
|
+
* SVG sigue en su sitio porque su contenedor posicionado cubre la misma caja. Pulsar en cualquier
|
|
9883
|
+
* punto sigue abriendo (el `Group` ya abría al pulsar, y el botón ya estaba DENTRO del grupo).
|
|
9884
|
+
* La ✕ va por encima (`z-10` en `CLEAR_BUTTON_CLASS`) para seguir recibiendo su clic.
|
|
9885
|
+
*
|
|
9886
|
+
* Se apunta por estructura porque es la única vía: `button:not([data-slot])` es ese botón, ya que
|
|
9887
|
+
* la ✕ lleva `data-slot="autocomplete-clear-button"`.
|
|
9888
|
+
*/
|
|
9889
|
+
const INDICATOR_BUTTON_OVERLAY_CLASS = [
|
|
9890
|
+
"[&>button:not([data-slot])]:absolute",
|
|
9891
|
+
"[&>button:not([data-slot])]:inset-0",
|
|
9892
|
+
"[&>button:not([data-slot])]:rounded-[inherit]",
|
|
9893
|
+
"[&>button:not([data-slot])]:outline-none",
|
|
9894
|
+
"[&>button:not([data-slot])]:cursor-[inherit]",
|
|
9895
|
+
].join(" ");
|
|
9896
|
+
/*
|
|
9897
|
+
* El foco en el disparador `Group`. El `FOCUS_BORDER_CLASS` del Select casa `aria-expanded` y
|
|
9898
|
+
* `:focus` del `<button>`; aquí el disparador es un `<div>` que no lleva ninguno de los dos: el foco
|
|
9899
|
+
* lo tiene el botón del chevron, DENTRO (`focus-within`), y el estado abierto lo escribe v3 en la
|
|
9900
|
+
* RAÍZ (`data-open="true"` en `div.autocomplete`), así que va por un `group` con nombre.
|
|
9901
|
+
*/
|
|
9902
|
+
const FOCUS_BORDER_GROUP_CLASS = "focus-within:border-primary-500! group-data-[open=true]/aura-select:border-primary-500!";
|
|
9903
|
+
/*
|
|
9904
|
+
* El buscador del desplegable. `.autocomplete__popover` de v3 ya lo coloca (`[data-slot=
|
|
9905
|
+
* "search-field"] { px-3 py-1 }`, `pt-2` en el popover, `overflow-hidden` + lista con su propio
|
|
9906
|
+
* scroll, de modo que el buscador no se va al desplazar la lista). Solo se retira el anillo de
|
|
9907
|
+
* foco de v3 (`status-focused-field` → `ring-2`), por lo mismo que en todos los campos de la
|
|
9908
|
+
* librería (`FIELD_FOCUS_CLASS`): el foco lo marca el borde, en primario.
|
|
9909
|
+
*/
|
|
9910
|
+
const SEARCH_GROUP_CLASS = "w-full ring-0 ring-offset-0 outline-0 focus-within:border-primary-500";
|
|
9911
|
+
/** El `ListBox` es el contenedor que se desplaza cuando el consumidor le fija alto. */
|
|
9912
|
+
const LISTBOX_SCROLL_CLASS = "overflow-y-auto";
|
|
9666
9913
|
// Medidas del estado vacío. Se toman las del ITEM de v3 (`@heroui/styles`,
|
|
9667
9914
|
// `dist/components/list-box-item.css:6`: `min-h-9 … px-2 py-1.5`) y no las del slot `emptyContent`
|
|
9668
9915
|
// de v2, y es una decisión medida: el literal de v2 era
|
|
@@ -9934,7 +10181,9 @@ size = "md", variant = "bordered", radius = "md",
|
|
|
9934
10181
|
//
|
|
9935
10182
|
// Por eso este componente sigue sin `SelectTranslations`: la corrección consiste precisamente en
|
|
9936
10183
|
// 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,
|
|
10184
|
+
placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
|
|
10185
|
+
// Alias nativo de `isRequired` (ver `SelectProps.required`).
|
|
10186
|
+
required = false, isDisabled = false,
|
|
9938
10187
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
9939
10188
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
9940
10189
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -9944,14 +10193,35 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
|
|
|
9944
10193
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
9945
10194
|
fullWidth = true, isLoading = false,
|
|
9946
10195
|
// Aceptada y descartada: ver `SelectProps.isVirtualized`.
|
|
9947
|
-
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps,
|
|
10196
|
+
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, onChange,
|
|
10197
|
+
// Aceptada y descartada: ver `SelectProps.labelPlacement`.
|
|
10198
|
+
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
10199
|
const generatedId = useId();
|
|
9949
10200
|
const selectId = id || generatedId;
|
|
9950
|
-
const requiredField = isRequired;
|
|
10201
|
+
const requiredField = isRequired || required;
|
|
9951
10202
|
const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
|
|
10203
|
+
// Raíz `Autocomplete` (buscador y/o ✕) o `Select` de siempre: ver el bloque «EL BUSCADOR Y LA ✕».
|
|
10204
|
+
const usesAutocompleteShell = isSearchable || isClearable;
|
|
10205
|
+
// Filtro de react-aria sin mayúsculas ni acentos. El hook va fuera de la condición (reglas de
|
|
10206
|
+
// hooks); sin `isSearchable` no se usa.
|
|
10207
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
|
10208
|
+
// Texto del buscador, en la fachada para saber qué mensaje poner en la lista vacía y para
|
|
10209
|
+
// vaciarlo al cerrar (abrir de nuevo = lista completa, que es lo que hacía el buscador manual
|
|
10210
|
+
// de `timezone-search-select` con su `onOpenChange`).
|
|
10211
|
+
const [searchText, setSearchText] = useState("");
|
|
9952
10212
|
// 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
|
-
|
|
10213
|
+
// que es el default de la fachada (el de v2 era el literal inglés "No items."). Con el buscador
|
|
10214
|
+
// escrito, la lista vacía significa «sin resultados», no «sin opciones».
|
|
10215
|
+
const emptyContent = isSearchable && searchText.trim() !== ""
|
|
10216
|
+
? t.noResults
|
|
10217
|
+
: (listboxProps?.emptyContent ?? t.emptyContent);
|
|
10218
|
+
const resolvedSearchPlaceholder = searchPlaceholder ?? t.searchPlaceholder;
|
|
10219
|
+
const handleOpenChange = (open) => {
|
|
10220
|
+
if (!open) {
|
|
10221
|
+
setSearchText("");
|
|
10222
|
+
}
|
|
10223
|
+
onOpenChange?.(open);
|
|
10224
|
+
};
|
|
9955
10225
|
// Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
|
|
9956
10226
|
// cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
|
|
9957
10227
|
// no se lee (el hook va fuera de la condición por las reglas de hooks).
|
|
@@ -9963,55 +10233,97 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
9963
10233
|
// En v2 el estado inválido MANDABA también con el campo enfocado
|
|
9964
10234
|
// (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
|
|
9965
10235
|
// el campo está en error: se queda el `danger`.
|
|
9966
|
-
isInvalid
|
|
10236
|
+
isInvalid
|
|
10237
|
+
? INVALID_VARIANT_CLASS[variant]
|
|
10238
|
+
: usesAutocompleteShell
|
|
10239
|
+
? FOCUS_BORDER_GROUP_CLASS
|
|
10240
|
+
: FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1, usesAutocompleteShell ? INDICATOR_BUTTON_OVERLAY_CLASS : "",
|
|
9967
10241
|
// Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
|
|
9968
10242
|
// sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
|
|
9969
10243
|
// responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
|
|
9970
10244
|
// con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
|
|
9971
10245
|
// cualquier utilidad.
|
|
9972
10246
|
"w-full", classNames?.trigger);
|
|
10247
|
+
// `id`/`textValue` desde la `key` de React: ver `utils/collection-items.ts` — sin esto un select
|
|
10248
|
+
// múltiple no pinta lo elegido y uno controlado arranca vacío. `withV2ItemContent` traduce
|
|
10249
|
+
// `description`/`startContent`/`endContent`/`classNames` del item y `title`/`showDivider` de la
|
|
10250
|
+
// sección a la anatomía de v3 (ver `_internal/v2-collection.tsx` para el orden), y
|
|
10251
|
+
// `withItemIndicator` monta el checkmark del seleccionado, que en v3 es un subcomponente aparte
|
|
10252
|
+
// (ver `_internal/with-item-indicator.tsx`, y ahí por qué va en este orden).
|
|
10253
|
+
const decorate = (nodes) => withItemIndicator(withV2ItemContent(withCollectionItemIds(nodes)));
|
|
10254
|
+
// Colección dinámica (`items` + función, la forma de `Profile.tsx`). La función se envuelve para
|
|
10255
|
+
// decorar lo que devuelva, y el paso de `withCollectionItemIds` importa aquí MÁS que en la forma
|
|
10256
|
+
// estática: react-aria toma la clave de una colección dinámica de `rendered.props.id ??
|
|
10257
|
+
// item.key ?? item.id`, nunca de la `key` de React. Sin reponer el `id` desde la `key`, las
|
|
10258
|
+
// opciones de `Profile.tsx` (objetos `{ value: "es", id: 1 }` con `<SelectItem key={value}>`)
|
|
10259
|
+
// se llamarían `1`/`2` y ni `defaultSelectedKeys={["es"]}` casaría ni `onSelectionChange`
|
|
10260
|
+
// devolvería la clave de la app.
|
|
10261
|
+
const collection = typeof children === "function"
|
|
10262
|
+
? (item) => decorate(children(item))
|
|
10263
|
+
: decorate(children);
|
|
10264
|
+
const listBox = (jsx(ListBox
|
|
10265
|
+
// v2 `items`: react-aria recorre la colección y llama a la función de `children`.
|
|
10266
|
+
, {
|
|
10267
|
+
// v2 `items`: react-aria recorre la colección y llama a la función de `children`.
|
|
10268
|
+
items: items,
|
|
10269
|
+
// v2: `classNames.listboxWrapper` y `scrollShadowProps.className` iban al mismo slot
|
|
10270
|
+
// (`cn(classNames.listboxWrapper, scrollShadowProps.className)`, `chunk-YFRCT47S.mjs:
|
|
10271
|
+
// 321-331`), en ese orden.
|
|
10272
|
+
className: classNames?.listboxWrapper || scrollShadowProps?.className
|
|
10273
|
+
? cn(LISTBOX_SCROLL_CLASS, classNames?.listboxWrapper, scrollShadowProps?.className)
|
|
10274
|
+
: undefined, style: scrollShadowProps?.style,
|
|
10275
|
+
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
10276
|
+
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
10277
|
+
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve el
|
|
10278
|
+
// resultado en un `<div role="option" style="display:contents">`
|
|
10279
|
+
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja que se
|
|
10280
|
+
// ve es la nuestra.
|
|
10281
|
+
renderEmptyState: () => (jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: collection }));
|
|
10282
|
+
// Las piezas del disparador son las mismas en las dos raíces; solo cambia de qué compound salen
|
|
10283
|
+
// (ver el bloque «EL BUSCADOR Y LA ✕»). `Autocomplete.Value` y `Select.Value` son el mismo
|
|
10284
|
+
// `SelectValue` de react-aria con otra clase base, así que reciben las mismas props.
|
|
10285
|
+
const ValuePart = usesAutocompleteShell
|
|
10286
|
+
? Autocomplete.Value
|
|
10287
|
+
: Select$1.Value;
|
|
10288
|
+
const triggerContent = (jsxs(Fragment$1, { children: [startContent != null && startContent !== false && (jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsx(ValuePart, { className: cn(SIZE_TEXT_CLASS$1[size], isMultiline
|
|
10289
|
+
? MULTILINE_VALUE_ROOM_CLASS
|
|
10290
|
+
: isClearable
|
|
10291
|
+
? VALUE_ROOM_CLEARABLE_CLASS
|
|
10292
|
+
: VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
|
|
10293
|
+
? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
|
|
10294
|
+
// con selección, la función con los items en la forma de v2. El nodo de la
|
|
10295
|
+
// colección de v3 trae los mismos campos que el de v2, así que el mapeo es
|
|
10296
|
+
// 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
|
|
10297
|
+
({ state, isPlaceholder, defaultChildren }) => {
|
|
10298
|
+
if (isPlaceholder) {
|
|
10299
|
+
return defaultChildren;
|
|
10300
|
+
}
|
|
10301
|
+
const items = state.selectedItems.map((item) => ({
|
|
10302
|
+
key: item.key,
|
|
10303
|
+
data: item.value,
|
|
10304
|
+
type: item.type,
|
|
10305
|
+
props: item.props,
|
|
10306
|
+
textValue: item.textValue,
|
|
10307
|
+
rendered: item.rendered,
|
|
10308
|
+
"aria-label": item["aria-label"],
|
|
10309
|
+
}));
|
|
10310
|
+
return renderValue(items);
|
|
10311
|
+
}
|
|
10312
|
+
: undefined }), isClearable && (jsx(Autocomplete.ClearButton, { "aria-label": t.clearButtonAriaLabel, className: CLEAR_BUTTON_CLASS$1 })), usesAutocompleteShell ? (
|
|
10313
|
+
/* En el `Autocomplete` el chevron es el BOTÓN que recibe el foco (ver el bloque
|
|
10314
|
+
«EL BUSCADOR Y LA ✕»), así que el spinner va DENTRO de él en vez de sustituirlo:
|
|
10315
|
+
sin el botón el campo no se alcanzaría con el teclado. `Autocomplete.Indicator` clona
|
|
10316
|
+
su hijo con la clase del indicador (`absolute inset-y-0 end-2 my-auto`), así que el
|
|
10317
|
+
spinner cae en el sitio del chevron sin clase propia. */
|
|
10318
|
+
jsx(Autocomplete.Indicator, { className: "end-3", children: isLoading ? (jsx(Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current" })) : undefined })) : isLoading ? (jsx(Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
|
|
10319
|
+
/* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
|
|
10320
|
+
`selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
|
|
10321
|
+
acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
|
|
10322
|
+
de más. */
|
|
10323
|
+
jsx(Select$1.Indicator, { className: "end-3" }))] }));
|
|
9973
10324
|
const inner = (jsxs(Fragment$1, { children: [label && (jsx(Label, { className: cn(LABEL_CLASS$1, hasHelper
|
|
9974
10325
|
? LABEL_OFFSET_HELPER_CLASS[size]
|
|
9975
|
-
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })),
|
|
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 ? (jsx(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
|
-
jsx(Select$1.Indicator, { className: "end-3" }))] }), description && !isInvalid && (jsx(Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), jsx(Select$1.Popover, { className: classNames?.popoverContent, children: jsx(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: () => (jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: withItemIndicator(withV2ItemContent(withCollectionItemIds(children))) }) }), isInvalid && errorMessage && (jsx(FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
|
|
10326
|
+
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), usesAutocompleteShell ? (jsx(Autocomplete.Trigger, { className: triggerClassName, children: triggerContent })) : (jsx(Select$1.Trigger, { className: triggerClassName, children: triggerContent })), description && !isInvalid && (jsx(Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), usesAutocompleteShell ? (jsx(Autocomplete.Popover, { className: classNames?.popoverContent, children: isSearchable ? (jsxs(Autocomplete.Filter, { filter: contains, inputValue: searchText, onInputChange: setSearchText, children: [jsx(SearchField, { autoFocus: true, "aria-label": resolvedSearchPlaceholder, fullWidth: true, children: jsxs(SearchField.Group, { className: SEARCH_GROUP_CLASS, children: [jsx(SearchField.SearchIcon, {}), jsx(SearchField.Input, { placeholder: resolvedSearchPlaceholder })] }) }), listBox] })) : (listBox) })) : (jsx(Select$1.Popover, { className: classNames?.popoverContent, children: listBox })), isInvalid && errorMessage && (jsx(FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
|
|
10015
10327
|
const commonProps = {
|
|
10016
10328
|
id: selectId,
|
|
10017
10329
|
name,
|
|
@@ -10037,7 +10349,14 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10037
10349
|
isDisabled,
|
|
10038
10350
|
isOpen,
|
|
10039
10351
|
defaultOpen,
|
|
10040
|
-
onOpenChange,
|
|
10352
|
+
onOpenChange: handleOpenChange,
|
|
10353
|
+
// v2 `closeOnSelect` → `shouldCloseOnSelect` de react-stately, que ya trae el mismo default
|
|
10354
|
+
// por modo (`useSelectState.mjs:23`: cierra en `single`, se queda abierto en `multiple`).
|
|
10355
|
+
// `undefined` deja ese default intacto.
|
|
10356
|
+
...(closeOnSelect === undefined
|
|
10357
|
+
? {}
|
|
10358
|
+
: { shouldCloseOnSelect: closeOnSelect }),
|
|
10359
|
+
onClear,
|
|
10041
10360
|
fullWidth,
|
|
10042
10361
|
// `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
|
|
10043
10362
|
// de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
|
|
@@ -10050,7 +10369,13 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10050
10369
|
className: cn(FIELD_STACK_CLASS$1[size],
|
|
10051
10370
|
// `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
|
|
10052
10371
|
// (ver `ROOT_MIN_WIDTH_CLASS`).
|
|
10053
|
-
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
|
|
10372
|
+
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
|
|
10373
|
+
// Grupo con nombre para el borde de foco del disparador `Group` (ver
|
|
10374
|
+
// `FOCUS_BORDER_GROUP_CLASS`). Sin la raíz `Autocomplete` no hace falta.
|
|
10375
|
+
usesAutocompleteShell ? "group/aura-select" : "",
|
|
10376
|
+
// v2 `classNames.base`: la raíz, igual que `className`, y por eso delante de él (en v2
|
|
10377
|
+
// los dos acababan en `slots.base({ class: cn(classNames?.base, className) })`).
|
|
10378
|
+
classNames?.base, className),
|
|
10054
10379
|
};
|
|
10055
10380
|
if (selectionMode === "multiple") {
|
|
10056
10381
|
// `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
|
|
@@ -10058,7 +10383,12 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10058
10383
|
// la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
|
|
10059
10384
|
// no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
|
|
10060
10385
|
const ownsValue = disallowEmptySelection && selectedKeys === undefined;
|
|
10061
|
-
|
|
10386
|
+
const multipleProps = {
|
|
10387
|
+
...commonProps,
|
|
10388
|
+
selectionMode: "multiple",
|
|
10389
|
+
value: ownsValue ? ownMultiple : toArray(selectedKeys),
|
|
10390
|
+
defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys),
|
|
10391
|
+
onChange: (keys) => {
|
|
10062
10392
|
if (disallowEmptySelection && keys.length === 0) {
|
|
10063
10393
|
return;
|
|
10064
10394
|
}
|
|
@@ -10066,7 +10396,10 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10066
10396
|
setOwnMultiple(keys);
|
|
10067
10397
|
}
|
|
10068
10398
|
onSelectionChange?.(new Set(keys));
|
|
10069
|
-
|
|
10399
|
+
onChange?.({ target: { name, value: keys.join(",") } });
|
|
10400
|
+
},
|
|
10401
|
+
};
|
|
10402
|
+
return usesAutocompleteShell ? (jsx(Autocomplete, { ...multipleProps, children: inner })) : (jsx(Select$1, { ...multipleProps, children: inner }));
|
|
10070
10403
|
}
|
|
10071
10404
|
// `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
|
|
10072
10405
|
// cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
|
|
@@ -10081,7 +10414,17 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
|
|
|
10081
10414
|
: defaultSelectedKeys === "all"
|
|
10082
10415
|
? null
|
|
10083
10416
|
: (Array.from(defaultSelectedKeys)[0] ?? null);
|
|
10084
|
-
|
|
10417
|
+
const singleProps = {
|
|
10418
|
+
...commonProps,
|
|
10419
|
+
value: singleValue,
|
|
10420
|
+
defaultValue: singleDefault,
|
|
10421
|
+
onChange: (key) => {
|
|
10422
|
+
onSelectionChange?.(key == null ? new Set() : new Set([key]));
|
|
10423
|
+
// v2 llamaba a `onChange` después de `onSelectionChange` (ver `SelectChangeEvent`).
|
|
10424
|
+
onChange?.({ target: { name, value: key == null ? "" : String(key) } });
|
|
10425
|
+
},
|
|
10426
|
+
};
|
|
10427
|
+
return usesAutocompleteShell ? (jsx(Autocomplete, { ...singleProps, children: inner })) : (jsx(Select$1, { ...singleProps, children: inner }));
|
|
10085
10428
|
};
|
|
10086
10429
|
|
|
10087
10430
|
const defaultTranslations$4 = {
|
|
@@ -11122,6 +11465,28 @@ const VARIANT_CLASS = {
|
|
|
11122
11465
|
// placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
|
|
11123
11466
|
const INVALID_LABEL_CLASS = "text-danger";
|
|
11124
11467
|
const INVALID_TEXT_CLASS = "text-danger";
|
|
11468
|
+
// ── `isClearable` / `onClear` / `classNames.clearButton` (API v2, repuesta) ──────────────────
|
|
11469
|
+
//
|
|
11470
|
+
// El `Textarea` de v2 ES el `Input` con `isMultiline` (mismo `useInput`), así que heredaba su botón
|
|
11471
|
+
// de limpiar: `isClearable = !!onClear || originalProps.isClearable` y `handleClear` pone el valor
|
|
11472
|
+
// a "" (lo que dispara `onValueChange("")`), llama a `onClear` y devuelve el foco
|
|
11473
|
+
// (`@heroui/input@2.4.33`, `dist/chunk-KQW2TNLT.mjs`); Escape con valor también limpia. Uso real:
|
|
11474
|
+
// `campaigns/**/AudienceReachStep.tsx` en beweossmbs (`isClearable` + `onClear` +
|
|
11475
|
+
// `classNames.clearButton`), el único de los 33 `Textarea` de la app que lo pide.
|
|
11476
|
+
//
|
|
11477
|
+
// Posición, literal del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/input.js`):
|
|
11478
|
+
// · `isMultiline.clearButton` → "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10": la X va
|
|
11479
|
+
// ARRIBA a la derecha, no centrada como en el campo de una línea (aquí `end-2` = right/left
|
|
11480
|
+
// según la dirección, lo mismo que el par `right-2 rtl:left-2`);
|
|
11481
|
+
// · `isClearable.input` → "pe-6": el texto deja sitio a la X aunque todavía no se vea,
|
|
11482
|
+
// para que no salte al aparecer.
|
|
11483
|
+
// Lo relativo en v2 era el `inputWrapper`, que aquí no existe (el `<textarea>` es la caja), así que
|
|
11484
|
+
// se añade un envoltorio `relative` SOLO cuando el campo es limpiable: sin la prop el DOM no cambia.
|
|
11485
|
+
// El glifo es el `CloseButton` de v3, el mismo que ya usa la fachada `Input` (§9.1: verse como v3),
|
|
11486
|
+
// y `classNames.clearButton` va el último para que las clases del consumidor ganen.
|
|
11487
|
+
const CLEAR_WRAPPER_CLASS = "relative w-full";
|
|
11488
|
+
const CLEAR_BUTTON_CLASS = "absolute top-2 end-2 z-10 h-4 w-4 min-h-0 p-0.5";
|
|
11489
|
+
const CLEARABLE_FIELD_CLASS = "pe-6";
|
|
11125
11490
|
// ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
|
|
11126
11491
|
//
|
|
11127
11492
|
// Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
|
|
@@ -11164,7 +11529,10 @@ minRows, rows,
|
|
|
11164
11529
|
// llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
|
|
11165
11530
|
maxRows = DEFAULT_MAX_ROWS,
|
|
11166
11531
|
// Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
|
|
11167
|
-
disableAutosize = false, placeholder,
|
|
11532
|
+
disableAutosize = false, placeholder, isClearable: isClearableProp = false, onClear,
|
|
11533
|
+
// Sin efecto (ver `TextareaProps.labelPlacement`): fuera del spread para que no llegue
|
|
11534
|
+
// al `<textarea>` como atributo desconocido.
|
|
11535
|
+
labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
11168
11536
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
11169
11537
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
11170
11538
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -11172,7 +11540,7 @@ disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isD
|
|
|
11172
11540
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
11173
11541
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
11174
11542
|
// 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) => {
|
|
11543
|
+
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, onKeyDown, ...rest }, ref) => {
|
|
11176
11544
|
const generatedId = useId();
|
|
11177
11545
|
const textareaId = id || generatedId;
|
|
11178
11546
|
const requestedRows = resolveRows(minRows, rows);
|
|
@@ -11209,6 +11577,46 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11209
11577
|
minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
|
|
11210
11578
|
...style,
|
|
11211
11579
|
};
|
|
11580
|
+
// v2: `isClearable = !!onClear || originalProps.isClearable`.
|
|
11581
|
+
const isClearable = isClearableProp || Boolean(onClear);
|
|
11582
|
+
// Limpiar en modo NO controlado tiene que poder vaciar el valor, y el estado interno del
|
|
11583
|
+
// `TextField` no se puede escribir desde fuera: en ese caso (y solo en ese) la fachada lleva
|
|
11584
|
+
// el valor en su propio estado, igual que `Input.tsx`. Sin `isClearable` nada cambia: el
|
|
11585
|
+
// valor no controlado sigue siendo del `TextField` (y su `reset`, de `useFormReset`).
|
|
11586
|
+
const isControlled = value !== undefined;
|
|
11587
|
+
const ownsValue = isClearable && !isControlled;
|
|
11588
|
+
const [ownValue, setOwnValue] = useState(defaultValue ?? "");
|
|
11589
|
+
const currentValue = isControlled ? value : ownsValue ? ownValue : "";
|
|
11590
|
+
const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
|
|
11591
|
+
// Orden de v2 (`handleClear`): el valor a "" —que en v2 disparaba `onValueChange`—, luego
|
|
11592
|
+
// `onClear`. El `onChange` recibe un evento de valor vacío, como en `Input.tsx`, para que un
|
|
11593
|
+
// consumidor que solo escucha `onChange` (p. ej. `field.onChange` de react-hook-form) se entere.
|
|
11594
|
+
const clearValue = () => {
|
|
11595
|
+
if (ownsValue) {
|
|
11596
|
+
setOwnValue("");
|
|
11597
|
+
}
|
|
11598
|
+
onValueChange?.("");
|
|
11599
|
+
if (onChange) {
|
|
11600
|
+
const emptyEvent = {
|
|
11601
|
+
target: { value: "" },
|
|
11602
|
+
currentTarget: { value: "" },
|
|
11603
|
+
};
|
|
11604
|
+
onChange(emptyEvent);
|
|
11605
|
+
}
|
|
11606
|
+
onClear?.();
|
|
11607
|
+
};
|
|
11608
|
+
const handleClear = () => {
|
|
11609
|
+
clearValue();
|
|
11610
|
+
innerRef.current?.focus();
|
|
11611
|
+
};
|
|
11612
|
+
// Escape limpia, igual que v2 (con valor, limpiable y no de solo lectura). El `onKeyDown` del
|
|
11613
|
+
// consumidor se llama primero.
|
|
11614
|
+
const handleKeyDown = (event) => {
|
|
11615
|
+
onKeyDown?.(event);
|
|
11616
|
+
if (event.key === "Escape" && showClear) {
|
|
11617
|
+
clearValue();
|
|
11618
|
+
}
|
|
11619
|
+
};
|
|
11212
11620
|
// El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
|
|
11213
11621
|
// `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
|
|
11214
11622
|
// el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
|
|
@@ -11217,6 +11625,10 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11217
11625
|
onChange?.(event);
|
|
11218
11626
|
onValueChange?.(event.target.value);
|
|
11219
11627
|
};
|
|
11628
|
+
const field = (jsx(TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, onKeyDown: handleKeyDown, className: cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
|
|
11629
|
+
// Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
|
|
11630
|
+
// y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
|
|
11631
|
+
"w-full", isClearable ? CLEARABLE_FIELD_CLASS : "", classNames?.input ?? classNames?.inputWrapper) }));
|
|
11220
11632
|
return (
|
|
11221
11633
|
// ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
|
|
11222
11634
|
//
|
|
@@ -11231,13 +11643,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
11231
11643
|
// pero el estado interno del `TextField` seguía en "".
|
|
11232
11644
|
// Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
|
|
11233
11645
|
// es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
|
|
11234
|
-
jsxs(TextField, { id: textareaId, value:
|
|
11646
|
+
jsxs(TextField, { id: textareaId, value: ownsValue ? ownValue : value, defaultValue: ownsValue ? undefined : defaultValue,
|
|
11647
|
+
// Solo cuando la fachada es dueña del valor (limpiable y no controlado).
|
|
11648
|
+
onChange: ownsValue ? setOwnValue : undefined, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
|
|
11235
11649
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
11236
11650
|
// v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
|
|
11237
|
-
className), children: [label && (jsx(Label, { className: cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })),
|
|
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 && (jsx(Description, { className: cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsx(FieldError, { className: cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
|
|
11651
|
+
className), children: [label && (jsx(Label, { className: cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), isClearable ? (jsxs("div", { className: CLEAR_WRAPPER_CLASS, children: [field, showClear && (jsx(CloseButton, { "aria-label": "Limpiar", className: cn(CLEAR_BUTTON_CLASS, classNames?.clearButton), onPress: handleClear }))] })) : (field), description && !isInvalid && (jsx(Description, { className: cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsx(FieldError, { className: cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
|
|
11241
11652
|
});
|
|
11242
11653
|
Textarea.displayName = "Textarea";
|
|
11243
11654
|
|
|
@@ -12990,8 +13401,8 @@ const DrawerFiltersFooter = ({ onClearFilters, onCancel, onApplyFilters, transla
|
|
|
12990
13401
|
/**
|
|
12991
13402
|
* Header of the DrawerFilters with title and description.
|
|
12992
13403
|
*/
|
|
12993
|
-
const DrawerFiltersHeader = ({ title, description, }) => {
|
|
12994
|
-
return (jsxs("div", { className: "px-6 pt-10", children: [jsx(H2, { className: "text-xl font-semibold text-foreground mb-2", children: title }), jsx(P, { className: "text-default-500", children: description })] }));
|
|
13404
|
+
const DrawerFiltersHeader = ({ titleId, title, description, }) => {
|
|
13405
|
+
return (jsxs("div", { className: "px-6 pt-10", children: [jsx(H2, { id: titleId, className: "text-xl font-semibold text-foreground mb-2", children: title }), jsx(P, { className: "text-default-500", children: description })] }));
|
|
12995
13406
|
};
|
|
12996
13407
|
|
|
12997
13408
|
const NO_YEAR_CLASS = "[&_[data-type='year']]:hidden [&_[data-type='literal']:has(+[data-type='year'])]:hidden";
|
|
@@ -13670,6 +14081,11 @@ const NumberFilter = ({ filter, value, onChange }) => {
|
|
|
13670
14081
|
*/
|
|
13671
14082
|
const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFilters, onCancel, className, translations, initialValues, }) => {
|
|
13672
14083
|
const [filterValues, setFilterValues] = useState(initialValues ?? {});
|
|
14084
|
+
// Nombre accesible del panel: su título. Con `Drawer.Root`, el `DialogTrigger` nombraba el
|
|
14085
|
+
// diálogo con el id de su disparador (que aquí no existe); sin root nadie lo nombra y
|
|
14086
|
+
// react-aria avisa en cada apertura («A dialog must have a title for accessibility», medido en
|
|
14087
|
+
// jsdom). Mismo arreglo que `headerId` en la fachada `Drawer`.
|
|
14088
|
+
const titleId = useId();
|
|
13673
14089
|
// Handle changes in individual filters
|
|
13674
14090
|
const handleFilterChange = useCallback((filterKey, value) => {
|
|
13675
14091
|
setFilterValues((prev) => ({
|
|
@@ -13782,9 +14198,16 @@ const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFi
|
|
|
13782
14198
|
onApplyFilters(processedFilters);
|
|
13783
14199
|
onOpenChange(false);
|
|
13784
14200
|
}, [filterValues, config.data, onApplyFilters, onOpenChange]);
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
14201
|
+
// Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito, igual que la fachada
|
|
14202
|
+
// `Drawer` (`src/components/drawer/Drawer.tsx`): aquí no hay disparador —lo abre el `isOpen`
|
|
14203
|
+
// de la app— y un `DialogTrigger` sin hijo pulsable avisa en consola en CADA montaje, abierto o
|
|
14204
|
+
// cerrado («A PressResponder was rendered without a pressable child», medido en jsdom y por
|
|
14205
|
+
// beweossmbs con la .140). `Drawer.Backdrop` (`ModalOverlay`) ya crea su propio estado de
|
|
14206
|
+
// overlay con `isOpen`/`onOpenChange` y calcula todos los slots de `drawerVariants()`, así que
|
|
14207
|
+
// el root no aportaba nada.
|
|
14208
|
+
return (jsx(Drawer$1.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsx(Drawer$1.Content, { placement: "right", className: cn("drawer-filters", className), children: jsxs(Drawer$1.Dialog, { "aria-labelledby": titleId, className: "w-full p-0 sm:max-w-md", children: [jsx(Drawer$1.CloseTrigger, {}), jsx(DrawerFiltersHeader, { titleId: titleId, title: config.title, description: config.description }), jsx(Drawer$1.Body, { className: "px-6 py-10", children: jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
|
|
14209
|
+
return (jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
|
|
14210
|
+
}) }) }), jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }));
|
|
13788
14211
|
};
|
|
13789
14212
|
DrawerFilters.displayName = "DrawerFilters";
|
|
13790
14213
|
|
|
@@ -16929,21 +17352,66 @@ function hasIndicator(children) {
|
|
|
16929
17352
|
return node.type === Fragment && hasIndicator(node.props.children);
|
|
16930
17353
|
});
|
|
16931
17354
|
}
|
|
17355
|
+
// ---------------------------------------------------------------------------------------------
|
|
17356
|
+
// `size` de v2 → medidas de pestaña de v2
|
|
17357
|
+
// ---------------------------------------------------------------------------------------------
|
|
17358
|
+
/**
|
|
17359
|
+
* Medidas de pestaña por `size`, **copiadas del oráculo** `@heroui/theme@2.4.15`
|
|
17360
|
+
* `dist/components/tabs.js` (slot `tab`, `px-3` en la base y `size.{sm,md,lg}.tab` para la altura y
|
|
17361
|
+
* el cuerpo), traducidas a clases de Tailwind 4 con el MISMO valor. Las `text-tiny` /
|
|
17362
|
+
* `text-small` / `text-medium` de v2 son del plugin `heroui()` y en TW4 no existen
|
|
17363
|
+
* (`default-layout.js:32-40`):
|
|
17364
|
+
*
|
|
17365
|
+
* | size | v2 | aquí | cuerpo / interlineado |
|
|
17366
|
+
* |------|------------------------|----------------|-----------------------|
|
|
17367
|
+
* | sm | `h-7 text-tiny` + px-3 | `h-7 px-3 text-xs` | 0.75rem / 1rem |
|
|
17368
|
+
* | md | `h-8 text-small` + px-3 | `h-8 px-3 text-sm` | 0.875rem / 1.25rem |
|
|
17369
|
+
* | lg | `h-9 text-medium` + px-3 | `h-9 px-3 text-base` | 1rem / 1.5rem |
|
|
17370
|
+
*
|
|
17371
|
+
* Por qué se mapea, cuando `radius` y `color` se ignoran (§9.1): `size` no es estética, es
|
|
17372
|
+
* TAMAÑO, y descartarlo provoca un desbordamiento medido. linda-business embebido en el host v1 con
|
|
17373
|
+
* la `.140`: con `size="sm"` descartado, el texto pasaba de 12 px a 14 px y cada pestaña salía de 6
|
|
17374
|
+
* a 17 px más ancha. El carril medía 352 px en una columna de 287 px y el chevron del scroller
|
|
17375
|
+
* tapaba la última pestaña. smbs tiene 10 `size="sm"`.
|
|
17376
|
+
*
|
|
17377
|
+
* Lo que NO se copia: el peso (v3 pone `font-medium`, v2 no ponía nada) y el radio (`rounded-small`),
|
|
17378
|
+
* que siguen como v3 por §9.1. Tampoco el `py-1` de v2: con altura fija y `items-center` no
|
|
17379
|
+
* cambia nada.
|
|
17380
|
+
*
|
|
17381
|
+
* Las clases van ANTES de `classNames.tab` y del `className` del `Tab` en un `cn`
|
|
17382
|
+
* (tailwind-merge): lo de la app gana. El `h-7 px-2` de linda-business sigue mandando sobre el
|
|
17383
|
+
* `px-3`. Clases literales en el fuente (no construidas), para que Tailwind las genere.
|
|
17384
|
+
*/
|
|
17385
|
+
const TAB_SIZE_CLASS = {
|
|
17386
|
+
sm: "h-7 px-3 text-xs",
|
|
17387
|
+
md: "h-8 px-3 text-sm",
|
|
17388
|
+
lg: "h-9 px-3 text-base",
|
|
17389
|
+
};
|
|
17390
|
+
/**
|
|
17391
|
+
* El `size` del root llega a los `Tabs.Tab` de la forma v3 por contexto, porque esas pestañas las
|
|
17392
|
+
* escribe la app dentro de su `Tabs.List` y la fachada no las recorre. Sin `size` el valor es
|
|
17393
|
+
* `undefined` y la pestaña sale byte a byte como antes.
|
|
17394
|
+
*/
|
|
17395
|
+
const TabSizeContext = createContext(undefined);
|
|
16932
17396
|
const TabFacade = ({ children: ownChildren, title,
|
|
16933
17397
|
// `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
|
|
16934
17398
|
titleValue: _titleValue, ...props }) => {
|
|
16935
17399
|
// En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
|
|
16936
17400
|
// `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
|
|
16937
17401
|
const children = ownChildren ?? title;
|
|
17402
|
+
const sizeClass = useContext(TabSizeContext);
|
|
17403
|
+
const tabProps = sizeClass
|
|
17404
|
+
? { ...props, className: cn(sizeClass, props.className) }
|
|
17405
|
+
: props;
|
|
16938
17406
|
// `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
|
|
16939
17407
|
// puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
|
|
16940
17408
|
// hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
|
|
16941
17409
|
// indicador él (es la vía de escape de este caso). Exposición hoy: 0 usos en los 6 repos.
|
|
16942
17410
|
if (typeof children === "function") {
|
|
16943
|
-
return jsx(Tabs$1.Tab, { ...
|
|
17411
|
+
return jsx(Tabs$1.Tab, { ...tabProps, children: children });
|
|
16944
17412
|
}
|
|
16945
17413
|
if (hasIndicator(children)) {
|
|
16946
|
-
return jsx(Tabs$1.Tab, { ...
|
|
17414
|
+
return jsx(Tabs$1.Tab, { ...tabProps, children: children });
|
|
16947
17415
|
}
|
|
16948
17416
|
// El indicador va PRIMERO por legibilidad del marcado, no por efecto: es `position:absolute`
|
|
16949
17417
|
// con `z-index:-1`, o sea fuera del flujo y detrás del texto, así que el orden no cambia el
|
|
@@ -16953,7 +17421,7 @@ titleValue: _titleValue, ...props }) => {
|
|
|
16953
17421
|
// `{children}` se pasa SIN normalizar (nada de `Children.toArray`) para no reescribir las
|
|
16954
17422
|
// keys del consumidor: hacerlo cambiaría la reconciliación de una lista de pestañas
|
|
16955
17423
|
// condicional. Verificado en el navegador que la anidación no produce aviso de key.
|
|
16956
|
-
return (jsxs(Tabs$1.Tab, { ...
|
|
17424
|
+
return (jsxs(Tabs$1.Tab, { ...tabProps, children: [jsx(Tabs$1.Indicator, {}), children] }));
|
|
16957
17425
|
};
|
|
16958
17426
|
TabFacade.displayName = "Aurora.Tab";
|
|
16959
17427
|
/**
|
|
@@ -17064,8 +17532,10 @@ function hasPanelContent(children) {
|
|
|
17064
17532
|
}
|
|
17065
17533
|
const TabsFacade = (allProps) => {
|
|
17066
17534
|
const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
|
|
17535
|
+
// v2: medidas de pestaña (ver `TAB_SIZE_CLASS`).
|
|
17536
|
+
size,
|
|
17067
17537
|
// Sin equivalente en v3: se consumen para que no lleguen al DOM (ver README, «Props v2»).
|
|
17068
|
-
|
|
17538
|
+
radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
|
|
17069
17539
|
const v3Variant = toV3Variant(variant);
|
|
17070
17540
|
const v3Orientation = orientation ??
|
|
17071
17541
|
(isVertical || placement === "start" || placement === "end"
|
|
@@ -17126,7 +17596,7 @@ const TabsFacade = (allProps) => {
|
|
|
17126
17596
|
// `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
|
|
17127
17597
|
// neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
|
|
17128
17598
|
const orientationAttr = v3Orientation ?? "horizontal";
|
|
17129
|
-
return (jsxs(Tabs$1, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsx("div", { className: cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsx(Tabs$1.ListContainer, { className: cn(fullWidth && "w-full"), children: jsx(Tabs$1.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => (createElement(Tabs$1.Tab, { ...tabProps, className: cn(classNames?.tab, tabClassName), id: id, key: id },
|
|
17599
|
+
return (jsxs(Tabs$1, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsx("div", { className: cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsx(Tabs$1.ListContainer, { className: cn(fullWidth && "w-full"), children: jsx(Tabs$1.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => (createElement(Tabs$1.Tab, { ...tabProps, className: cn(size && TAB_SIZE_CLASS[size], classNames?.tab, tabClassName), id: id, key: id },
|
|
17130
17600
|
jsx(Tabs$1.Indicator, { className: cn(classNames?.cursor) }),
|
|
17131
17601
|
classNames?.tabContent ? (jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
|
|
17132
17602
|
.filter(({ panel }) => hasPanelContent(panel))
|
|
@@ -17145,7 +17615,9 @@ const TabsFacade = (allProps) => {
|
|
|
17145
17615
|
orientation: v3Orientation,
|
|
17146
17616
|
variant: v3Variant,
|
|
17147
17617
|
};
|
|
17148
|
-
|
|
17618
|
+
const v3Tabs = jsx(TabsV3, { ...v3Props, children: v3Children });
|
|
17619
|
+
// Sin `size` no se monta el provider: el árbol queda idéntico al de antes.
|
|
17620
|
+
return size ? (jsx(TabSizeContext.Provider, { value: TAB_SIZE_CLASS[size], children: v3Tabs })) : (v3Tabs);
|
|
17149
17621
|
};
|
|
17150
17622
|
const TabsV3 = ({ children, ...props }) => {
|
|
17151
17623
|
const nodes = Children.toArray(children);
|