@mediquo/medicore 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/components/advisor-contact/advisor-contact.js +1 -1
  2. package/dist/components/advisor-contact/advisor-contact.js.map +1 -1
  3. package/dist/components/bottom-bar/bottom-bar.js +1 -1
  4. package/dist/components/bottom-bar/bottom-bar.js.map +1 -1
  5. package/dist/components/breadcrumb/breadcrumb.js +1 -1
  6. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  7. package/dist/components/button/button.d.ts +2 -1
  8. package/dist/components/button/button.js +8 -4
  9. package/dist/components/button/button.js.map +1 -1
  10. package/dist/components/card/card.d.ts +2 -1
  11. package/dist/components/card/card.js +10 -3
  12. package/dist/components/card/card.js.map +1 -1
  13. package/dist/components/card-article/card-article.js +6 -2
  14. package/dist/components/card-article/card-article.js.map +1 -1
  15. package/dist/components/card-professional-mini/card-professional-mini.js +1 -1
  16. package/dist/components/card-professional-mini/card-professional-mini.js.map +1 -1
  17. package/dist/components/card-program/card-program.js +38 -5
  18. package/dist/components/card-program/card-program.js.map +1 -1
  19. package/dist/components/chat/chat-composer.js +1 -1
  20. package/dist/components/chat/chat-composer.js.map +1 -1
  21. package/dist/components/chat/chat-conversation-row.js +1 -1
  22. package/dist/components/chat/chat-conversation-row.js.map +1 -1
  23. package/dist/components/checkbox/checkbox.js +2 -1
  24. package/dist/components/checkbox/checkbox.js.map +1 -1
  25. package/dist/components/chip/chip.js +2 -2
  26. package/dist/components/chip/chip.js.map +1 -1
  27. package/dist/components/day-cell/day-cell.js +2 -1
  28. package/dist/components/day-cell/day-cell.js.map +1 -1
  29. package/dist/components/hover-arrow/hover-arrow.d.ts +4 -0
  30. package/dist/components/hover-arrow/hover-arrow.js +18 -0
  31. package/dist/components/hover-arrow/hover-arrow.js.map +1 -0
  32. package/dist/components/hover-arrow/index.d.ts +1 -0
  33. package/dist/components/input/control-styles.d.ts +2 -2
  34. package/dist/components/input/control-styles.js +2 -2
  35. package/dist/components/input/control-styles.js.map +1 -1
  36. package/dist/components/link/link.d.ts +2 -1
  37. package/dist/components/link/link.js +8 -5
  38. package/dist/components/link/link.js.map +1 -1
  39. package/dist/components/list-row/list-row.js +1 -1
  40. package/dist/components/list-row/list-row.js.map +1 -1
  41. package/dist/components/menu/menu.js +1 -1
  42. package/dist/components/menu/menu.js.map +1 -1
  43. package/dist/components/modal/modal.js +1 -1
  44. package/dist/components/modal/modal.js.map +1 -1
  45. package/dist/components/nav-item/nav-item.js +2 -2
  46. package/dist/components/nav-item/nav-item.js.map +1 -1
  47. package/dist/components/otp-input/otp-input.js +3 -2
  48. package/dist/components/otp-input/otp-input.js.map +1 -1
  49. package/dist/components/radio/radio.js +2 -1
  50. package/dist/components/radio/radio.js.map +1 -1
  51. package/dist/components/segmented-control/segmented-control.js +1 -1
  52. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  53. package/dist/components/select/select.js +1 -1
  54. package/dist/components/select/select.js.map +1 -1
  55. package/dist/components/selection-card/selection-card.js +2 -1
  56. package/dist/components/selection-card/selection-card.js.map +1 -1
  57. package/dist/components/switch/switch.js +3 -2
  58. package/dist/components/switch/switch.js.map +1 -1
  59. package/dist/components/table/table.js +3 -3
  60. package/dist/components/table/table.js.map +1 -1
  61. package/dist/components/tabs/tabs.js +3 -3
  62. package/dist/components/tabs/tabs.js.map +1 -1
  63. package/dist/components/time-slot/time-slot.js +2 -1
  64. package/dist/components/time-slot/time-slot.js.map +1 -1
  65. package/dist/components/toast/toast.js +1 -1
  66. package/dist/components/toast/toast.js.map +1 -1
  67. package/dist/components/tooltip/tooltip.js +1 -1
  68. package/dist/components/tooltip/tooltip.js.map +1 -1
  69. package/dist/components/video/video-control-button.js +1 -1
  70. package/dist/components/video/video-control-button.js.map +1 -1
  71. package/dist/icons/create-icon.d.ts +1 -0
  72. package/dist/icons/create-icon.js +7 -1
  73. package/dist/icons/create-icon.js.map +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.js +2 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/styles/tokens.css +3 -0
  78. package/dist/styles/utilities.css +129 -0
  79. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"modal.js","sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["import { Dialog } from \"@base-ui/react/dialog\";\nimport { Drawer } from \"@base-ui/react/drawer\";\nimport {\n type ComponentProps,\n type CSSProperties,\n createContext,\n type ReactNode,\n type Ref,\n type RefObject,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { IconButton } from \"../icon-button\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"./use-media-query\";\n\nexport type ModalLayout = \"center\" | \"sheet\";\n\nexport type ModalChangeEventDetails =\n | Dialog.Root.ChangeEventDetails\n | Drawer.Root.ChangeEventDetails;\n\nexport interface ModalActions {\n /** Closes the modal as if the person had dismissed it. */\n close: () => void;\n /** Unmounts the popup after an externally controlled closing animation. */\n unmount: () => void;\n}\n\ninterface ModalContextValue {\n layout: ModalLayout;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\nfunction useModalContext(part: string): ModalContextValue {\n const context = useContext(ModalContext);\n if (context === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Modal>.`);\n }\n return context;\n}\n\nexport interface ModalProps {\n /**\n * `center` is the 480px dialog, `sheet` the bottom sheet. Leave it unset and it\n * follows the screen: sheet below `md`, centered dialog from `md` up.\n */\n layout?: ModalLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: ModalChangeEventDetails) => void;\n /** Called after the open/close animation finishes. */\n onOpenChangeComplete?: (open: boolean) => void;\n /** Keeps the modal open when the person clicks or taps outside it. */\n disablePointerDismissal?: boolean;\n /** Imperative `close()` / `unmount()`. */\n actionsRef?: RefObject<ModalActions | null>;\n children?: ReactNode;\n}\n\n/**\n * Confirmation and interruption dialog. One component, two layouts with the same\n * content: a centered dialog on desktop and a bottom sheet on mobile.\n *\n * - `center`: close X in the header, actions in a row aligned right.\n * - `sheet`: drag handle and no X (it closes by swiping down or tapping outside),\n * actions stacked full width with the primary on top.\n *\n * Focus moves into the dialog when it opens, Tab stays inside, Escape closes it\n * and focus returns to the element that opened it.\n *\n * There is no destructive Modal: what is destructive is the action, so pass a\n * `destructive` Button as the primary action (or use `ConfirmModal`).\n */\nexport function Modal({\n layout,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: ModalProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: ModalLayout =\n layout ?? (isDesktop ? \"center\" : \"sheet\");\n\n // The open state lives here (not in Base UI) so the modal stays open when the\n // screen crosses the breakpoint and the Dialog root is swapped for the Drawer one.\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const open = openProp ?? uncontrolledOpen;\n const isControlled = openProp !== undefined;\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean, eventDetails: ModalChangeEventDetails) => {\n onOpenChange?.(nextOpen, eventDetails);\n if (!isControlled && !eventDetails.isCanceled) {\n setUncontrolledOpen(nextOpen);\n }\n },\n [isControlled, onOpenChange],\n );\n\n const context = useMemo(() => ({ layout: resolvedLayout }), [resolvedLayout]);\n\n return (\n <ModalContext.Provider value={context}>\n {resolvedLayout === \"sheet\" ? (\n <Drawer.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Drawer.Root>\n ) : (\n <Dialog.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Dialog.Root>\n )}\n </ModalContext.Provider>\n );\n}\n\nexport interface ModalTriggerProps extends Dialog.Trigger.Props {}\n\n/**\n * The element that opens the modal. Unstyled: pass the look with\n * `render={<Button variant=\"…\" />}`.\n */\nexport function ModalTrigger(props: ModalTriggerProps): React.JSX.Element {\n // Drawer.Trigger is Dialog.Trigger, so the same part works for both layouts.\n return <Dialog.Trigger data-slot=\"modal-trigger\" {...props} />;\n}\n\nexport interface ModalCloseProps extends Dialog.Close.Props {}\n\n/** Closes the modal. Use it for the \"Cancelar\" action: `render={<Button variant=\"secondary\" />}`. */\nexport function ModalClose(props: ModalCloseProps): React.JSX.Element {\n return <Dialog.Close data-slot=\"modal-close\" {...props} />;\n}\n\nconst backdropBase =\n \"fixed inset-0 z-50 min-h-dvh bg-scrim-image transition-opacity duration-200 data-starting-style:opacity-0 data-ending-style:opacity-0 motion-reduce:transition-none supports-[-webkit-touch-callout:none]:absolute\";\n\nexport interface ModalContentProps\n extends Omit<Dialog.Popup.Props, \"className\" | \"style\" | \"render\" | \"ref\"> {\n className?: string;\n style?: CSSProperties;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The dialog itself, with the scrim behind it. Children go in a column with 16px\n * between them: `ModalHeader`, then `ModalBody` (or your own content, sized to\n * the popup width) and `ModalFooter`.\n *\n * On open, focus goes to the dialog itself so screen readers announce its title and\n * message, and a destructive primary action is never focused by default. Pass\n * `initialFocus` to focus a field instead (e.g. in a form).\n */\nexport function ModalContent({\n className,\n children,\n initialFocus,\n ref,\n ...props\n}: ModalContentProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalContent\");\n const popupRef = useRef<HTMLDivElement | null>(null);\n\n const setPopupRef = useCallback(\n (node: HTMLDivElement | null) => {\n popupRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n if (layout === \"sheet\") {\n return (\n <Drawer.Portal>\n <Drawer.Backdrop\n data-slot=\"modal-backdrop\"\n className={cn(\n backdropBase,\n \"opacity-[calc(1-var(--drawer-swipe-progress,0))] data-swiping:duration-0\",\n )}\n />\n <Drawer.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-end justify-center\"\n >\n <Drawer.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"sheet\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-[90dvh] w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-t-2xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden\",\n \"[transform:translateY(var(--drawer-swipe-movement-y,0px))] transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]\",\n \"data-starting-style:[transform:translateY(100%)] data-ending-style:[transform:translateY(100%)]\",\n \"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] data-swiping:select-none\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n <div\n aria-hidden=\"true\"\n data-slot=\"modal-handle\"\n className=\"mx-auto h-1 w-9 shrink-0 rounded-full bg-border\"\n />\n <Drawer.Content className=\"flex flex-col gap-4\">\n {children}\n </Drawer.Content>\n </Drawer.Popup>\n </Drawer.Viewport>\n </Drawer.Portal>\n );\n }\n\n return (\n <Dialog.Portal>\n <Dialog.Backdrop data-slot=\"modal-backdrop\" className={backdropBase} />\n <Dialog.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-center justify-center p-6\"\n >\n <Dialog.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"center\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-full w-120 max-w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-2xl bg-background p-6 text-text-primary shadow-xl outline-hidden\",\n \"transition-[opacity,scale] duration-200 ease-out\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n );\n}\n\nexport interface ModalHeaderProps extends ComponentProps<\"div\"> {\n /**\n * Shows the close X (default). Only in the `center` layout: the sheet never has one,\n * it closes by swiping or tapping outside.\n */\n closeButton?: boolean;\n}\n\n/** Title row. Put a `ModalTitle` inside; the close X is added for you. */\nexport function ModalHeader({\n closeButton = true,\n className,\n children,\n ...props\n}: ModalHeaderProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalHeader\");\n const labels = useMediCoreLabels();\n\n return (\n <div\n data-slot=\"modal-header\"\n className={cn(\"flex items-start gap-3\", className)}\n {...props}\n >\n {children}\n {closeButton && layout === \"center\" && (\n <Dialog.Close\n render={\n <IconButton\n variant=\"ghost\"\n icon={<XCloseIcon />}\n aria-label={labels.close}\n />\n }\n />\n )}\n </div>\n );\n}\n\nexport interface ModalTitleProps extends Omit<Dialog.Title.Props, \"className\"> {\n className?: string;\n}\n\n/** Names the dialog (it becomes its accessible name). Renders an `<h2>`. */\nexport function ModalTitle({\n className,\n ...props\n}: ModalTitleProps): React.JSX.Element {\n return (\n <Dialog.Title\n data-slot=\"modal-title\"\n className={cn(\n \"min-w-0 flex-1 text-heading-h3 text-text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface ModalBodyProps\n extends Omit<Dialog.Description.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * The default content: the dialog's message, announced as its description.\n * For your own content (a form, a list) put it directly inside `ModalContent`\n * instead of `ModalBody`.\n */\nexport function ModalBody({\n className,\n ...props\n}: ModalBodyProps): React.JSX.Element {\n return (\n <Dialog.Description\n data-slot=\"modal-body\"\n className={cn(\"text-body-default text-text-secondary\", className)}\n {...props}\n />\n );\n}\n\nexport interface ModalFooterProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /** The main action, usually a `Button` (`variant=\"destructive\"` for a destructive confirmation). */\n primaryAction: ReactNode;\n /** Usually `<ModalClose render={<Button variant=\"secondary\" />}>Cancelar</ModalClose>`. */\n secondaryAction?: ReactNode;\n}\n\n/**\n * The actions. `center`: in a row aligned right, secondary first. `sheet`: stacked\n * full width with the primary on top, away from the resting thumb.\n */\nexport function ModalFooter({\n primaryAction,\n secondaryAction,\n className,\n ...props\n}: ModalFooterProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalFooter\");\n const isSheet = layout === \"sheet\";\n\n return (\n <div\n data-slot=\"modal-footer\"\n className={cn(\n \"flex gap-2\",\n isSheet ? \"flex-col *:w-full\" : \"flex-row flex-wrap justify-end\",\n className,\n )}\n {...props}\n >\n {isSheet ? primaryAction : secondaryAction}\n {isSheet ? secondaryAction : primaryAction}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"modal.js","sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["import { Dialog } from \"@base-ui/react/dialog\";\nimport { Drawer } from \"@base-ui/react/drawer\";\nimport {\n type ComponentProps,\n type CSSProperties,\n createContext,\n type ReactNode,\n type Ref,\n type RefObject,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { IconButton } from \"../icon-button\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"./use-media-query\";\n\nexport type ModalLayout = \"center\" | \"sheet\";\n\nexport type ModalChangeEventDetails =\n | Dialog.Root.ChangeEventDetails\n | Drawer.Root.ChangeEventDetails;\n\nexport interface ModalActions {\n /** Closes the modal as if the person had dismissed it. */\n close: () => void;\n /** Unmounts the popup after an externally controlled closing animation. */\n unmount: () => void;\n}\n\ninterface ModalContextValue {\n layout: ModalLayout;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\nfunction useModalContext(part: string): ModalContextValue {\n const context = useContext(ModalContext);\n if (context === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Modal>.`);\n }\n return context;\n}\n\nexport interface ModalProps {\n /**\n * `center` is the 480px dialog, `sheet` the bottom sheet. Leave it unset and it\n * follows the screen: sheet below `md`, centered dialog from `md` up.\n */\n layout?: ModalLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: ModalChangeEventDetails) => void;\n /** Called after the open/close animation finishes. */\n onOpenChangeComplete?: (open: boolean) => void;\n /** Keeps the modal open when the person clicks or taps outside it. */\n disablePointerDismissal?: boolean;\n /** Imperative `close()` / `unmount()`. */\n actionsRef?: RefObject<ModalActions | null>;\n children?: ReactNode;\n}\n\n/**\n * Confirmation and interruption dialog. One component, two layouts with the same\n * content: a centered dialog on desktop and a bottom sheet on mobile.\n *\n * - `center`: close X in the header, actions in a row aligned right.\n * - `sheet`: drag handle and no X (it closes by swiping down or tapping outside),\n * actions stacked full width with the primary on top.\n *\n * Focus moves into the dialog when it opens, Tab stays inside, Escape closes it\n * and focus returns to the element that opened it.\n *\n * There is no destructive Modal: what is destructive is the action, so pass a\n * `destructive` Button as the primary action (or use `ConfirmModal`).\n */\nexport function Modal({\n layout,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: ModalProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: ModalLayout =\n layout ?? (isDesktop ? \"center\" : \"sheet\");\n\n // The open state lives here (not in Base UI) so the modal stays open when the\n // screen crosses the breakpoint and the Dialog root is swapped for the Drawer one.\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const open = openProp ?? uncontrolledOpen;\n const isControlled = openProp !== undefined;\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean, eventDetails: ModalChangeEventDetails) => {\n onOpenChange?.(nextOpen, eventDetails);\n if (!isControlled && !eventDetails.isCanceled) {\n setUncontrolledOpen(nextOpen);\n }\n },\n [isControlled, onOpenChange],\n );\n\n const context = useMemo(() => ({ layout: resolvedLayout }), [resolvedLayout]);\n\n return (\n <ModalContext.Provider value={context}>\n {resolvedLayout === \"sheet\" ? (\n <Drawer.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Drawer.Root>\n ) : (\n <Dialog.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Dialog.Root>\n )}\n </ModalContext.Provider>\n );\n}\n\nexport interface ModalTriggerProps extends Dialog.Trigger.Props {}\n\n/**\n * The element that opens the modal. Unstyled: pass the look with\n * `render={<Button variant=\"…\" />}`.\n */\nexport function ModalTrigger(props: ModalTriggerProps): React.JSX.Element {\n // Drawer.Trigger is Dialog.Trigger, so the same part works for both layouts.\n return <Dialog.Trigger data-slot=\"modal-trigger\" {...props} />;\n}\n\nexport interface ModalCloseProps extends Dialog.Close.Props {}\n\n/** Closes the modal. Use it for the \"Cancelar\" action: `render={<Button variant=\"secondary\" />}`. */\nexport function ModalClose(props: ModalCloseProps): React.JSX.Element {\n return <Dialog.Close data-slot=\"modal-close\" {...props} />;\n}\n\nconst backdropBase =\n \"fixed inset-0 z-50 min-h-dvh bg-scrim-image transition-opacity duration-200 data-starting-style:opacity-0 data-ending-style:opacity-0 motion-reduce:transition-none supports-[-webkit-touch-callout:none]:absolute\";\n\nexport interface ModalContentProps\n extends Omit<Dialog.Popup.Props, \"className\" | \"style\" | \"render\" | \"ref\"> {\n className?: string;\n style?: CSSProperties;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The dialog itself, with the scrim behind it. Children go in a column with 16px\n * between them: `ModalHeader`, then `ModalBody` (or your own content, sized to\n * the popup width) and `ModalFooter`.\n *\n * On open, focus goes to the dialog itself so screen readers announce its title and\n * message, and a destructive primary action is never focused by default. Pass\n * `initialFocus` to focus a field instead (e.g. in a form).\n */\nexport function ModalContent({\n className,\n children,\n initialFocus,\n ref,\n ...props\n}: ModalContentProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalContent\");\n const popupRef = useRef<HTMLDivElement | null>(null);\n\n const setPopupRef = useCallback(\n (node: HTMLDivElement | null) => {\n popupRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n if (layout === \"sheet\") {\n return (\n <Drawer.Portal>\n <Drawer.Backdrop\n data-slot=\"modal-backdrop\"\n className={cn(\n backdropBase,\n \"opacity-[calc(1-var(--drawer-swipe-progress,0))] data-swiping:duration-0\",\n )}\n />\n <Drawer.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-end justify-center\"\n >\n <Drawer.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"sheet\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-[90dvh] w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-t-2xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden\",\n \"[transform:translateY(var(--drawer-swipe-movement-y,0px))] transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]\",\n \"data-starting-style:[transform:translateY(100%)] data-ending-style:[transform:translateY(100%)]\",\n \"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] data-swiping:select-none\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n <div\n aria-hidden=\"true\"\n data-slot=\"modal-handle\"\n className=\"mx-auto h-1 w-9 shrink-0 rounded-full bg-border\"\n />\n <Drawer.Content className=\"flex flex-col gap-4\">\n {children}\n </Drawer.Content>\n </Drawer.Popup>\n </Drawer.Viewport>\n </Drawer.Portal>\n );\n }\n\n return (\n <Dialog.Portal>\n <Dialog.Backdrop data-slot=\"modal-backdrop\" className={backdropBase} />\n <Dialog.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-center justify-center p-6\"\n >\n <Dialog.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"center\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-full w-120 max-w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-2xl bg-background p-6 text-text-primary shadow-xl outline-hidden\",\n \"transition-[opacity,scale] duration-200 ease-out-soft\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n );\n}\n\nexport interface ModalHeaderProps extends ComponentProps<\"div\"> {\n /**\n * Shows the close X (default). Only in the `center` layout: the sheet never has one,\n * it closes by swiping or tapping outside.\n */\n closeButton?: boolean;\n}\n\n/** Title row. Put a `ModalTitle` inside; the close X is added for you. */\nexport function ModalHeader({\n closeButton = true,\n className,\n children,\n ...props\n}: ModalHeaderProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalHeader\");\n const labels = useMediCoreLabels();\n\n return (\n <div\n data-slot=\"modal-header\"\n className={cn(\"flex items-start gap-3\", className)}\n {...props}\n >\n {children}\n {closeButton && layout === \"center\" && (\n <Dialog.Close\n render={\n <IconButton\n variant=\"ghost\"\n icon={<XCloseIcon />}\n aria-label={labels.close}\n />\n }\n />\n )}\n </div>\n );\n}\n\nexport interface ModalTitleProps extends Omit<Dialog.Title.Props, \"className\"> {\n className?: string;\n}\n\n/** Names the dialog (it becomes its accessible name). Renders an `<h2>`. */\nexport function ModalTitle({\n className,\n ...props\n}: ModalTitleProps): React.JSX.Element {\n return (\n <Dialog.Title\n data-slot=\"modal-title\"\n className={cn(\n \"min-w-0 flex-1 text-heading-h3 text-text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface ModalBodyProps\n extends Omit<Dialog.Description.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * The default content: the dialog's message, announced as its description.\n * For your own content (a form, a list) put it directly inside `ModalContent`\n * instead of `ModalBody`.\n */\nexport function ModalBody({\n className,\n ...props\n}: ModalBodyProps): React.JSX.Element {\n return (\n <Dialog.Description\n data-slot=\"modal-body\"\n className={cn(\"text-body-default text-text-secondary\", className)}\n {...props}\n />\n );\n}\n\nexport interface ModalFooterProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /** The main action, usually a `Button` (`variant=\"destructive\"` for a destructive confirmation). */\n primaryAction: ReactNode;\n /** Usually `<ModalClose render={<Button variant=\"secondary\" />}>Cancelar</ModalClose>`. */\n secondaryAction?: ReactNode;\n}\n\n/**\n * The actions. `center`: in a row aligned right, secondary first. `sheet`: stacked\n * full width with the primary on top, away from the resting thumb.\n */\nexport function ModalFooter({\n primaryAction,\n secondaryAction,\n className,\n ...props\n}: ModalFooterProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalFooter\");\n const isSheet = layout === \"sheet\";\n\n return (\n <div\n data-slot=\"modal-footer\"\n className={cn(\n \"flex gap-2\",\n isSheet ? \"flex-col *:w-full\" : \"flex-row flex-wrap justify-end\",\n className,\n )}\n {...props}\n >\n {isSheet ? primaryAction : secondaryAction}\n {isSheet ? secondaryAction : primaryAction}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@ import { Spinner } from "../spinner/spinner.js";
8
8
  const navItemVariants = cva(
9
9
  [
10
10
  "group/nav-item relative flex min-w-0 cursor-pointer select-none items-center gap-2.5 rounded-md p-2.5",
11
- "text-body-small text-text-secondary no-underline transition-colors",
11
+ "text-body-small text-text-secondary no-underline interactive",
12
12
  "[&_svg]:size-icon-md [&_svg]:shrink-0",
13
13
  // Inside the item, not around it: in navigation there is no room outside. It is
14
14
  // added on top of hover, so with the keyboard it shows even without the mouse.
@@ -96,7 +96,7 @@ function NavItem({
96
96
  "data-slot": "nav-item-tooltip",
97
97
  className: cn(
98
98
  "max-w-60 rounded-md bg-surface-inverse px-2 py-1.5 text-label-small text-text-inverse",
99
- "origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out",
99
+ "origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out-soft",
100
100
  "data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0",
101
101
  "data-instant:transition-none motion-reduce:transition-none"
102
102
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"nav-item.js","sources":["../../../src/components/nav-item/nav-item.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { Tooltip } from \"@base-ui/react/tooltip\";\nimport type { BaseUIEvent } from \"@base-ui/react/types\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva } from \"class-variance-authority\";\nimport type { MouseEvent, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Spinner } from \"../spinner\";\n\nexport const navItemVariants = cva(\n [\n \"group/nav-item relative flex min-w-0 cursor-pointer select-none items-center gap-2.5 rounded-md p-2.5\",\n \"text-body-small text-text-secondary no-underline transition-colors\",\n \"[&_svg]:size-icon-md [&_svg]:shrink-0\",\n // Inside the item, not around it: in navigation there is no room outside. It is\n // added on top of hover, so with the keyboard it shows even without the mouse.\n \"focus-ring focus-visible:-outline-offset-2\",\n // Figma says hover = `surface`, but every nav bar already sits on `surface`, where\n // it would be invisible: the next step up keeps the hover visible.\n \"not-aria-disabled:not-aria-busy:not-aria-[current=page]:hover:bg-surface-raised\",\n \"not-aria-disabled:not-aria-busy:not-aria-[current=page]:hover:text-text-primary\",\n // Active: brand-subtle pill + brand text + the 3px rail (the non-colour cue).\n \"aria-[current=page]:bg-brand-subtle aria-[current=page]:text-text-brand\",\n \"aria-disabled:cursor-not-allowed aria-disabled:text-text-disabled\",\n \"aria-busy:cursor-progress aria-busy:text-text-secondary\",\n ],\n {\n variants: {\n collapsed: {\n true: \"size-10 shrink-0 justify-center\",\n false: \"w-full\",\n },\n },\n defaultVariants: {\n collapsed: false,\n },\n },\n);\n\nexport interface NavItemState extends Record<string, unknown> {\n active: boolean;\n collapsed: boolean;\n disabled: boolean;\n loading: boolean;\n}\n\nexport interface NavItemProps\n extends Omit<\n useRender.ComponentProps<\"a\", NavItemState>,\n \"className\" | \"children\"\n > {\n /** A MediCore icon (`<Home02Icon />`). It is sized to 20 by the item. */\n icon: ReactNode;\n /** The section name (\"Inicio\"). Always required: it stays in the DOM when collapsed. */\n children: ReactNode;\n /**\n * The section on screen: `aria-current=\"page\"`, brand pill and rail. Leave it out if\n * the router link already sets `aria-current=\"page\"` (TanStack Router's `<Link>`\n * does): the styles follow the attribute, not the prop.\n */\n active?: boolean;\n /**\n * Figma \"Show Label\" off: icon only, 40×40. The label becomes screen-reader text and\n * shows in a tooltip on hover and keyboard focus.\n */\n collapsed?: boolean;\n /** Section not available: `aria-disabled`, out of the tab order, ignores clicks. */\n disabled?: boolean;\n /**\n * The section is loading after a click: the icon turns into a spinner, the label\n * stays and further clicks are ignored (without it people click twice).\n */\n loading?: boolean;\n className?: string;\n}\n\n// Cancels the navigation and skips the handlers passed in `props`.\nconst preventNavigation = (\n event: BaseUIEvent<MouseEvent<HTMLAnchorElement>>,\n): void => {\n event.preventDefault();\n event.preventBaseUIHandler();\n};\n\n/**\n * Navigation item: icon + label. The atom of `SidebarItem` (and any other nav bar).\n * Renders an `<a>`; pass the router's link with `render`:\n *\n * ```tsx\n * <NavItem icon={<Home02Icon />} render={<Link to=\"/\" activeOptions={{ exact: true }} />}>\n * Inicio\n * </NavItem>\n * ```\n *\n * - The active item is not told apart by colour alone (brand-subtle on surface is\n * 1.22:1): it carries a 3px rail on the left. Mark it with `active` or let the\n * router set `aria-current=\"page\"`.\n * - Collapsed (icon only) keeps the label in the DOM as its accessible name and adds a\n * tooltip with it.\n * - Hover and focus are CSS: the focus ring is inside the item and adds to the hover.\n */\nexport function NavItem({\n icon,\n children,\n active = false,\n collapsed = false,\n disabled = false,\n loading = false,\n className,\n render,\n ...props\n}: NavItemProps): React.JSX.Element {\n const state: NavItemState = { active, collapsed, disabled, loading };\n\n const ownProps: useRender.ElementProps<\"a\"> & { \"data-slot\": string } = {\n \"data-slot\": \"nav-item\",\n className: cn(navItemVariants({ collapsed }), className),\n \"aria-current\": active ? \"page\" : undefined,\n children: (\n <>\n <span\n aria-hidden\n data-slot=\"nav-item-rail\"\n className=\"absolute inset-y-0 left-0 hidden w-0.75 rounded-xs bg-text-brand group-aria-[current=page]/nav-item:block\"\n />\n {loading ? <Spinner size={20} label={null} /> : icon}\n <span\n data-slot=\"nav-item-label\"\n className={collapsed ? \"sr-only\" : \"min-w-0 flex-1\"}\n >\n {children}\n </span>\n </>\n ),\n };\n\n // Last, so they win over whatever came in `props`.\n const blockedProps: useRender.ElementProps<\"a\"> = disabled\n ? {\n \"aria-disabled\": true,\n tabIndex: -1,\n onClick: preventNavigation,\n }\n : loading\n ? { \"aria-busy\": true, onClick: preventNavigation }\n : {};\n\n const element = useRender({\n defaultTagName: \"a\",\n render,\n state,\n props: mergeProps<\"a\">(ownProps, props, blockedProps),\n });\n\n if (!collapsed) {\n return element;\n }\n\n // Same look as MediCore's `Tooltip`, but to the right (it only opens top/bottom) and\n // without `aria-describedby`: the label is already the link's name, and describing\n // it with the same words would make screen readers say it twice.\n return (\n <Tooltip.Root disabled={disabled}>\n <Tooltip.Trigger render={element} />\n <Tooltip.Portal>\n <Tooltip.Positioner side=\"right\" sideOffset={8} className=\"z-70\">\n <Tooltip.Popup\n data-slot=\"nav-item-tooltip\"\n className={cn(\n \"max-w-60 rounded-md bg-surface-inverse px-2 py-1.5 text-label-small text-text-inverse\",\n \"origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0\",\n \"data-instant:transition-none motion-reduce:transition-none\",\n )}\n >\n <Tooltip.Arrow\n className={cn(\n \"h-2.5 w-1.5 bg-surface-inverse\",\n \"data-[side=right]:right-full data-[side=right]:[clip-path:polygon(100%_0,100%_100%,0_50%)]\",\n \"data-[side=left]:left-full data-[side=left]:[clip-path:polygon(0_0,100%_50%,0_100%)]\",\n )}\n />\n {children}\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAUO,MAAM,kBAAkB;AAAA,EAC7B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,WAAW;AAAA,QACT,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,WAAW;AAAA,IAAA;AAAA,EACb;AAEJ;AAwCA,MAAM,oBAAoB,CACxB,UACS;AACT,QAAM,eAAA;AACN,QAAM,qBAAA;AACR;AAmBO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoC;AAClC,QAAM,QAAsB,EAAE,QAAQ,WAAW,UAAU,QAAA;AAE3D,QAAM,WAAkE;AAAA,IACtE,aAAa;AAAA,IACb,WAAW,GAAG,gBAAgB,EAAE,UAAA,CAAW,GAAG,SAAS;AAAA,IACvD,gBAAgB,SAAS,SAAS;AAAA,IAClC,UACE,qBAAA,UAAA,EACE,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAW;AAAA,UACX,aAAU;AAAA,UACV,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAEX,UAAU,oBAAC,SAAA,EAAQ,MAAM,IAAI,OAAO,MAAM,IAAK;AAAA,MAChD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,aAAU;AAAA,UACV,WAAW,YAAY,YAAY;AAAA,UAElC;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CACF;AAAA,EAAA;AAKJ,QAAM,eAA4C,WAC9C;AAAA,IACE,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,SAAS;AAAA,EAAA,IAEX,UACE,EAAE,aAAa,MAAM,SAAS,kBAAA,IAC9B,CAAA;AAEN,QAAM,UAAU,UAAU;AAAA,IACxB,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,OAAO,WAAgB,UAAU,OAAO,YAAY;AAAA,EAAA,CACrD;AAED,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAKA,SACE,qBAAC,QAAQ,MAAR,EAAa,UACZ,UAAA;AAAA,IAAA,oBAAC,QAAQ,SAAR,EAAgB,QAAQ,QAAA,CAAS;AAAA,IAClC,oBAAC,QAAQ,QAAR,EACC,UAAA,oBAAC,QAAQ,YAAR,EAAmB,MAAK,SAAQ,YAAY,GAAG,WAAU,QACxD,UAAA;AAAA,MAAC,QAAQ;AAAA,MAAR;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAA;AAAA,YAAC,QAAQ;AAAA,YAAR;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,YACF;AAAA,UAAA;AAAA,UAED;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAEL,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"nav-item.js","sources":["../../../src/components/nav-item/nav-item.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { Tooltip } from \"@base-ui/react/tooltip\";\nimport type { BaseUIEvent } from \"@base-ui/react/types\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva } from \"class-variance-authority\";\nimport type { MouseEvent, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Spinner } from \"../spinner\";\n\nexport const navItemVariants = cva(\n [\n \"group/nav-item relative flex min-w-0 cursor-pointer select-none items-center gap-2.5 rounded-md p-2.5\",\n \"text-body-small text-text-secondary no-underline interactive\",\n \"[&_svg]:size-icon-md [&_svg]:shrink-0\",\n // Inside the item, not around it: in navigation there is no room outside. It is\n // added on top of hover, so with the keyboard it shows even without the mouse.\n \"focus-ring focus-visible:-outline-offset-2\",\n // Figma says hover = `surface`, but every nav bar already sits on `surface`, where\n // it would be invisible: the next step up keeps the hover visible.\n \"not-aria-disabled:not-aria-busy:not-aria-[current=page]:hover:bg-surface-raised\",\n \"not-aria-disabled:not-aria-busy:not-aria-[current=page]:hover:text-text-primary\",\n // Active: brand-subtle pill + brand text + the 3px rail (the non-colour cue).\n \"aria-[current=page]:bg-brand-subtle aria-[current=page]:text-text-brand\",\n \"aria-disabled:cursor-not-allowed aria-disabled:text-text-disabled\",\n \"aria-busy:cursor-progress aria-busy:text-text-secondary\",\n ],\n {\n variants: {\n collapsed: {\n true: \"size-10 shrink-0 justify-center\",\n false: \"w-full\",\n },\n },\n defaultVariants: {\n collapsed: false,\n },\n },\n);\n\nexport interface NavItemState extends Record<string, unknown> {\n active: boolean;\n collapsed: boolean;\n disabled: boolean;\n loading: boolean;\n}\n\nexport interface NavItemProps\n extends Omit<\n useRender.ComponentProps<\"a\", NavItemState>,\n \"className\" | \"children\"\n > {\n /** A MediCore icon (`<Home02Icon />`). It is sized to 20 by the item. */\n icon: ReactNode;\n /** The section name (\"Inicio\"). Always required: it stays in the DOM when collapsed. */\n children: ReactNode;\n /**\n * The section on screen: `aria-current=\"page\"`, brand pill and rail. Leave it out if\n * the router link already sets `aria-current=\"page\"` (TanStack Router's `<Link>`\n * does): the styles follow the attribute, not the prop.\n */\n active?: boolean;\n /**\n * Figma \"Show Label\" off: icon only, 40×40. The label becomes screen-reader text and\n * shows in a tooltip on hover and keyboard focus.\n */\n collapsed?: boolean;\n /** Section not available: `aria-disabled`, out of the tab order, ignores clicks. */\n disabled?: boolean;\n /**\n * The section is loading after a click: the icon turns into a spinner, the label\n * stays and further clicks are ignored (without it people click twice).\n */\n loading?: boolean;\n className?: string;\n}\n\n// Cancels the navigation and skips the handlers passed in `props`.\nconst preventNavigation = (\n event: BaseUIEvent<MouseEvent<HTMLAnchorElement>>,\n): void => {\n event.preventDefault();\n event.preventBaseUIHandler();\n};\n\n/**\n * Navigation item: icon + label. The atom of `SidebarItem` (and any other nav bar).\n * Renders an `<a>`; pass the router's link with `render`:\n *\n * ```tsx\n * <NavItem icon={<Home02Icon />} render={<Link to=\"/\" activeOptions={{ exact: true }} />}>\n * Inicio\n * </NavItem>\n * ```\n *\n * - The active item is not told apart by colour alone (brand-subtle on surface is\n * 1.22:1): it carries a 3px rail on the left. Mark it with `active` or let the\n * router set `aria-current=\"page\"`.\n * - Collapsed (icon only) keeps the label in the DOM as its accessible name and adds a\n * tooltip with it.\n * - Hover and focus are CSS: the focus ring is inside the item and adds to the hover.\n */\nexport function NavItem({\n icon,\n children,\n active = false,\n collapsed = false,\n disabled = false,\n loading = false,\n className,\n render,\n ...props\n}: NavItemProps): React.JSX.Element {\n const state: NavItemState = { active, collapsed, disabled, loading };\n\n const ownProps: useRender.ElementProps<\"a\"> & { \"data-slot\": string } = {\n \"data-slot\": \"nav-item\",\n className: cn(navItemVariants({ collapsed }), className),\n \"aria-current\": active ? \"page\" : undefined,\n children: (\n <>\n <span\n aria-hidden\n data-slot=\"nav-item-rail\"\n className=\"absolute inset-y-0 left-0 hidden w-0.75 rounded-xs bg-text-brand group-aria-[current=page]/nav-item:block\"\n />\n {loading ? <Spinner size={20} label={null} /> : icon}\n <span\n data-slot=\"nav-item-label\"\n className={collapsed ? \"sr-only\" : \"min-w-0 flex-1\"}\n >\n {children}\n </span>\n </>\n ),\n };\n\n // Last, so they win over whatever came in `props`.\n const blockedProps: useRender.ElementProps<\"a\"> = disabled\n ? {\n \"aria-disabled\": true,\n tabIndex: -1,\n onClick: preventNavigation,\n }\n : loading\n ? { \"aria-busy\": true, onClick: preventNavigation }\n : {};\n\n const element = useRender({\n defaultTagName: \"a\",\n render,\n state,\n props: mergeProps<\"a\">(ownProps, props, blockedProps),\n });\n\n if (!collapsed) {\n return element;\n }\n\n // Same look as MediCore's `Tooltip`, but to the right (it only opens top/bottom) and\n // without `aria-describedby`: the label is already the link's name, and describing\n // it with the same words would make screen readers say it twice.\n return (\n <Tooltip.Root disabled={disabled}>\n <Tooltip.Trigger render={element} />\n <Tooltip.Portal>\n <Tooltip.Positioner side=\"right\" sideOffset={8} className=\"z-70\">\n <Tooltip.Popup\n data-slot=\"nav-item-tooltip\"\n className={cn(\n \"max-w-60 rounded-md bg-surface-inverse px-2 py-1.5 text-label-small text-text-inverse\",\n \"origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out-soft\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0\",\n \"data-instant:transition-none motion-reduce:transition-none\",\n )}\n >\n <Tooltip.Arrow\n className={cn(\n \"h-2.5 w-1.5 bg-surface-inverse\",\n \"data-[side=right]:right-full data-[side=right]:[clip-path:polygon(100%_0,100%_100%,0_50%)]\",\n \"data-[side=left]:left-full data-[side=left]:[clip-path:polygon(0_0,100%_50%,0_100%)]\",\n )}\n />\n {children}\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAUO,MAAM,kBAAkB;AAAA,EAC7B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,WAAW;AAAA,QACT,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,WAAW;AAAA,IAAA;AAAA,EACb;AAEJ;AAwCA,MAAM,oBAAoB,CACxB,UACS;AACT,QAAM,eAAA;AACN,QAAM,qBAAA;AACR;AAmBO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoC;AAClC,QAAM,QAAsB,EAAE,QAAQ,WAAW,UAAU,QAAA;AAE3D,QAAM,WAAkE;AAAA,IACtE,aAAa;AAAA,IACb,WAAW,GAAG,gBAAgB,EAAE,UAAA,CAAW,GAAG,SAAS;AAAA,IACvD,gBAAgB,SAAS,SAAS;AAAA,IAClC,UACE,qBAAA,UAAA,EACE,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAW;AAAA,UACX,aAAU;AAAA,UACV,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAEX,UAAU,oBAAC,SAAA,EAAQ,MAAM,IAAI,OAAO,MAAM,IAAK;AAAA,MAChD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,aAAU;AAAA,UACV,WAAW,YAAY,YAAY;AAAA,UAElC;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CACF;AAAA,EAAA;AAKJ,QAAM,eAA4C,WAC9C;AAAA,IACE,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,SAAS;AAAA,EAAA,IAEX,UACE,EAAE,aAAa,MAAM,SAAS,kBAAA,IAC9B,CAAA;AAEN,QAAM,UAAU,UAAU;AAAA,IACxB,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,OAAO,WAAgB,UAAU,OAAO,YAAY;AAAA,EAAA,CACrD;AAED,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAKA,SACE,qBAAC,QAAQ,MAAR,EAAa,UACZ,UAAA;AAAA,IAAA,oBAAC,QAAQ,SAAR,EAAgB,QAAQ,QAAA,CAAS;AAAA,IAClC,oBAAC,QAAQ,QAAR,EACC,UAAA,oBAAC,QAAQ,YAAR,EAAmB,MAAK,SAAQ,YAAY,GAAG,WAAU,QACxD,UAAA;AAAA,MAAC,QAAQ;AAAA,MAAR;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAA;AAAA,YAAC,QAAQ;AAAA,YAAR;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,YACF;AAAA,UAAA;AAAA,UAED;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAEL,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
@@ -63,7 +63,7 @@ function OTPInput({
63
63
  "data-slot": "otp-input",
64
64
  className: cn("inline-flex max-w-full flex-col", className),
65
65
  children: [
66
- /* @__PURE__ */ jsxs("div", { className: "relative flex gap-2", children: [
66
+ /* @__PURE__ */ jsxs("div", { className: "group/otp relative flex gap-2", children: [
67
67
  cells.map((position) => {
68
68
  const digit = code.charAt(position);
69
69
  const active = focused && !validating && !disabled && position === activeCell;
@@ -74,7 +74,8 @@ function OTPInput({
74
74
  "data-slot": "otp-input-cell",
75
75
  className: cn(
76
76
  "flex h-14 w-12 shrink-0 items-center justify-center rounded-md border border-border-input bg-background text-heading-h3 text-text-primary",
77
- "ring-1 ring-transparent ring-inset transition-[border-color,box-shadow]",
77
+ "ring-1 ring-transparent ring-inset interactive",
78
+ !hasError && !active && !disabled && "group-hover/otp:border-border-strong",
78
79
  hasError && "border-error ring-error",
79
80
  active && "border-border-focus ring-border-focus",
80
81
  disabled && "border-border bg-surface text-text-disabled"
@@ -1 +1 @@
1
- {"version":3,"file":"otp-input.js","sources":["../../../src/components/otp-input/otp-input.tsx"],"sourcesContent":["import { Input as BaseInput } from \"@base-ui/react/input\";\nimport { type ReactNode, useId, useState } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Spinner } from \"../spinner\";\n\nexport interface OTPInputProps\n extends Omit<\n BaseInput.Props,\n | \"className\"\n | \"value\"\n | \"defaultValue\"\n | \"onValueChange\"\n | \"type\"\n | \"size\"\n | \"maxLength\"\n | \"pattern\"\n > {\n /** Number of digits. The Figma spec is 6 (WhatsApp / SMS code). */\n length?: number;\n /** The code typed so far (digits only). Use when controlled. */\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /**\n * Called when the last digit is typed or a full code is pasted. Check the code here\n * and set `validating`: there is no submit button.\n */\n onComplete?: (value: string) => void;\n /** State=Validating: spinner + message, and the code can't be edited meanwhile. */\n validating?: boolean;\n /** Text next to the spinner: \"Comprobando el código…\". Defaults to \"Cargando\". */\n validatingLabel?: ReactNode;\n /**\n * State=Error: every cell in red and this message under them. Say what to do: \"El\n * código no es correcto. Revisa el mensaje o pide uno nuevo.\"\n */\n error?: ReactNode;\n className?: string;\n}\n\nfunction onlyDigits(text: string, length: number): string {\n return text.replace(/\\D/g, \"\").slice(0, length);\n}\n\n/**\n * Verification code sent by WhatsApp or SMS. Drawn as six cells but it is ONE input\n * (`inputmode=\"numeric\" autocomplete=\"one-time-code\"`), laid transparently over the\n * cells: the phone offers the code from the message in one tap, paste works, and a\n * screen reader announces a single field (\"Código de verificación\"), not six.\n *\n * Base UI's OTPField renders one input per cell, which is exactly what the spec rules\n * out, so this is a plain input driving the cells.\n *\n * - The code is checked on its own when complete (`onComplete` → `validating`).\n * - The message under the cells (validating / error) is `aria-live=\"polite\"`.\n * - The accessible name defaults to \"Código de verificación\"; the heading above the\n * cells (\"Escribe el código\") is not a label.\n */\nexport function OTPInput({\n length = 6,\n value,\n defaultValue,\n onValueChange,\n onComplete,\n validating = false,\n validatingLabel,\n error,\n disabled = false,\n readOnly,\n className,\n onFocus,\n onBlur,\n onChange,\n onPaste,\n onSelect,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: OTPInputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const messageId = useId();\n const [uncontrolled, setUncontrolled] = useState(() =>\n onlyDigits(defaultValue ?? \"\", length),\n );\n const [focused, setFocused] = useState(false);\n const code = value === undefined ? uncontrolled : onlyDigits(value, length);\n const hasError = error !== undefined && error !== null && error !== false;\n const cells = Array.from({ length }, (_, position) => position);\n const activeCell = Math.min(code.length, length - 1);\n\n function commit(next: string): void {\n if (next === code) {\n return;\n }\n if (value === undefined) {\n setUncontrolled(next);\n }\n onValueChange?.(next);\n if (next.length === length) {\n onComplete?.(next);\n }\n }\n\n // One input, so the caret always sits after the last digit: typing appends and\n // backspace removes the last one. Selecting everything (to retype) is allowed.\n function keepCaretAtEnd(input: HTMLInputElement): void {\n const end = input.value.length;\n const selectsAll = input.selectionStart === 0 && input.selectionEnd === end;\n if (!selectsAll && input.selectionStart !== end) {\n input.setSelectionRange(end, end);\n }\n }\n\n return (\n <div\n data-slot=\"otp-input\"\n className={cn(\"inline-flex max-w-full flex-col\", className)}\n >\n <div className=\"relative flex gap-2\">\n {cells.map((position) => {\n const digit = code.charAt(position);\n const active =\n focused && !validating && !disabled && position === activeCell;\n return (\n <div\n key={position}\n aria-hidden\n data-slot=\"otp-input-cell\"\n className={cn(\n \"flex h-14 w-12 shrink-0 items-center justify-center rounded-md border border-border-input bg-background text-heading-h3 text-text-primary\",\n \"ring-1 ring-transparent ring-inset transition-[border-color,box-shadow]\",\n hasError && \"border-error ring-error\",\n active && \"border-border-focus ring-border-focus\",\n disabled && \"border-border bg-surface text-text-disabled\",\n )}\n >\n {digit ||\n (active && (\n <span className=\"h-6 w-0.5 bg-text-primary motion-safe:animate-pulse\" />\n ))}\n </div>\n );\n })}\n <BaseInput\n data-slot=\"otp-input-control\"\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n pattern=\"[0-9]*\"\n maxLength={length}\n spellCheck={false}\n value={code}\n disabled={disabled}\n readOnly={readOnly || validating}\n aria-label={ariaLabel ?? labels.verificationCode}\n aria-describedby={[messageId, ariaDescribedBy]\n .filter(Boolean)\n .join(\" \")}\n aria-invalid={hasError && !disabled ? true : undefined}\n onChange={(event) => {\n commit(onlyDigits(event.currentTarget.value, length));\n onChange?.(event);\n }}\n onPaste={(event) => {\n onPaste?.(event);\n if (event.defaultPrevented) {\n return;\n }\n // Codes are often pasted as \"482 916\" or \"482-916\": keep the digits.\n event.preventDefault();\n commit(onlyDigits(event.clipboardData.getData(\"text\"), length));\n }}\n onFocus={(event) => {\n setFocused(true);\n keepCaretAtEnd(event.currentTarget);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n setFocused(false);\n onBlur?.(event);\n }}\n onSelect={(event) => {\n keepCaretAtEnd(event.currentTarget);\n onSelect?.(event);\n }}\n className=\"absolute inset-0 size-full cursor-text bg-transparent text-heading-h3 text-transparent caret-transparent outline-none selection:bg-transparent disabled:cursor-not-allowed\"\n {...props}\n />\n </div>\n {/* Always in the DOM so the live region exists before its text arrives. */}\n <div\n id={messageId}\n aria-live=\"polite\"\n className=\"w-0 min-w-full text-body-small\"\n >\n {validating ? (\n <p className=\"mt-3 flex items-center gap-2 text-text-secondary\">\n <Spinner size={16} label={null} />\n {validatingLabel ?? labels.loading}\n </p>\n ) : hasError ? (\n <p className=\"mt-3 text-text-error\">{error}</p>\n ) : null}\n </div>\n </div>\n );\n}\n"],"names":["BaseInput"],"mappings":"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"}
1
+ {"version":3,"file":"otp-input.js","sources":["../../../src/components/otp-input/otp-input.tsx"],"sourcesContent":["import { Input as BaseInput } from \"@base-ui/react/input\";\nimport { type ReactNode, useId, useState } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Spinner } from \"../spinner\";\n\nexport interface OTPInputProps\n extends Omit<\n BaseInput.Props,\n | \"className\"\n | \"value\"\n | \"defaultValue\"\n | \"onValueChange\"\n | \"type\"\n | \"size\"\n | \"maxLength\"\n | \"pattern\"\n > {\n /** Number of digits. The Figma spec is 6 (WhatsApp / SMS code). */\n length?: number;\n /** The code typed so far (digits only). Use when controlled. */\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /**\n * Called when the last digit is typed or a full code is pasted. Check the code here\n * and set `validating`: there is no submit button.\n */\n onComplete?: (value: string) => void;\n /** State=Validating: spinner + message, and the code can't be edited meanwhile. */\n validating?: boolean;\n /** Text next to the spinner: \"Comprobando el código…\". Defaults to \"Cargando\". */\n validatingLabel?: ReactNode;\n /**\n * State=Error: every cell in red and this message under them. Say what to do: \"El\n * código no es correcto. Revisa el mensaje o pide uno nuevo.\"\n */\n error?: ReactNode;\n className?: string;\n}\n\nfunction onlyDigits(text: string, length: number): string {\n return text.replace(/\\D/g, \"\").slice(0, length);\n}\n\n/**\n * Verification code sent by WhatsApp or SMS. Drawn as six cells but it is ONE input\n * (`inputmode=\"numeric\" autocomplete=\"one-time-code\"`), laid transparently over the\n * cells: the phone offers the code from the message in one tap, paste works, and a\n * screen reader announces a single field (\"Código de verificación\"), not six.\n *\n * Base UI's OTPField renders one input per cell, which is exactly what the spec rules\n * out, so this is a plain input driving the cells.\n *\n * - The code is checked on its own when complete (`onComplete` → `validating`).\n * - The message under the cells (validating / error) is `aria-live=\"polite\"`.\n * - The accessible name defaults to \"Código de verificación\"; the heading above the\n * cells (\"Escribe el código\") is not a label.\n */\nexport function OTPInput({\n length = 6,\n value,\n defaultValue,\n onValueChange,\n onComplete,\n validating = false,\n validatingLabel,\n error,\n disabled = false,\n readOnly,\n className,\n onFocus,\n onBlur,\n onChange,\n onPaste,\n onSelect,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: OTPInputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const messageId = useId();\n const [uncontrolled, setUncontrolled] = useState(() =>\n onlyDigits(defaultValue ?? \"\", length),\n );\n const [focused, setFocused] = useState(false);\n const code = value === undefined ? uncontrolled : onlyDigits(value, length);\n const hasError = error !== undefined && error !== null && error !== false;\n const cells = Array.from({ length }, (_, position) => position);\n const activeCell = Math.min(code.length, length - 1);\n\n function commit(next: string): void {\n if (next === code) {\n return;\n }\n if (value === undefined) {\n setUncontrolled(next);\n }\n onValueChange?.(next);\n if (next.length === length) {\n onComplete?.(next);\n }\n }\n\n // One input, so the caret always sits after the last digit: typing appends and\n // backspace removes the last one. Selecting everything (to retype) is allowed.\n function keepCaretAtEnd(input: HTMLInputElement): void {\n const end = input.value.length;\n const selectsAll = input.selectionStart === 0 && input.selectionEnd === end;\n if (!selectsAll && input.selectionStart !== end) {\n input.setSelectionRange(end, end);\n }\n }\n\n return (\n <div\n data-slot=\"otp-input\"\n className={cn(\"inline-flex max-w-full flex-col\", className)}\n >\n <div className=\"group/otp relative flex gap-2\">\n {cells.map((position) => {\n const digit = code.charAt(position);\n const active =\n focused && !validating && !disabled && position === activeCell;\n return (\n <div\n key={position}\n aria-hidden\n data-slot=\"otp-input-cell\"\n className={cn(\n \"flex h-14 w-12 shrink-0 items-center justify-center rounded-md border border-border-input bg-background text-heading-h3 text-text-primary\",\n \"ring-1 ring-transparent ring-inset interactive\",\n !hasError &&\n !active &&\n !disabled &&\n \"group-hover/otp:border-border-strong\",\n hasError && \"border-error ring-error\",\n active && \"border-border-focus ring-border-focus\",\n disabled && \"border-border bg-surface text-text-disabled\",\n )}\n >\n {digit ||\n (active && (\n <span className=\"h-6 w-0.5 bg-text-primary motion-safe:animate-pulse\" />\n ))}\n </div>\n );\n })}\n <BaseInput\n data-slot=\"otp-input-control\"\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n pattern=\"[0-9]*\"\n maxLength={length}\n spellCheck={false}\n value={code}\n disabled={disabled}\n readOnly={readOnly || validating}\n aria-label={ariaLabel ?? labels.verificationCode}\n aria-describedby={[messageId, ariaDescribedBy]\n .filter(Boolean)\n .join(\" \")}\n aria-invalid={hasError && !disabled ? true : undefined}\n onChange={(event) => {\n commit(onlyDigits(event.currentTarget.value, length));\n onChange?.(event);\n }}\n onPaste={(event) => {\n onPaste?.(event);\n if (event.defaultPrevented) {\n return;\n }\n // Codes are often pasted as \"482 916\" or \"482-916\": keep the digits.\n event.preventDefault();\n commit(onlyDigits(event.clipboardData.getData(\"text\"), length));\n }}\n onFocus={(event) => {\n setFocused(true);\n keepCaretAtEnd(event.currentTarget);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n setFocused(false);\n onBlur?.(event);\n }}\n onSelect={(event) => {\n keepCaretAtEnd(event.currentTarget);\n onSelect?.(event);\n }}\n className=\"absolute inset-0 size-full cursor-text bg-transparent text-heading-h3 text-transparent caret-transparent outline-none selection:bg-transparent disabled:cursor-not-allowed\"\n {...props}\n />\n </div>\n {/* Always in the DOM so the live region exists before its text arrives. */}\n <div\n id={messageId}\n aria-live=\"polite\"\n className=\"w-0 min-w-full text-body-small\"\n >\n {validating ? (\n <p className=\"mt-3 flex items-center gap-2 text-text-secondary\">\n <Spinner size={16} label={null} />\n {validatingLabel ?? labels.loading}\n </p>\n ) : hasError ? (\n <p className=\"mt-3 text-text-error\">{error}</p>\n ) : null}\n </div>\n </div>\n );\n}\n"],"names":["BaseInput"],"mappings":"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"}
@@ -8,7 +8,8 @@ import { useFieldGroupLabel } from "./use-field-group-label.js";
8
8
  const radioVariants = cva(
9
9
  [
10
10
  "inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full border",
11
- "border-border-input bg-background transition-colors",
11
+ "border-border-input bg-background interactive",
12
+ "not-data-checked:not-data-disabled:hover:border-text-brand",
12
13
  "data-checked:border-brand data-checked:bg-brand",
13
14
  "data-invalid:data-unchecked:border-error",
14
15
  "data-disabled:cursor-not-allowed data-disabled:border-border data-disabled:bg-surface",
@@ -1 +1 @@
1
- {"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport { RadioGroup as BaseRadioGroup } from \"@base-ui/react/radio-group\";\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { FieldItemLabel } from \"./field-item-label\";\nimport { useFieldGroupLabel } from \"./use-field-group-label\";\n\nexport const radioVariants = cva(\n [\n \"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full border\",\n \"border-border-input bg-background transition-colors\",\n \"data-checked:border-brand data-checked:bg-brand\",\n \"data-invalid:data-unchecked:border-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border data-disabled:bg-surface\",\n \"data-disabled:data-checked:border-border-strong data-disabled:data-checked:bg-border-strong\",\n ],\n {\n variants: {\n // Off when the focus ring and the hit area belong to a bigger container that\n // wraps the radio (SelectionCard).\n standalone: {\n true: \"focus-ring touch-target\",\n false: \"\",\n },\n },\n defaultVariants: {\n standalone: true,\n },\n },\n);\n\n/** The dot of a checked radio: 8px in text-inverse. */\nexport function RadioDot(): React.JSX.Element {\n return (\n <BaseRadio.Indicator className=\"size-2 rounded-full bg-text-inverse\" />\n );\n}\n\nexport interface RadioProps<Value = string>\n extends Omit<BaseRadio.Root.Props<Value>, \"className\" | \"children\"> {\n className?: string;\n}\n\n/**\n * Single choice inside a `RadioGroup`. The label lives outside: use `RadioField`, or\n * wrap radio and text in a `<label>`.\n *\n * A radio never unticks itself. If the person can go back to \"none\", that is a\n * Checkbox or an explicit \"Ninguna\" option. The drawing is 20×20; the hit area is\n * 44×44, and the whole row with `RadioField`.\n */\nexport function Radio<Value = string>({\n className,\n ...props\n}: RadioProps<Value>): React.JSX.Element {\n return (\n <BaseRadio.Root\n data-slot=\"radio\"\n className={cn(radioVariants(), className)}\n {...props}\n >\n <RadioDot />\n </BaseRadio.Root>\n );\n}\n\nexport interface RadioFieldProps<Value = string> extends RadioProps<Value> {\n /** The option's text. */\n label: ReactNode;\n}\n\n/**\n * Radio with its label: a row at least 44px high, all of it pressable, with the\n * text 8px after the radio.\n */\nexport function RadioField<Value = string>({\n label,\n className,\n ...props\n}: RadioFieldProps<Value>): React.JSX.Element {\n return (\n <FieldItemLabel\n data-slot=\"radio-field\"\n className={cn(\n \"flex min-h-11 cursor-pointer items-start gap-2 py-3 text-label-default text-text-primary\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:text-text-disabled\",\n className,\n )}\n >\n <Radio {...props} />\n <span>{label}</span>\n </FieldItemLabel>\n );\n}\n\nexport interface RadioGroupProps<Value = string>\n extends Omit<BaseRadioGroup.Props<Value>, \"className\"> {\n className?: string;\n}\n\n/**\n * Group of radios with arrow-key navigation. Name it with `aria-labelledby`, or put\n * it inside `Field` (Control=Custom), which labels it and shows the message under it.\n * Don't preselect an option unless there is a real default.\n */\nexport function RadioGroup<Value = string>({\n className,\n ...props\n}: RadioGroupProps<Value>): React.JSX.Element {\n useFieldGroupLabel();\n\n return (\n <BaseRadioGroup\n data-slot=\"radio-group\"\n className={cn(\"flex flex-col\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseRadio","BaseRadioGroup"],"mappings":";;;;;;;AASO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA;AAAA;AAAA,MAGR,YAAY;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,YAAY;AAAA,IAAA;AAAA,EACd;AAEJ;AAGO,SAAS,WAA8B;AAC5C,SACE,oBAACA,QAAU,WAAV,EAAoB,WAAU,sCAAA,CAAsC;AAEzE;AAeO,SAAS,MAAsB;AAAA,EACpC;AAAA,EACA,GAAG;AACL,GAAyC;AACvC,SACE;AAAA,IAACA,QAAU;AAAA,IAAV;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAA,GAAiB,SAAS;AAAA,MACvC,GAAG;AAAA,MAEJ,8BAAC,UAAA,CAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAGhB;AAWO,SAAS,WAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAO,GAAG,OAAO;AAAA,QAClB,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGnB;AAYO,SAAS,WAA2B;AAAA,EACzC;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,qBAAA;AAEA,SACE;AAAA,IAACC;AAAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iBAAiB,SAAS;AAAA,MACvC,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
1
+ {"version":3,"file":"radio.js","sources":["../../../src/components/radio/radio.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport { RadioGroup as BaseRadioGroup } from \"@base-ui/react/radio-group\";\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { FieldItemLabel } from \"./field-item-label\";\nimport { useFieldGroupLabel } from \"./use-field-group-label\";\n\nexport const radioVariants = cva(\n [\n \"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full border\",\n \"border-border-input bg-background interactive\",\n \"not-data-checked:not-data-disabled:hover:border-text-brand\",\n \"data-checked:border-brand data-checked:bg-brand\",\n \"data-invalid:data-unchecked:border-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border data-disabled:bg-surface\",\n \"data-disabled:data-checked:border-border-strong data-disabled:data-checked:bg-border-strong\",\n ],\n {\n variants: {\n // Off when the focus ring and the hit area belong to a bigger container that\n // wraps the radio (SelectionCard).\n standalone: {\n true: \"focus-ring touch-target\",\n false: \"\",\n },\n },\n defaultVariants: {\n standalone: true,\n },\n },\n);\n\n/** The dot of a checked radio: 8px in text-inverse. */\nexport function RadioDot(): React.JSX.Element {\n return (\n <BaseRadio.Indicator className=\"size-2 rounded-full bg-text-inverse\" />\n );\n}\n\nexport interface RadioProps<Value = string>\n extends Omit<BaseRadio.Root.Props<Value>, \"className\" | \"children\"> {\n className?: string;\n}\n\n/**\n * Single choice inside a `RadioGroup`. The label lives outside: use `RadioField`, or\n * wrap radio and text in a `<label>`.\n *\n * A radio never unticks itself. If the person can go back to \"none\", that is a\n * Checkbox or an explicit \"Ninguna\" option. The drawing is 20×20; the hit area is\n * 44×44, and the whole row with `RadioField`.\n */\nexport function Radio<Value = string>({\n className,\n ...props\n}: RadioProps<Value>): React.JSX.Element {\n return (\n <BaseRadio.Root\n data-slot=\"radio\"\n className={cn(radioVariants(), className)}\n {...props}\n >\n <RadioDot />\n </BaseRadio.Root>\n );\n}\n\nexport interface RadioFieldProps<Value = string> extends RadioProps<Value> {\n /** The option's text. */\n label: ReactNode;\n}\n\n/**\n * Radio with its label: a row at least 44px high, all of it pressable, with the\n * text 8px after the radio.\n */\nexport function RadioField<Value = string>({\n label,\n className,\n ...props\n}: RadioFieldProps<Value>): React.JSX.Element {\n return (\n <FieldItemLabel\n data-slot=\"radio-field\"\n className={cn(\n \"flex min-h-11 cursor-pointer items-start gap-2 py-3 text-label-default text-text-primary\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:text-text-disabled\",\n className,\n )}\n >\n <Radio {...props} />\n <span>{label}</span>\n </FieldItemLabel>\n );\n}\n\nexport interface RadioGroupProps<Value = string>\n extends Omit<BaseRadioGroup.Props<Value>, \"className\"> {\n className?: string;\n}\n\n/**\n * Group of radios with arrow-key navigation. Name it with `aria-labelledby`, or put\n * it inside `Field` (Control=Custom), which labels it and shows the message under it.\n * Don't preselect an option unless there is a real default.\n */\nexport function RadioGroup<Value = string>({\n className,\n ...props\n}: RadioGroupProps<Value>): React.JSX.Element {\n useFieldGroupLabel();\n\n return (\n <BaseRadioGroup\n data-slot=\"radio-group\"\n className={cn(\"flex flex-col\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseRadio","BaseRadioGroup"],"mappings":";;;;;;;AASO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA;AAAA;AAAA,MAGR,YAAY;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,YAAY;AAAA,IAAA;AAAA,EACd;AAEJ;AAGO,SAAS,WAA8B;AAC5C,SACE,oBAACA,QAAU,WAAV,EAAoB,WAAU,sCAAA,CAAsC;AAEzE;AAeO,SAAS,MAAsB;AAAA,EACpC;AAAA,EACA,GAAG;AACL,GAAyC;AACvC,SACE;AAAA,IAACA,QAAU;AAAA,IAAV;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAA,GAAiB,SAAS;AAAA,MACvC,GAAG;AAAA,MAEJ,8BAAC,UAAA,CAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAGhB;AAWO,SAAS,WAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAO,GAAG,OAAO;AAAA,QAClB,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGnB;AAYO,SAAS,WAA2B;AAAA,EACzC;AAAA,EACA,GAAG;AACL,GAA8C;AAC5C,qBAAA;AAEA,SACE;AAAA,IAACC;AAAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iBAAiB,SAAS;AAAA,MACvC,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
@@ -24,7 +24,7 @@ function SegmentedControl({
24
24
  "data-slot": "segmented-control-segment",
25
25
  className: cn(
26
26
  "inline-flex min-w-0 cursor-pointer select-none items-center justify-center rounded-md px-3",
27
- "text-label-default text-text-secondary transition-colors",
27
+ "text-label-default text-text-secondary interactive",
28
28
  "not-data-disabled:not-data-checked:hover:text-text-primary",
29
29
  "data-checked:bg-background data-checked:text-label-strong data-checked:text-text-primary data-checked:shadow-xs",
30
30
  "data-disabled:cursor-not-allowed data-disabled:text-text-disabled",
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.js","sources":["../../../src/components/segmented-control/segmented-control.tsx"],"sourcesContent":["import { Radio } from \"@base-ui/react/radio\";\nimport { RadioGroup } from \"@base-ui/react/radio-group\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface SegmentedControlOption<Value extends string = string> {\n value: Value;\n /** Short: one or two words. With long labels the segments get cut on mobile. */\n label: ReactNode;\n disabled?: boolean;\n}\n\n/** Two or three segments, never more: the type doesn't let you build a fourth. */\nexport type SegmentedControlOptions<Value extends string = string> =\n | readonly [SegmentedControlOption<Value>, SegmentedControlOption<Value>]\n | readonly [\n SegmentedControlOption<Value>,\n SegmentedControlOption<Value>,\n SegmentedControlOption<Value>,\n ];\n\nexport interface SegmentedControlProps<Value extends string = string>\n extends Omit<RadioGroup.Props<Value>, \"className\" | \"children\"> {\n /** 2 or 3 options. For more use a `Select` or a row of `Chip`s. */\n options: SegmentedControlOptions<Value>;\n className?: string;\n}\n\n/**\n * Exclusive filter of the SAME list: Todos / No leídos in Chats.\n *\n * - It filters, it doesn't switch views: it's a `radiogroup` of `radio`s with\n * `aria-checked` (←/→ move the selection), not a tablist. To change what you are\n * looking at, use `Tabs`.\n * - Name it with `aria-label` (\"Filtrar chats\") or `aria-labelledby`.\n * - The active segment is told apart by shape, not only colour: the only white pill\n * with a shadow on the grey track, label in Medium. Inactive `text-secondary` on\n * `surface-raised` is 4.60:1 — don't lighten the track.\n * - Every segment is the same width even if the labels aren't, so the control\n * doesn't jump when the language changes. It hugs the longest label; pass\n * `className=\"w-full\"` to fill its container.\n */\nexport function SegmentedControl<Value extends string>({\n options,\n className,\n ...props\n}: SegmentedControlProps<Value>): React.JSX.Element {\n return (\n <RadioGroup\n data-slot=\"segmented-control\"\n className={cn(\n \"relative inline-grid h-control-md auto-cols-fr grid-flow-col gap-1 rounded-lg bg-surface-raised p-1\",\n className,\n )}\n {...props}\n >\n {options.map((option) => (\n <Radio.Root\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n data-slot=\"segmented-control-segment\"\n className={cn(\n \"inline-flex min-w-0 cursor-pointer select-none items-center justify-center rounded-md px-3\",\n \"text-label-default text-text-secondary transition-colors\",\n \"not-data-disabled:not-data-checked:hover:text-text-primary\",\n \"data-checked:bg-background data-checked:text-label-strong data-checked:text-text-primary data-checked:shadow-xs\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n // 32px drawn, 44px hit area.\n \"focus-ring touch-target\",\n )}\n >\n <span\n // An invisible Medium copy of the label reserves the selected width, so\n // the control doesn't jump when the selection changes.\n data-label={\n typeof option.label === \"string\" ? option.label : undefined\n }\n className=\"flex min-w-0 flex-col after:invisible after:h-0 after:overflow-hidden after:text-label-strong after:content-[attr(data-label)]\"\n >\n <span className=\"truncate\">{option.label}</span>\n </span>\n </Radio.Root>\n ))}\n </RadioGroup>\n );\n}\n"],"names":[],"mappings":";;;;AA2CO,SAAS,iBAAuC;AAAA,EACrD;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoD;AAClD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA,QAAQ,IAAI,CAAC,WACZ;AAAA,QAAC,MAAM;AAAA,QAAN;AAAA,UAEC,OAAO,OAAO;AAAA,UACd,UAAU,OAAO;AAAA,UACjB,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,YAEA;AAAA,UAAA;AAAA,UAGF,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cAGC,cACE,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,cAEpD,WAAU;AAAA,cAEV,UAAA,oBAAC,QAAA,EAAK,WAAU,YAAY,iBAAO,MAAA,CAAM;AAAA,YAAA;AAAA,UAAA;AAAA,QAC3C;AAAA,QAvBK,OAAO;AAAA,MAAA,CAyBf;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"segmented-control.js","sources":["../../../src/components/segmented-control/segmented-control.tsx"],"sourcesContent":["import { Radio } from \"@base-ui/react/radio\";\nimport { RadioGroup } from \"@base-ui/react/radio-group\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface SegmentedControlOption<Value extends string = string> {\n value: Value;\n /** Short: one or two words. With long labels the segments get cut on mobile. */\n label: ReactNode;\n disabled?: boolean;\n}\n\n/** Two or three segments, never more: the type doesn't let you build a fourth. */\nexport type SegmentedControlOptions<Value extends string = string> =\n | readonly [SegmentedControlOption<Value>, SegmentedControlOption<Value>]\n | readonly [\n SegmentedControlOption<Value>,\n SegmentedControlOption<Value>,\n SegmentedControlOption<Value>,\n ];\n\nexport interface SegmentedControlProps<Value extends string = string>\n extends Omit<RadioGroup.Props<Value>, \"className\" | \"children\"> {\n /** 2 or 3 options. For more use a `Select` or a row of `Chip`s. */\n options: SegmentedControlOptions<Value>;\n className?: string;\n}\n\n/**\n * Exclusive filter of the SAME list: Todos / No leídos in Chats.\n *\n * - It filters, it doesn't switch views: it's a `radiogroup` of `radio`s with\n * `aria-checked` (←/→ move the selection), not a tablist. To change what you are\n * looking at, use `Tabs`.\n * - Name it with `aria-label` (\"Filtrar chats\") or `aria-labelledby`.\n * - The active segment is told apart by shape, not only colour: the only white pill\n * with a shadow on the grey track, label in Medium. Inactive `text-secondary` on\n * `surface-raised` is 4.60:1 — don't lighten the track.\n * - Every segment is the same width even if the labels aren't, so the control\n * doesn't jump when the language changes. It hugs the longest label; pass\n * `className=\"w-full\"` to fill its container.\n */\nexport function SegmentedControl<Value extends string>({\n options,\n className,\n ...props\n}: SegmentedControlProps<Value>): React.JSX.Element {\n return (\n <RadioGroup\n data-slot=\"segmented-control\"\n className={cn(\n \"relative inline-grid h-control-md auto-cols-fr grid-flow-col gap-1 rounded-lg bg-surface-raised p-1\",\n className,\n )}\n {...props}\n >\n {options.map((option) => (\n <Radio.Root\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n data-slot=\"segmented-control-segment\"\n className={cn(\n \"inline-flex min-w-0 cursor-pointer select-none items-center justify-center rounded-md px-3\",\n \"text-label-default text-text-secondary interactive\",\n \"not-data-disabled:not-data-checked:hover:text-text-primary\",\n \"data-checked:bg-background data-checked:text-label-strong data-checked:text-text-primary data-checked:shadow-xs\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n // 32px drawn, 44px hit area.\n \"focus-ring touch-target\",\n )}\n >\n <span\n // An invisible Medium copy of the label reserves the selected width, so\n // the control doesn't jump when the selection changes.\n data-label={\n typeof option.label === \"string\" ? option.label : undefined\n }\n className=\"flex min-w-0 flex-col after:invisible after:h-0 after:overflow-hidden after:text-label-strong after:content-[attr(data-label)]\"\n >\n <span className=\"truncate\">{option.label}</span>\n </span>\n </Radio.Root>\n ))}\n </RadioGroup>\n );\n}\n"],"names":[],"mappings":";;;;AA2CO,SAAS,iBAAuC;AAAA,EACrD;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoD;AAClD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA,QAAQ,IAAI,CAAC,WACZ;AAAA,QAAC,MAAM;AAAA,QAAN;AAAA,UAEC,OAAO,OAAO;AAAA,UACd,UAAU,OAAO;AAAA,UACjB,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,YAEA;AAAA,UAAA;AAAA,UAGF,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cAGC,cACE,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,cAEpD,WAAU;AAAA,cAEV,UAAA,oBAAC,QAAA,EAAK,WAAU,YAAY,iBAAO,MAAA,CAAM;AAAA,YAAA;AAAA,UAAA;AAAA,QAC3C;AAAA,QAvBK,OAAO;AAAA,MAAA,CAyBf;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -77,7 +77,7 @@ function Select({
77
77
  // shadow doesn't show (dark backgrounds, poor screens).
78
78
  "max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto",
79
79
  "rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-none",
80
- "transition-[opacity,scale] duration-150 motion-reduce:transition-none",
80
+ "transition-[opacity,scale] duration-150 ease-out-soft motion-reduce:transition-none",
81
81
  "data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
82
82
  ),
83
83
  children: /* @__PURE__ */ jsx(Select$1.List, { children: children ?? items?.map((item) => /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sources":["../../../src/components/select/select.tsx"],"sourcesContent":["import { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { type ReactNode, type Ref, useLayoutEffect } from \"react\";\n\nimport { CheckIcon, ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n type ControlSize,\n controlBox,\n controlHeight,\n controlIcon,\n controlRow,\n triggerControlStates,\n} from \"../input/control-styles\";\n\nexport interface SelectOption<Value> {\n value: Value;\n label: ReactNode;\n disabled?: boolean;\n}\n\nexport interface SelectProps<Value>\n extends Omit<\n BaseSelect.Root.Props<Value, false>,\n \"multiple\" | \"items\" | \"children\"\n > {\n /** The options. Also lets the trigger show the label of the chosen value. */\n items?: readonly SelectOption<Value>[];\n /** Custom options (`SelectItem`) instead of `items`. */\n children?: ReactNode;\n /** Shown in text-secondary while nothing is chosen: \"Elige una opción\". */\n placeholder?: ReactNode;\n /** `default` 40px, `small` 36px: the same heights as Input and Button. */\n size?: ControlSize;\n /** Red 2px border and `aria-invalid`. The message goes under it, in `Field`. */\n invalid?: boolean;\n /** Content icon before the value. The chevron is structure, not a prop. */\n leadingIcon?: ReactNode;\n /** Applied to the trigger. */\n className?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * Dropdown with a styled list (Base UI Select). The trigger has exactly the geometry\n * of Input, so a field and a select in the same row only differ by the chevron.\n *\n * The open list is not in Figma on purpose. Prefer `NativeSelect` on mobile and\n * whenever the options are plain text: it opens the operating system's picker and\n * is accessible out of the box. Use this one on desktop screens (the clinics admin)\n * where the list has to look like MediCore. Options that an `<option>` can't draw\n * (a doctor with photo and licence number) are a `Menu`, not this.\n *\n * Name it with `Field`'s label, or with `aria-label` when there is no visible label.\n */\nexport function Select<Value>({\n items,\n children,\n placeholder,\n size = \"default\",\n invalid = false,\n leadingIcon,\n className,\n disabled,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ref,\n ...props\n}: SelectProps<Value>): React.JSX.Element {\n const setNativeLabel = useFieldContext()?.setNativeLabel;\n\n // A native <label> around a button would open the list on click: Field renders a\n // non-native label while a Select is inside.\n useLayoutEffect(() => {\n if (!setNativeLabel) {\n return undefined;\n }\n setNativeLabel(false);\n return () => setNativeLabel(true);\n }, [setNativeLabel]);\n\n return (\n <BaseSelect.Root items={items} disabled={disabled} {...props}>\n <BaseSelect.Trigger\n ref={ref}\n data-slot=\"select\"\n // Only when given: an explicit undefined would override Field's aria-labelledby.\n {...(ariaLabel !== undefined && { \"aria-label\": ariaLabel })}\n {...(ariaLabelledBy !== undefined && {\n \"aria-labelledby\": ariaLabelledBy,\n })}\n aria-describedby={ariaDescribedBy}\n aria-invalid={invalid && !disabled ? true : undefined}\n className={cn(\n \"group/control cursor-pointer text-start\",\n controlBox,\n controlRow,\n controlHeight[size],\n triggerControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseSelect.Value\n placeholder={placeholder}\n className=\"min-w-0 flex-1 truncate data-placeholder:text-text-secondary group-data-disabled/control:text-text-disabled\"\n />\n <BaseSelect.Icon className=\"flex shrink-0 text-text-primary group-data-disabled/control:text-text-disabled\">\n <ChevronDownIcon size=\"sm\" />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n alignItemWithTrigger={false}\n sideOffset={4}\n className=\"z-50 outline-none\"\n >\n <BaseSelect.Popup\n data-slot=\"select-popup\"\n className={cn(\n // Same surface as Menu: shadow-md to lift it and a border for when the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto\",\n \"rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-none\",\n \"transition-[opacity,scale] duration-150 motion-reduce:transition-none\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0\",\n )}\n >\n <BaseSelect.List>\n {children ??\n items?.map((item) => (\n <SelectItem\n key={String(item.value)}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </SelectItem>\n ))}\n </BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n </BaseSelect.Root>\n );\n}\n\nexport interface SelectItemProps\n extends Omit<BaseSelect.Item.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * One option of `Select`: 40px rows like MenuItem (a list is used with a finger on\n * it), surface on hover / arrow keys, a check on the chosen one.\n */\nexport function SelectItem({\n className,\n children,\n ...props\n}: SelectItemProps): React.JSX.Element {\n return (\n <BaseSelect.Item\n data-slot=\"select-item\"\n className={cn(\n \"flex h-control-md cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-label-default text-text-primary outline-none\",\n \"data-highlighted:bg-surface focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n className,\n )}\n {...props}\n >\n <BaseSelect.ItemText className=\"min-w-0 flex-1 truncate\">\n {children}\n </BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className=\"flex shrink-0 text-text-brand\">\n <CheckIcon size=\"sm\" />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n"],"names":["BaseSelect"],"mappings":";;;;;;;;AA0DO,SAAS,OAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,QAAM,iBAAiB,mBAAmB;AAI1C,kBAAgB,MAAM;AACpB,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,IACT;AACA,mBAAe,KAAK;AACpB,WAAO,MAAM,eAAe,IAAI;AAAA,EAClC,GAAG,CAAC,cAAc,CAAC;AAEnB,8BACGA,SAAW,MAAX,EAAgB,OAAc,UAAqB,GAAG,OACrD,UAAA;AAAA,IAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QAET,GAAI,cAAc,UAAa,EAAE,cAAc,UAAA;AAAA,QAC/C,GAAI,mBAAmB,UAAa;AAAA,UACnC,mBAAmB;AAAA,QAAA;AAAA,QAErB,oBAAkB;AAAA,QAClB,gBAAc,WAAW,CAAC,WAAW,OAAO;AAAA,QAC5C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc,IAAI;AAAA,UAClB;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,eAAe,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,aAAY;AAAA,UAC3D;AAAA,YAACA,SAAW;AAAA,YAAX;AAAA,cACC;AAAA,cACA,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAEZ,oBAACA,SAAW,MAAX,EAAgB,WAAU,kFACzB,UAAA,oBAAC,iBAAA,EAAgB,MAAK,KAAA,CAAK,EAAA,CAC7B;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,oBAACA,SAAW,QAAX,EACC,UAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC,sBAAsB;AAAA,QACtB,YAAY;AAAA,QACZ,WAAU;AAAA,QAEV,UAAA;AAAA,UAACA,SAAW;AAAA,UAAX;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,oBAACA,SAAW,MAAX,EACE,sBACC,OAAO,IAAI,CAAC,SACV;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,OAAO,KAAK;AAAA,gBACZ,UAAU,KAAK;AAAA,gBAEd,UAAA,KAAK;AAAA,cAAA;AAAA,cAJD,OAAO,KAAK,KAAK;AAAA,YAAA,CAMzB,EAAA,CACL;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GACF;AAEJ;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAACA,SAAW,UAAX,EAAoB,WAAU,2BAC5B,UACH;AAAA,QACA,oBAACA,SAAW,eAAX,EAAyB,WAAU,iCAClC,UAAA,oBAAC,WAAA,EAAU,MAAK,KAAA,CAAK,EAAA,CACvB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"select.js","sources":["../../../src/components/select/select.tsx"],"sourcesContent":["import { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { type ReactNode, type Ref, useLayoutEffect } from \"react\";\n\nimport { CheckIcon, ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n type ControlSize,\n controlBox,\n controlHeight,\n controlIcon,\n controlRow,\n triggerControlStates,\n} from \"../input/control-styles\";\n\nexport interface SelectOption<Value> {\n value: Value;\n label: ReactNode;\n disabled?: boolean;\n}\n\nexport interface SelectProps<Value>\n extends Omit<\n BaseSelect.Root.Props<Value, false>,\n \"multiple\" | \"items\" | \"children\"\n > {\n /** The options. Also lets the trigger show the label of the chosen value. */\n items?: readonly SelectOption<Value>[];\n /** Custom options (`SelectItem`) instead of `items`. */\n children?: ReactNode;\n /** Shown in text-secondary while nothing is chosen: \"Elige una opción\". */\n placeholder?: ReactNode;\n /** `default` 40px, `small` 36px: the same heights as Input and Button. */\n size?: ControlSize;\n /** Red 2px border and `aria-invalid`. The message goes under it, in `Field`. */\n invalid?: boolean;\n /** Content icon before the value. The chevron is structure, not a prop. */\n leadingIcon?: ReactNode;\n /** Applied to the trigger. */\n className?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * Dropdown with a styled list (Base UI Select). The trigger has exactly the geometry\n * of Input, so a field and a select in the same row only differ by the chevron.\n *\n * The open list is not in Figma on purpose. Prefer `NativeSelect` on mobile and\n * whenever the options are plain text: it opens the operating system's picker and\n * is accessible out of the box. Use this one on desktop screens (the clinics admin)\n * where the list has to look like MediCore. Options that an `<option>` can't draw\n * (a doctor with photo and licence number) are a `Menu`, not this.\n *\n * Name it with `Field`'s label, or with `aria-label` when there is no visible label.\n */\nexport function Select<Value>({\n items,\n children,\n placeholder,\n size = \"default\",\n invalid = false,\n leadingIcon,\n className,\n disabled,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ref,\n ...props\n}: SelectProps<Value>): React.JSX.Element {\n const setNativeLabel = useFieldContext()?.setNativeLabel;\n\n // A native <label> around a button would open the list on click: Field renders a\n // non-native label while a Select is inside.\n useLayoutEffect(() => {\n if (!setNativeLabel) {\n return undefined;\n }\n setNativeLabel(false);\n return () => setNativeLabel(true);\n }, [setNativeLabel]);\n\n return (\n <BaseSelect.Root items={items} disabled={disabled} {...props}>\n <BaseSelect.Trigger\n ref={ref}\n data-slot=\"select\"\n // Only when given: an explicit undefined would override Field's aria-labelledby.\n {...(ariaLabel !== undefined && { \"aria-label\": ariaLabel })}\n {...(ariaLabelledBy !== undefined && {\n \"aria-labelledby\": ariaLabelledBy,\n })}\n aria-describedby={ariaDescribedBy}\n aria-invalid={invalid && !disabled ? true : undefined}\n className={cn(\n \"group/control cursor-pointer text-start\",\n controlBox,\n controlRow,\n controlHeight[size],\n triggerControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseSelect.Value\n placeholder={placeholder}\n className=\"min-w-0 flex-1 truncate data-placeholder:text-text-secondary group-data-disabled/control:text-text-disabled\"\n />\n <BaseSelect.Icon className=\"flex shrink-0 text-text-primary group-data-disabled/control:text-text-disabled\">\n <ChevronDownIcon size=\"sm\" />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n alignItemWithTrigger={false}\n sideOffset={4}\n className=\"z-50 outline-none\"\n >\n <BaseSelect.Popup\n data-slot=\"select-popup\"\n className={cn(\n // Same surface as Menu: shadow-md to lift it and a border for when the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto\",\n \"rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-none\",\n \"transition-[opacity,scale] duration-150 ease-out-soft motion-reduce:transition-none\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0\",\n )}\n >\n <BaseSelect.List>\n {children ??\n items?.map((item) => (\n <SelectItem\n key={String(item.value)}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </SelectItem>\n ))}\n </BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n </BaseSelect.Root>\n );\n}\n\nexport interface SelectItemProps\n extends Omit<BaseSelect.Item.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * One option of `Select`: 40px rows like MenuItem (a list is used with a finger on\n * it), surface on hover / arrow keys, a check on the chosen one.\n */\nexport function SelectItem({\n className,\n children,\n ...props\n}: SelectItemProps): React.JSX.Element {\n return (\n <BaseSelect.Item\n data-slot=\"select-item\"\n className={cn(\n \"flex h-control-md cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-label-default text-text-primary outline-none\",\n \"data-highlighted:bg-surface focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n className,\n )}\n {...props}\n >\n <BaseSelect.ItemText className=\"min-w-0 flex-1 truncate\">\n {children}\n </BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className=\"flex shrink-0 text-text-brand\">\n <CheckIcon size=\"sm\" />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n"],"names":["BaseSelect"],"mappings":";;;;;;;;AA0DO,SAAS,OAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,QAAM,iBAAiB,mBAAmB;AAI1C,kBAAgB,MAAM;AACpB,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,IACT;AACA,mBAAe,KAAK;AACpB,WAAO,MAAM,eAAe,IAAI;AAAA,EAClC,GAAG,CAAC,cAAc,CAAC;AAEnB,8BACGA,SAAW,MAAX,EAAgB,OAAc,UAAqB,GAAG,OACrD,UAAA;AAAA,IAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QAET,GAAI,cAAc,UAAa,EAAE,cAAc,UAAA;AAAA,QAC/C,GAAI,mBAAmB,UAAa;AAAA,UACnC,mBAAmB;AAAA,QAAA;AAAA,QAErB,oBAAkB;AAAA,QAClB,gBAAc,WAAW,CAAC,WAAW,OAAO;AAAA,QAC5C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc,IAAI;AAAA,UAClB;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,eAAe,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,aAAY;AAAA,UAC3D;AAAA,YAACA,SAAW;AAAA,YAAX;AAAA,cACC;AAAA,cACA,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAEZ,oBAACA,SAAW,MAAX,EAAgB,WAAU,kFACzB,UAAA,oBAAC,iBAAA,EAAgB,MAAK,KAAA,CAAK,EAAA,CAC7B;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,oBAACA,SAAW,QAAX,EACC,UAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC,sBAAsB;AAAA,QACtB,YAAY;AAAA,QACZ,WAAU;AAAA,QAEV,UAAA;AAAA,UAACA,SAAW;AAAA,UAAX;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,oBAACA,SAAW,MAAX,EACE,sBACC,OAAO,IAAI,CAAC,SACV;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,OAAO,KAAK;AAAA,gBACZ,UAAU,KAAK;AAAA,gBAEd,UAAA,KAAK;AAAA,cAAA;AAAA,cAJD,OAAO,KAAK,KAAK;AAAA,YAAA,CAMzB,EAAA,CACL;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GACF;AAEJ;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAACA,SAAW,UAAX,EAAoB,WAAU,2BAC5B,UACH;AAAA,QACA,oBAACA,SAAW,eAAX,EAAyB,WAAU,iCAClC,UAAA,oBAAC,WAAA,EAAU,MAAK,KAAA,CAAK,EAAA,CACvB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -33,7 +33,8 @@ function SelectionCard({
33
33
  {
34
34
  "data-slot": "selection-card",
35
35
  className: cn(
36
- "group/selection-card flex min-h-14 cursor-pointer items-center gap-3 rounded-lg border border-border bg-background p-4 transition-colors",
36
+ "group/selection-card flex min-h-14 cursor-pointer items-center gap-3 rounded-lg border border-border bg-background p-4 interactive",
37
+ "not-has-data-checked:not-has-data-disabled:hover:border-border-strong not-has-data-checked:not-has-data-disabled:hover:bg-surface",
37
38
  "focus-ring-within",
38
39
  // 2px border without moving the content: the 1px border plus a 1px inset ring.
39
40
  "has-data-checked:border-text-brand has-data-checked:bg-brand-subtle has-data-checked:inset-ring has-data-checked:inset-ring-text-brand",
@@ -1 +1 @@
1
- {"version":3,"file":"selection-card.js","sources":["../../../src/components/selection-card/selection-card.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport { type ReactNode, useId } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { IconContainer } from \"../icon-container\";\nimport { FieldItemLabel } from \"../radio/field-item-label\";\nimport {\n RadioDot,\n RadioGroup,\n type RadioGroupProps,\n radioVariants,\n} from \"../radio/radio\";\n\nexport interface SelectionCardGroupProps<Value = string>\n extends RadioGroupProps<Value> {}\n\n/**\n * The group of `SelectionCard`s: one choice, arrow keys to move between cards.\n * Name it with `aria-labelledby` (the question: \"¿Cómo quieres la consulta?\") or put\n * it inside `Field` (Control=Custom).\n */\nexport function SelectionCardGroup<Value = string>({\n className,\n ...props\n}: SelectionCardGroupProps<Value>): React.JSX.Element {\n return (\n <RadioGroup<Value>\n data-slot=\"selection-card-group\"\n className={cn(\"gap-3\", className)}\n {...props}\n />\n );\n}\n\nexport interface SelectionCardProps<Value = string>\n extends Omit<\n BaseRadio.Root.Props<Value>,\n \"className\" | \"children\" | \"title\"\n > {\n /** \"Videoconsulta\". It is the radio's accessible name. */\n title: ReactNode;\n /**\n * What the person needs to know to choose: \"Con cámara, desde el móvil o el\n * ordenador\". Leave it out if it adds nothing new; don't fill it in.\n */\n description?: ReactNode;\n /** A MediCore icon, shown in a 40px IconContainer that turns brand when selected. */\n icon?: ReactNode;\n className?: string;\n}\n\n/**\n * Large selectable option: consultation type, payment method, who the appointment is\n * for. Goes inside `SelectionCardGroup`.\n *\n * The whole card is the target (a `<label>` wrapping a real radio), at least 56px\n * high. Selected reads without colour: 2px border, ticked radio and the icon in brand.\n */\nexport function SelectionCard<Value = string>({\n title,\n description,\n icon,\n className,\n ...props\n}: SelectionCardProps<Value>): React.JSX.Element {\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n\n return (\n <FieldItemLabel\n data-slot=\"selection-card\"\n className={cn(\n \"group/selection-card flex min-h-14 cursor-pointer items-center gap-3 rounded-lg border border-border bg-background p-4 transition-colors\",\n \"focus-ring-within\",\n // 2px border without moving the content: the 1px border plus a 1px inset ring.\n \"has-data-checked:border-text-brand has-data-checked:bg-brand-subtle has-data-checked:inset-ring has-data-checked:inset-ring-text-brand\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:border-border-subtle has-data-disabled:bg-surface\",\n className,\n )}\n >\n {icon && (\n <IconContainer\n size={40}\n tone=\"neutral\"\n icon={icon}\n className=\"group-has-data-checked/selection-card:bg-brand-subtle group-has-data-checked/selection-card:text-text-brand group-has-data-disabled/selection-card:opacity-50\"\n />\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n id={titleId}\n className=\"text-label-strong text-text-primary group-has-data-disabled/selection-card:text-text-disabled\"\n >\n {title}\n </span>\n {description && (\n <span\n id={descriptionId}\n className=\"text-body-small text-text-secondary group-has-data-checked/selection-card:text-text-primary group-has-data-disabled/selection-card:text-text-disabled\"\n >\n {description}\n </span>\n )}\n </span>\n <BaseRadio.Root\n data-slot=\"selection-card-radio\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n className={radioVariants({ standalone: false })}\n {...props}\n >\n <RadioDot />\n </BaseRadio.Root>\n </FieldItemLabel>\n );\n}\n"],"names":["BaseRadio"],"mappings":";;;;;;;AAqBO,SAAS,mBAAmC;AAAA,EACjD;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,SAAS,SAAS;AAAA,MAC/B,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AA0BO,SAAS,cAA8B;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,QAAM,KAAK,MAAA;AACX,QAAM,UAAU,GAAG,EAAE;AACrB,QAAM,gBAAgB,GAAG,EAAE;AAE3B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL;AAAA,YACA,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,qBAAC,QAAA,EAAK,WAAU,wCACd,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAEF,eACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,GAEJ;AAAA,QACA;AAAA,UAACA,MAAU;AAAA,UAAV;AAAA,YACC,aAAU;AAAA,YACV,mBAAiB;AAAA,YACjB,oBAAkB,cAAc,gBAAgB;AAAA,YAChD,WAAW,cAAc,EAAE,YAAY,OAAO;AAAA,YAC7C,GAAG;AAAA,YAEJ,8BAAC,UAAA,CAAA,CAAS;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"selection-card.js","sources":["../../../src/components/selection-card/selection-card.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport { type ReactNode, useId } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { IconContainer } from \"../icon-container\";\nimport { FieldItemLabel } from \"../radio/field-item-label\";\nimport {\n RadioDot,\n RadioGroup,\n type RadioGroupProps,\n radioVariants,\n} from \"../radio/radio\";\n\nexport interface SelectionCardGroupProps<Value = string>\n extends RadioGroupProps<Value> {}\n\n/**\n * The group of `SelectionCard`s: one choice, arrow keys to move between cards.\n * Name it with `aria-labelledby` (the question: \"¿Cómo quieres la consulta?\") or put\n * it inside `Field` (Control=Custom).\n */\nexport function SelectionCardGroup<Value = string>({\n className,\n ...props\n}: SelectionCardGroupProps<Value>): React.JSX.Element {\n return (\n <RadioGroup<Value>\n data-slot=\"selection-card-group\"\n className={cn(\"gap-3\", className)}\n {...props}\n />\n );\n}\n\nexport interface SelectionCardProps<Value = string>\n extends Omit<\n BaseRadio.Root.Props<Value>,\n \"className\" | \"children\" | \"title\"\n > {\n /** \"Videoconsulta\". It is the radio's accessible name. */\n title: ReactNode;\n /**\n * What the person needs to know to choose: \"Con cámara, desde el móvil o el\n * ordenador\". Leave it out if it adds nothing new; don't fill it in.\n */\n description?: ReactNode;\n /** A MediCore icon, shown in a 40px IconContainer that turns brand when selected. */\n icon?: ReactNode;\n className?: string;\n}\n\n/**\n * Large selectable option: consultation type, payment method, who the appointment is\n * for. Goes inside `SelectionCardGroup`.\n *\n * The whole card is the target (a `<label>` wrapping a real radio), at least 56px\n * high. Selected reads without colour: 2px border, ticked radio and the icon in brand.\n */\nexport function SelectionCard<Value = string>({\n title,\n description,\n icon,\n className,\n ...props\n}: SelectionCardProps<Value>): React.JSX.Element {\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n\n return (\n <FieldItemLabel\n data-slot=\"selection-card\"\n className={cn(\n \"group/selection-card flex min-h-14 cursor-pointer items-center gap-3 rounded-lg border border-border bg-background p-4 interactive\",\n \"not-has-data-checked:not-has-data-disabled:hover:border-border-strong not-has-data-checked:not-has-data-disabled:hover:bg-surface\",\n \"focus-ring-within\",\n // 2px border without moving the content: the 1px border plus a 1px inset ring.\n \"has-data-checked:border-text-brand has-data-checked:bg-brand-subtle has-data-checked:inset-ring has-data-checked:inset-ring-text-brand\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:border-border-subtle has-data-disabled:bg-surface\",\n className,\n )}\n >\n {icon && (\n <IconContainer\n size={40}\n tone=\"neutral\"\n icon={icon}\n className=\"group-has-data-checked/selection-card:bg-brand-subtle group-has-data-checked/selection-card:text-text-brand group-has-data-disabled/selection-card:opacity-50\"\n />\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n id={titleId}\n className=\"text-label-strong text-text-primary group-has-data-disabled/selection-card:text-text-disabled\"\n >\n {title}\n </span>\n {description && (\n <span\n id={descriptionId}\n className=\"text-body-small text-text-secondary group-has-data-checked/selection-card:text-text-primary group-has-data-disabled/selection-card:text-text-disabled\"\n >\n {description}\n </span>\n )}\n </span>\n <BaseRadio.Root\n data-slot=\"selection-card-radio\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n className={radioVariants({ standalone: false })}\n {...props}\n >\n <RadioDot />\n </BaseRadio.Root>\n </FieldItemLabel>\n );\n}\n"],"names":["BaseRadio"],"mappings":";;;;;;;AAqBO,SAAS,mBAAmC;AAAA,EACjD;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,SAAS,SAAS;AAAA,MAC/B,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AA0BO,SAAS,cAA8B;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,QAAM,KAAK,MAAA;AACX,QAAM,UAAU,GAAG,EAAE;AACrB,QAAM,gBAAgB,GAAG,EAAE;AAE3B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL;AAAA,YACA,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,qBAAC,QAAA,EAAK,WAAU,wCACd,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAEF,eACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAI;AAAA,cACJ,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,GAEJ;AAAA,QACA;AAAA,UAACA,MAAU;AAAA,UAAV;AAAA,YACC,aAAU;AAAA,YACV,mBAAiB;AAAA,YACjB,oBAAkB,cAAc,gBAAgB;AAAA,YAChD,WAAW,cAAc,EAAE,YAAY,OAAO;AAAA,YAC7C,GAAG;AAAA,YAEJ,8BAAC,UAAA,CAAA,CAAS;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -10,11 +10,12 @@ function Switch({
10
10
  {
11
11
  "data-slot": "switch",
12
12
  className: cn(
13
- "inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full p-0.5 transition-colors",
13
+ "inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full p-0.5 interactive",
14
14
  // Off is not a solid grey: surface with an inner border-input edge (5.41:1)
15
15
  // reads as "off"; a grey fill that passes 3:1 already reads as "on".
16
16
  "bg-surface inset-ring inset-ring-border-input",
17
17
  "data-checked:bg-brand data-checked:inset-ring-brand",
18
+ "not-data-checked:not-data-disabled:hover:inset-ring-border-strong",
18
19
  "data-disabled:cursor-not-allowed data-disabled:inset-ring-border",
19
20
  "data-disabled:data-checked:bg-border-strong data-disabled:data-checked:inset-ring-border-strong",
20
21
  "focus-ring touch-target",
@@ -26,7 +27,7 @@ function Switch({
26
27
  {
27
28
  className: cn(
28
29
  "size-5 rounded-full border border-border-input bg-text-inverse",
29
- "transition-transform motion-reduce:transition-none",
30
+ "transition-transform duration-200 ease-out-soft motion-reduce:transition-none",
30
31
  // Only Off has an edge: on brand the thumb is already 10.32:1.
31
32
  "data-checked:translate-x-5 data-checked:border-transparent",
32
33
  "data-disabled:border-border data-disabled:data-checked:border-transparent"
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","sources":["../../../src/components/switch/switch.tsx"],"sourcesContent":["import { Switch as BaseSwitch } from \"@base-ui/react/switch\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface SwitchProps\n extends Omit<BaseSwitch.Root.Props, \"className\" | \"children\"> {\n className?: string;\n}\n\n/**\n * Immediate-apply switch: when released, the change is already made. If the change\n * needs a save button, it isn't a Switch, it is a Checkbox.\n *\n * The label goes outside and to the left, with the switch aligned to the right of\n * the row: use `SwitchField`, or pass `aria-label`. Track 44×24 and thumb 20×20 are\n * fixed on purpose (any other pair stops being a circle); the hit area is 44 high.\n */\nexport function Switch({\n className,\n ...props\n}: SwitchProps): React.JSX.Element {\n return (\n <BaseSwitch.Root\n data-slot=\"switch\"\n className={cn(\n \"inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full p-0.5 transition-colors\",\n // Off is not a solid grey: surface with an inner border-input edge (5.41:1)\n // reads as \"off\"; a grey fill that passes 3:1 already reads as \"on\".\n \"bg-surface inset-ring inset-ring-border-input\",\n \"data-checked:bg-brand data-checked:inset-ring-brand\",\n \"data-disabled:cursor-not-allowed data-disabled:inset-ring-border\",\n \"data-disabled:data-checked:bg-border-strong data-disabled:data-checked:inset-ring-border-strong\",\n \"focus-ring touch-target\",\n className,\n )}\n {...props}\n >\n <BaseSwitch.Thumb\n className={cn(\n \"size-5 rounded-full border border-border-input bg-text-inverse\",\n \"transition-transform motion-reduce:transition-none\",\n // Only Off has an edge: on brand the thumb is already 10.32:1.\n \"data-checked:translate-x-5 data-checked:border-transparent\",\n \"data-disabled:border-border data-disabled:data-checked:border-transparent\",\n )}\n />\n </BaseSwitch.Root>\n );\n}\n\nexport interface SwitchFieldProps extends SwitchProps {\n /** What the switch turns on or off: \"Recordatorios por SMS\". */\n label: ReactNode;\n}\n\n/**\n * A settings row: label on the left, switch on the right, at least 44px high and\n * pressable as a whole.\n */\nexport function SwitchField({\n label,\n className,\n ...props\n}: SwitchFieldProps): React.JSX.Element {\n return (\n // biome-ignore lint/a11y/noLabelWithoutControl: the Base UI switch inside renders its own <input>, so the wrapping label is associated.\n <label\n data-slot=\"switch-field\"\n className={cn(\n \"flex min-h-11 cursor-pointer items-center justify-between gap-4 text-label-default text-text-primary\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:text-text-disabled\",\n className,\n )}\n >\n <span>{label}</span>\n <Switch {...props} />\n </label>\n );\n}\n"],"names":["BaseSwitch"],"mappings":";;;AAkBO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAmC;AACjC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAACA,SAAW;AAAA,QAAX;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA;AAAA;AAAA,YAEA;AAAA,YACA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;AAWO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwC;AACtC;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,UACb,oBAAC,QAAA,EAAQ,GAAG,MAAA,CAAO;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA;AAGzB;"}
1
+ {"version":3,"file":"switch.js","sources":["../../../src/components/switch/switch.tsx"],"sourcesContent":["import { Switch as BaseSwitch } from \"@base-ui/react/switch\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface SwitchProps\n extends Omit<BaseSwitch.Root.Props, \"className\" | \"children\"> {\n className?: string;\n}\n\n/**\n * Immediate-apply switch: when released, the change is already made. If the change\n * needs a save button, it isn't a Switch, it is a Checkbox.\n *\n * The label goes outside and to the left, with the switch aligned to the right of\n * the row: use `SwitchField`, or pass `aria-label`. Track 44×24 and thumb 20×20 are\n * fixed on purpose (any other pair stops being a circle); the hit area is 44 high.\n */\nexport function Switch({\n className,\n ...props\n}: SwitchProps): React.JSX.Element {\n return (\n <BaseSwitch.Root\n data-slot=\"switch\"\n className={cn(\n \"inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full p-0.5 interactive\",\n // Off is not a solid grey: surface with an inner border-input edge (5.41:1)\n // reads as \"off\"; a grey fill that passes 3:1 already reads as \"on\".\n \"bg-surface inset-ring inset-ring-border-input\",\n \"data-checked:bg-brand data-checked:inset-ring-brand\",\n \"not-data-checked:not-data-disabled:hover:inset-ring-border-strong\",\n \"data-disabled:cursor-not-allowed data-disabled:inset-ring-border\",\n \"data-disabled:data-checked:bg-border-strong data-disabled:data-checked:inset-ring-border-strong\",\n \"focus-ring touch-target\",\n className,\n )}\n {...props}\n >\n <BaseSwitch.Thumb\n className={cn(\n \"size-5 rounded-full border border-border-input bg-text-inverse\",\n \"transition-transform duration-200 ease-out-soft motion-reduce:transition-none\",\n // Only Off has an edge: on brand the thumb is already 10.32:1.\n \"data-checked:translate-x-5 data-checked:border-transparent\",\n \"data-disabled:border-border data-disabled:data-checked:border-transparent\",\n )}\n />\n </BaseSwitch.Root>\n );\n}\n\nexport interface SwitchFieldProps extends SwitchProps {\n /** What the switch turns on or off: \"Recordatorios por SMS\". */\n label: ReactNode;\n}\n\n/**\n * A settings row: label on the left, switch on the right, at least 44px high and\n * pressable as a whole.\n */\nexport function SwitchField({\n label,\n className,\n ...props\n}: SwitchFieldProps): React.JSX.Element {\n return (\n // biome-ignore lint/a11y/noLabelWithoutControl: the Base UI switch inside renders its own <input>, so the wrapping label is associated.\n <label\n data-slot=\"switch-field\"\n className={cn(\n \"flex min-h-11 cursor-pointer items-center justify-between gap-4 text-label-default text-text-primary\",\n \"has-data-disabled:cursor-not-allowed has-data-disabled:text-text-disabled\",\n className,\n )}\n >\n <span>{label}</span>\n <Switch {...props} />\n </label>\n );\n}\n"],"names":["BaseSwitch"],"mappings":";;;AAkBO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAmC;AACjC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAACA,SAAW;AAAA,QAAX;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA;AAAA;AAAA,YAEA;AAAA,YACA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;AAWO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwC;AACtC;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,UACb,oBAAC,QAAA,EAAQ,GAAG,MAAA,CAAO;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA;AAGzB;"}
@@ -77,7 +77,7 @@ function TableRow({
77
77
  {
78
78
  "data-slot": "table-row",
79
79
  className: cn(
80
- "transition-colors [tbody_&:last-child>td]:border-b-0 [tbody_&:hover>td]:bg-surface",
80
+ "interactive [tbody_&:last-child>td]:border-b-0 [tbody_&:hover>td]:bg-surface",
81
81
  className
82
82
  ),
83
83
  ...props
@@ -129,7 +129,7 @@ function TableHead({
129
129
  "aria-describedby": descriptionId,
130
130
  className: cn(
131
131
  "flex h-12 w-full cursor-pointer items-center gap-1 whitespace-nowrap px-4",
132
- "transition-colors hover:text-text-primary",
132
+ "interactive hover:text-text-primary",
133
133
  "focus-ring focus-visible:-outline-offset-2",
134
134
  // Numbers: the chevron goes on the inside so the label stays aligned
135
135
  // with the column.
@@ -148,7 +148,7 @@ function TableHead({
148
148
  );
149
149
  }
150
150
  const tableCellVariants = cva(
151
- "h-14 border-b border-border-subtle bg-background px-4 align-middle text-body-small text-text-primary transition-colors",
151
+ "h-14 border-b border-border-subtle bg-background px-4 align-middle text-body-small text-text-primary interactive",
152
152
  {
153
153
  variants: {
154
154
  type: {
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","sources":["../../../src/components/table/table.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport {\n type ComponentProps,\n type MouseEventHandler,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\n\nimport { ChevronDownIcon, ChevronUpIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\n/** The type decides the alignment: text and badge left, numbers right, action centred. */\nexport type TableCellType = \"text\" | \"numeric\" | \"badge\" | \"action\";\n\nexport type TableSort = \"none\" | \"ascending\" | \"descending\";\n\n/**\n * Maps a headless library's sort state to `TableSort`, e.g. TanStack's\n * `column.getIsSorted()` (`false | \"asc\" | \"desc\"`).\n */\nexport function tableSortFrom(value: false | \"asc\" | \"desc\"): TableSort {\n if (value === \"asc\") return \"ascending\";\n if (value === \"desc\") return \"descending\";\n return \"none\";\n}\n\nexport interface TableProps extends ComponentProps<\"table\"> {\n /**\n * Keeps the first column (usually the name or the date) in place while the rest\n * scrolls sideways. For dense admin tables that still have to work on a tablet.\n */\n stickyFirstColumn?: boolean;\n /** Classes for the scroll container around the `<table>` (border, radius, max height…). */\n containerClassName?: string;\n}\n\n/**\n * Semantic, composable table: `Table` › `TableHeader` / `TableBody` › `TableRow` ›\n * `TableHead` / `TableCell`. Plain elements with styles, so a headless library\n * (`@tanstack/react-table`) can drive it: map `headerGroups` to `TableRow` +\n * `TableHead` (`sort={tableSortFrom(column.getIsSorted())}`,\n * `onSort={column.getToggleSortingHandler()}`)\n * and `rows` to `TableRow` + `TableCell`.\n *\n * - Name it: `aria-label` (\"Facturas\") or `aria-labelledby` the section title.\n * - Desktop pattern (1024+). On mobile a six-column table doesn't fit: render each\n * row as a `ListRow` (title = concept, subtitle = date · specialty, trailing =\n * amount and action) or a stacked row with the actions in a menu, and switch\n * with `hidden lg:block` / `lg:hidden`. Horizontal scroll (automatic, with\n * `stickyFirstColumn` if needed) is the fallback for in-between widths, not the\n * mobile design.\n * - When it overflows, the scroll container becomes focusable so it can be scrolled\n * with the keyboard.\n */\nexport function Table({\n stickyFirstColumn = false,\n containerClassName,\n className,\n ...props\n}: TableProps): React.JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scrollable, setScrollable] = useState(false);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const update = (): void =>\n setScrollable(container.scrollWidth > container.clientWidth);\n update();\n\n const observer = new ResizeObserver(update);\n observer.observe(container);\n if (container.firstElementChild) {\n observer.observe(container.firstElementChild);\n }\n return () => observer.disconnect();\n }, []);\n\n // Only while it overflows: then it's a named region reachable with Tab, so it\n // can be scrolled with the arrow keys (WCAG 2.1.1). Otherwise it's a plain box.\n const scrollRegion = scrollable\n ? {\n role: \"region\",\n tabIndex: 0,\n \"aria-label\": props[\"aria-label\"],\n \"aria-labelledby\": props[\"aria-labelledby\"],\n }\n : {};\n\n return (\n <div\n ref={containerRef}\n data-slot=\"table-container\"\n {...scrollRegion}\n className={cn(\n \"relative w-full overflow-x-auto rounded-xl border border-border-subtle bg-background focus-ring\",\n containerClassName,\n )}\n >\n <table\n data-slot=\"table\"\n className={cn(\n // Separate borders so they travel with sticky cells.\n \"w-full border-separate border-spacing-0 text-left\",\n stickyFirstColumn &&\n \"[&_tr>:first-child]:sticky [&_tr>:first-child]:left-0 [&_tr>:first-child]:z-10 [&_tr>:first-child]:shadow-[inset_-1px_0_0_0_var(--color-border-subtle)]\",\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport interface TableHeaderProps extends ComponentProps<\"thead\"> {}\n\nexport function TableHeader(props: TableHeaderProps): React.JSX.Element {\n return <thead data-slot=\"table-header\" {...props} />;\n}\n\nexport interface TableBodyProps extends ComponentProps<\"tbody\"> {}\n\nexport function TableBody(props: TableBodyProps): React.JSX.Element {\n return <tbody data-slot=\"table-body\" {...props} />;\n}\n\nexport interface TableRowProps extends ComponentProps<\"tr\"> {}\n\n/**\n * A row. Figma has no Table/Row: a row is just cells as wide as their headers. If a\n * row opens a detail, put a link in its first cell — don't make the `<tr>` clickable.\n */\nexport function TableRow({\n className,\n ...props\n}: TableRowProps): React.JSX.Element {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"transition-colors [tbody_&:last-child>td]:border-b-0 [tbody_&:hover>td]:bg-surface\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst alignment = {\n text: \"text-left\",\n numeric: \"text-right\",\n badge: \"text-left\",\n // Shrinks to its buttons.\n action: \"w-0 whitespace-nowrap text-center\",\n} satisfies Record<TableCellType, string>;\n\nexport const tableHeadVariants = cva(\n \"h-11 border-b border-border-subtle bg-surface px-4 align-middle text-label-small text-text-secondary first:rounded-tl-xl last:rounded-tr-xl data-sorted:text-label-strong data-sorted:text-text-primary\",\n { variants: { type: alignment }, defaultVariants: { type: \"text\" } },\n);\n\nexport interface TableHeadProps extends ComponentProps<\"th\"> {\n /** Same as the column's cells, so the header lines up with them. */\n type?: TableCellType;\n /**\n * Current order of the column. The sorted column is recognised by its chevron,\n * not only by the darker label.\n */\n sort?: TableSort;\n /**\n * Makes the column sortable: the label becomes a `<button>` inside the `<th>`,\n * which carries `aria-sort`. Cycling the order is up to the app.\n */\n onSort?: MouseEventHandler<HTMLButtonElement>;\n}\n\n/** Column header (`<th scope=\"col\">`). Sortable when it gets `onSort`. */\nexport function TableHead({\n type = \"text\",\n sort = \"none\",\n onSort,\n className,\n children,\n ...props\n}: TableHeadProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const descriptionId = useId();\n const sorted = sort !== \"none\";\n const sortLabel = {\n none: labels.notSorted,\n ascending: labels.sortAscending,\n descending: labels.sortDescending,\n }[sort];\n\n return (\n <th\n data-slot=\"table-head\"\n scope=\"col\"\n aria-sort={sorted ? sort : undefined}\n data-sorted={sorted || undefined}\n className={cn(tableHeadVariants({ type }), onSort && \"p-0\", className)}\n {...props}\n >\n {onSort ? (\n <button\n type=\"button\"\n onClick={onSort}\n // The state is announced when the button gets focus, where aria-sort on\n // the <th> isn't always read.\n aria-describedby={descriptionId}\n className={cn(\n \"flex h-12 w-full cursor-pointer items-center gap-1 whitespace-nowrap px-4\",\n \"transition-colors hover:text-text-primary\",\n \"focus-ring focus-visible:-outline-offset-2\",\n // Numbers: the chevron goes on the inside so the label stays aligned\n // with the column.\n type === \"numeric\"\n ? \"flex-row-reverse\"\n : type === \"action\"\n ? \"justify-center\"\n : \"justify-start\",\n )}\n >\n <span>{children}</span>\n {sort === \"ascending\" && <ChevronUpIcon size=\"sm\" />}\n {sort === \"descending\" && <ChevronDownIcon size=\"sm\" />}\n </button>\n ) : (\n children\n )}\n {onSort && (\n <span id={descriptionId} hidden>\n {sortLabel}\n </span>\n )}\n </th>\n );\n}\n\nexport const tableCellVariants = cva(\n \"h-14 border-b border-border-subtle bg-background px-4 align-middle text-body-small text-text-primary transition-colors\",\n {\n variants: {\n type: {\n ...alignment,\n // Tabular figures so decimals line up and amounts can be compared.\n numeric: \"whitespace-nowrap text-right text-label-strong tabular-nums\",\n },\n },\n defaultVariants: { type: \"text\" },\n },\n);\n\nexport interface TableCellProps extends ComponentProps<\"td\"> {\n /**\n * - `text`: left.\n * - `numeric`: right, tabular figures. The value comes formatted by the app in\n * es-ES (\"29,00 €\", symbol after), the cell doesn't format.\n * - `badge`: left, a `Badge`.\n * - `action`: centred, a ghost small icon button whose `aria-label` carries the\n * row's context (\"Descargar factura del 5 de enero\") — twenty \"Descargar\"\n * buttons are indistinguishable for a screen reader.\n */\n type?: TableCellType;\n}\n\nexport function TableCell({\n type,\n className,\n ...props\n}: TableCellProps): React.JSX.Element {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(tableCellVariants({ type }), className)}\n {...props}\n />\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table.js","sources":["../../../src/components/table/table.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport {\n type ComponentProps,\n type MouseEventHandler,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\n\nimport { ChevronDownIcon, ChevronUpIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\n/** The type decides the alignment: text and badge left, numbers right, action centred. */\nexport type TableCellType = \"text\" | \"numeric\" | \"badge\" | \"action\";\n\nexport type TableSort = \"none\" | \"ascending\" | \"descending\";\n\n/**\n * Maps a headless library's sort state to `TableSort`, e.g. TanStack's\n * `column.getIsSorted()` (`false | \"asc\" | \"desc\"`).\n */\nexport function tableSortFrom(value: false | \"asc\" | \"desc\"): TableSort {\n if (value === \"asc\") return \"ascending\";\n if (value === \"desc\") return \"descending\";\n return \"none\";\n}\n\nexport interface TableProps extends ComponentProps<\"table\"> {\n /**\n * Keeps the first column (usually the name or the date) in place while the rest\n * scrolls sideways. For dense admin tables that still have to work on a tablet.\n */\n stickyFirstColumn?: boolean;\n /** Classes for the scroll container around the `<table>` (border, radius, max height…). */\n containerClassName?: string;\n}\n\n/**\n * Semantic, composable table: `Table` › `TableHeader` / `TableBody` › `TableRow` ›\n * `TableHead` / `TableCell`. Plain elements with styles, so a headless library\n * (`@tanstack/react-table`) can drive it: map `headerGroups` to `TableRow` +\n * `TableHead` (`sort={tableSortFrom(column.getIsSorted())}`,\n * `onSort={column.getToggleSortingHandler()}`)\n * and `rows` to `TableRow` + `TableCell`.\n *\n * - Name it: `aria-label` (\"Facturas\") or `aria-labelledby` the section title.\n * - Desktop pattern (1024+). On mobile a six-column table doesn't fit: render each\n * row as a `ListRow` (title = concept, subtitle = date · specialty, trailing =\n * amount and action) or a stacked row with the actions in a menu, and switch\n * with `hidden lg:block` / `lg:hidden`. Horizontal scroll (automatic, with\n * `stickyFirstColumn` if needed) is the fallback for in-between widths, not the\n * mobile design.\n * - When it overflows, the scroll container becomes focusable so it can be scrolled\n * with the keyboard.\n */\nexport function Table({\n stickyFirstColumn = false,\n containerClassName,\n className,\n ...props\n}: TableProps): React.JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scrollable, setScrollable] = useState(false);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const update = (): void =>\n setScrollable(container.scrollWidth > container.clientWidth);\n update();\n\n const observer = new ResizeObserver(update);\n observer.observe(container);\n if (container.firstElementChild) {\n observer.observe(container.firstElementChild);\n }\n return () => observer.disconnect();\n }, []);\n\n // Only while it overflows: then it's a named region reachable with Tab, so it\n // can be scrolled with the arrow keys (WCAG 2.1.1). Otherwise it's a plain box.\n const scrollRegion = scrollable\n ? {\n role: \"region\",\n tabIndex: 0,\n \"aria-label\": props[\"aria-label\"],\n \"aria-labelledby\": props[\"aria-labelledby\"],\n }\n : {};\n\n return (\n <div\n ref={containerRef}\n data-slot=\"table-container\"\n {...scrollRegion}\n className={cn(\n \"relative w-full overflow-x-auto rounded-xl border border-border-subtle bg-background focus-ring\",\n containerClassName,\n )}\n >\n <table\n data-slot=\"table\"\n className={cn(\n // Separate borders so they travel with sticky cells.\n \"w-full border-separate border-spacing-0 text-left\",\n stickyFirstColumn &&\n \"[&_tr>:first-child]:sticky [&_tr>:first-child]:left-0 [&_tr>:first-child]:z-10 [&_tr>:first-child]:shadow-[inset_-1px_0_0_0_var(--color-border-subtle)]\",\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport interface TableHeaderProps extends ComponentProps<\"thead\"> {}\n\nexport function TableHeader(props: TableHeaderProps): React.JSX.Element {\n return <thead data-slot=\"table-header\" {...props} />;\n}\n\nexport interface TableBodyProps extends ComponentProps<\"tbody\"> {}\n\nexport function TableBody(props: TableBodyProps): React.JSX.Element {\n return <tbody data-slot=\"table-body\" {...props} />;\n}\n\nexport interface TableRowProps extends ComponentProps<\"tr\"> {}\n\n/**\n * A row. Figma has no Table/Row: a row is just cells as wide as their headers. If a\n * row opens a detail, put a link in its first cell — don't make the `<tr>` clickable.\n */\nexport function TableRow({\n className,\n ...props\n}: TableRowProps): React.JSX.Element {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"interactive [tbody_&:last-child>td]:border-b-0 [tbody_&:hover>td]:bg-surface\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst alignment = {\n text: \"text-left\",\n numeric: \"text-right\",\n badge: \"text-left\",\n // Shrinks to its buttons.\n action: \"w-0 whitespace-nowrap text-center\",\n} satisfies Record<TableCellType, string>;\n\nexport const tableHeadVariants = cva(\n \"h-11 border-b border-border-subtle bg-surface px-4 align-middle text-label-small text-text-secondary first:rounded-tl-xl last:rounded-tr-xl data-sorted:text-label-strong data-sorted:text-text-primary\",\n { variants: { type: alignment }, defaultVariants: { type: \"text\" } },\n);\n\nexport interface TableHeadProps extends ComponentProps<\"th\"> {\n /** Same as the column's cells, so the header lines up with them. */\n type?: TableCellType;\n /**\n * Current order of the column. The sorted column is recognised by its chevron,\n * not only by the darker label.\n */\n sort?: TableSort;\n /**\n * Makes the column sortable: the label becomes a `<button>` inside the `<th>`,\n * which carries `aria-sort`. Cycling the order is up to the app.\n */\n onSort?: MouseEventHandler<HTMLButtonElement>;\n}\n\n/** Column header (`<th scope=\"col\">`). Sortable when it gets `onSort`. */\nexport function TableHead({\n type = \"text\",\n sort = \"none\",\n onSort,\n className,\n children,\n ...props\n}: TableHeadProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const descriptionId = useId();\n const sorted = sort !== \"none\";\n const sortLabel = {\n none: labels.notSorted,\n ascending: labels.sortAscending,\n descending: labels.sortDescending,\n }[sort];\n\n return (\n <th\n data-slot=\"table-head\"\n scope=\"col\"\n aria-sort={sorted ? sort : undefined}\n data-sorted={sorted || undefined}\n className={cn(tableHeadVariants({ type }), onSort && \"p-0\", className)}\n {...props}\n >\n {onSort ? (\n <button\n type=\"button\"\n onClick={onSort}\n // The state is announced when the button gets focus, where aria-sort on\n // the <th> isn't always read.\n aria-describedby={descriptionId}\n className={cn(\n \"flex h-12 w-full cursor-pointer items-center gap-1 whitespace-nowrap px-4\",\n \"interactive hover:text-text-primary\",\n \"focus-ring focus-visible:-outline-offset-2\",\n // Numbers: the chevron goes on the inside so the label stays aligned\n // with the column.\n type === \"numeric\"\n ? \"flex-row-reverse\"\n : type === \"action\"\n ? \"justify-center\"\n : \"justify-start\",\n )}\n >\n <span>{children}</span>\n {sort === \"ascending\" && <ChevronUpIcon size=\"sm\" />}\n {sort === \"descending\" && <ChevronDownIcon size=\"sm\" />}\n </button>\n ) : (\n children\n )}\n {onSort && (\n <span id={descriptionId} hidden>\n {sortLabel}\n </span>\n )}\n </th>\n );\n}\n\nexport const tableCellVariants = cva(\n \"h-14 border-b border-border-subtle bg-background px-4 align-middle text-body-small text-text-primary interactive\",\n {\n variants: {\n type: {\n ...alignment,\n // Tabular figures so decimals line up and amounts can be compared.\n numeric: \"whitespace-nowrap text-right text-label-strong tabular-nums\",\n },\n },\n defaultVariants: { type: \"text\" },\n },\n);\n\nexport interface TableCellProps extends ComponentProps<\"td\"> {\n /**\n * - `text`: left.\n * - `numeric`: right, tabular figures. The value comes formatted by the app in\n * es-ES (\"29,00 €\", symbol after), the cell doesn't format.\n * - `badge`: left, a `Badge`.\n * - `action`: centred, a ghost small icon button whose `aria-label` carries the\n * row's context (\"Descargar factura del 5 de enero\") — twenty \"Descargar\"\n * buttons are indistinguishable for a screen reader.\n */\n type?: TableCellType;\n}\n\nexport function TableCell({\n type,\n className,\n ...props\n}: TableCellProps): React.JSX.Element {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(tableCellVariants({ type }), className)}\n {...props}\n />\n );\n}\n"],"names":[],"mappings":"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"}
@@ -43,10 +43,10 @@ function TabsTab({
43
43
  "data-slot": "tabs-tab",
44
44
  className: cn(
45
45
  "relative inline-flex h-control-lg shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-md px-4",
46
- "text-label-default text-text-secondary transition-colors",
47
- "after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:bg-transparent",
46
+ "text-label-default text-text-secondary interactive",
47
+ "after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:scale-x-0 after:bg-transparent after:transition-[scale,background-color] after:duration-200 after:ease-out-soft motion-reduce:after:transition-none",
48
48
  "not-data-disabled:not-data-active:hover:text-text-primary",
49
- "data-active:text-label-strong data-active:text-text-brand data-active:after:bg-text-brand",
49
+ "data-active:text-label-strong data-active:text-text-brand data-active:after:scale-x-100 data-active:after:bg-text-brand",
50
50
  "data-disabled:cursor-not-allowed data-disabled:text-text-disabled",
51
51
  // Inside the tab, not around it: in navigation there is no room outside.
52
52
  "focus-ring focus-visible:-outline-offset-2",