@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":"chat-conversation-row.js","sources":["../../../src/components/chat/chat-conversation-row.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, MouseEventHandler, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Badge } from \"../badge\";\n\nexport type ChatConversationState = \"read\" | \"unread\" | \"selected\";\n\nexport interface ChatConversationRowProps\n extends Omit<ComponentProps<\"li\">, \"onClick\"> {\n /** The other person: \"Dra. Laura Martín\". It names the link. */\n name: ReactNode;\n /**\n * The last message, always: without it people have to open every chat to know if\n * something is pending. One line.\n */\n preview: ReactNode;\n /** Time of the last message, \"10:04\" (or \"Ayer\", \"lunes\"). */\n time: string;\n /** Machine-readable moment for `<time>` (ISO 8601). */\n dateTime?: string;\n /** `<Avatar size={40} src alt=\"\" />`: the name is already next to it. */\n avatar?: ReactNode;\n /**\n * Unread messages. Above 0 the row is unread: the preview goes Medium in\n * `text-primary`, the counter appears and the link's name says it (\"Dra. Laura\n * Martín, 2 mensajes sin leer\").\n */\n unreadCount?: number;\n /**\n * Desktop, with the conversation open on the right: brand fill plus the 3px rail\n * (`brand-subtle` on white alone is 1.14:1) and `aria-current=\"page\"`.\n */\n selected?: boolean;\n /** The conversation's URL: the whole row is the link. */\n href?: string;\n /** Router links: `render={<Link to=\"…\" />}`. */\n render?: useRender.RenderProp;\n onClick?: MouseEventHandler<HTMLElement>;\n}\n\n/**\n * A conversation in the chat list: avatar · name + time · last message + unread\n * counter. It is an `<li>`: put the rows in a `<ul>`.\n *\n * Unread shows without colour (weight and the counter); the purple time only\n * reinforces it. The time is `text-secondary`, never the brand's decorative hint.\n */\nexport function ChatConversationRow({\n name,\n preview,\n time,\n dateTime,\n avatar,\n unreadCount = 0,\n selected = false,\n href,\n render,\n onClick,\n className,\n ...props\n}: ChatConversationRowProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const unread = unreadCount > 0;\n const state: ChatConversationState = selected\n ? \"selected\"\n : unread\n ? \"unread\"\n : \"read\";\n const isLink = Boolean(href || render);\n\n const target = useRender({\n render,\n defaultTagName: isLink ? \"a\" : \"button\",\n props: {\n \"data-slot\": \"chat-conversation-row-target\",\n href,\n onClick,\n type: isLink ? undefined : \"button\",\n \"aria-current\": selected ? \"page\" : undefined,\n className: cn(\n \"block w-full cursor-pointer truncate text-left outline-none\",\n \"after:absolute after:inset-0\",\n \"focus-visible:after:outline-2 focus-visible:after:outline-border-focus focus-visible:after:-outline-offset-2\",\n ),\n children: (\n <>\n {name}\n {unread && (\n <span className=\"sr-only\">\n , {labels.chatUnreadMessages(unreadCount)}\n </span>\n )}\n </>\n ),\n },\n });\n\n return (\n <li\n data-slot=\"chat-conversation-row\"\n data-state={state}\n className={cn(\n \"relative flex min-h-18 items-center gap-3 bg-background px-4 py-3 transition-colors\",\n selected ? \"bg-brand-subtle\" : \"hover:bg-surface\",\n className,\n )}\n {...props}\n >\n {selected && (\n <span\n aria-hidden\n className=\"absolute inset-y-0 left-0 w-0.75 bg-text-brand\"\n />\n )}\n {avatar}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center gap-2\">\n <div className=\"min-w-0 flex-1 text-label-strong text-text-primary\">\n {target}\n </div>\n <time\n dateTime={dateTime}\n className={cn(\n \"shrink-0 text-caption-default\",\n selected\n ? \"text-text-primary\"\n : unread\n ? \"text-text-brand\"\n : \"text-text-secondary\",\n )}\n >\n {time}\n </time>\n </div>\n <div className=\"flex items-center gap-2\">\n <p\n className={cn(\n \"min-w-0 flex-1 truncate\",\n unread ? \"text-label-strong\" : \"text-body-small\",\n unread || selected ? \"text-text-primary\" : \"text-text-secondary\",\n )}\n >\n {preview}\n </p>\n {unread && (\n <Badge variant=\"brand\" aria-hidden>\n {unreadCount}\n </Badge>\n )}\n </div>\n </div>\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;AAiDO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,QAAM,SAAS,kBAAA;AACf,QAAM,SAAS,cAAc;AAC7B,QAAM,QAA+B,WACjC,aACA,SACE,WACA;AACN,QAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,QAAM,SAAS,UAAU;AAAA,IACvB;AAAA,IACA,gBAAgB,SAAS,MAAM;AAAA,IAC/B,OAAO;AAAA,MACL,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY;AAAA,MAC3B,gBAAgB,WAAW,SAAS;AAAA,MACpC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,UACE,qBAAA,UAAA,EACG,UAAA;AAAA,QAAA;AAAA,QACA,UACC,qBAAC,QAAA,EAAK,WAAU,WAAU,UAAA;AAAA,UAAA;AAAA,UACrB,OAAO,mBAAmB,WAAW;AAAA,QAAA,EAAA,CAC1C;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAEJ,CACD;AAED,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY;AAAA,MACZ,WAAW;AAAA,QACT;AAAA,QACA,WAAW,oBAAoB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGb;AAAA,QACD,qBAAC,OAAA,EAAI,WAAU,wCACb,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAI,WAAU,sDACZ,UAAA,QACH;AAAA,YACA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC;AAAA,gBACA,WAAW;AAAA,kBACT;AAAA,kBACA,WACI,sBACA,SACE,oBACA;AAAA,gBAAA;AAAA,gBAGP,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GACF;AAAA,UACA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,sBAAsB;AAAA,kBAC/B,UAAU,WAAW,sBAAsB;AAAA,gBAAA;AAAA,gBAG5C,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,YAEF,UACC,oBAAC,OAAA,EAAM,SAAQ,SAAQ,eAAW,MAC/B,UAAA,YAAA,CACH;AAAA,UAAA,EAAA,CAEJ;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"chat-conversation-row.js","sources":["../../../src/components/chat/chat-conversation-row.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, MouseEventHandler, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Badge } from \"../badge\";\n\nexport type ChatConversationState = \"read\" | \"unread\" | \"selected\";\n\nexport interface ChatConversationRowProps\n extends Omit<ComponentProps<\"li\">, \"onClick\"> {\n /** The other person: \"Dra. Laura Martín\". It names the link. */\n name: ReactNode;\n /**\n * The last message, always: without it people have to open every chat to know if\n * something is pending. One line.\n */\n preview: ReactNode;\n /** Time of the last message, \"10:04\" (or \"Ayer\", \"lunes\"). */\n time: string;\n /** Machine-readable moment for `<time>` (ISO 8601). */\n dateTime?: string;\n /** `<Avatar size={40} src alt=\"\" />`: the name is already next to it. */\n avatar?: ReactNode;\n /**\n * Unread messages. Above 0 the row is unread: the preview goes Medium in\n * `text-primary`, the counter appears and the link's name says it (\"Dra. Laura\n * Martín, 2 mensajes sin leer\").\n */\n unreadCount?: number;\n /**\n * Desktop, with the conversation open on the right: brand fill plus the 3px rail\n * (`brand-subtle` on white alone is 1.14:1) and `aria-current=\"page\"`.\n */\n selected?: boolean;\n /** The conversation's URL: the whole row is the link. */\n href?: string;\n /** Router links: `render={<Link to=\"…\" />}`. */\n render?: useRender.RenderProp;\n onClick?: MouseEventHandler<HTMLElement>;\n}\n\n/**\n * A conversation in the chat list: avatar · name + time · last message + unread\n * counter. It is an `<li>`: put the rows in a `<ul>`.\n *\n * Unread shows without colour (weight and the counter); the purple time only\n * reinforces it. The time is `text-secondary`, never the brand's decorative hint.\n */\nexport function ChatConversationRow({\n name,\n preview,\n time,\n dateTime,\n avatar,\n unreadCount = 0,\n selected = false,\n href,\n render,\n onClick,\n className,\n ...props\n}: ChatConversationRowProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const unread = unreadCount > 0;\n const state: ChatConversationState = selected\n ? \"selected\"\n : unread\n ? \"unread\"\n : \"read\";\n const isLink = Boolean(href || render);\n\n const target = useRender({\n render,\n defaultTagName: isLink ? \"a\" : \"button\",\n props: {\n \"data-slot\": \"chat-conversation-row-target\",\n href,\n onClick,\n type: isLink ? undefined : \"button\",\n \"aria-current\": selected ? \"page\" : undefined,\n className: cn(\n \"block w-full cursor-pointer truncate text-left outline-none\",\n \"after:absolute after:inset-0\",\n \"focus-visible:after:outline-2 focus-visible:after:outline-border-focus focus-visible:after:-outline-offset-2\",\n ),\n children: (\n <>\n {name}\n {unread && (\n <span className=\"sr-only\">\n , {labels.chatUnreadMessages(unreadCount)}\n </span>\n )}\n </>\n ),\n },\n });\n\n return (\n <li\n data-slot=\"chat-conversation-row\"\n data-state={state}\n className={cn(\n \"relative flex min-h-18 items-center gap-3 bg-background px-4 py-3 interactive\",\n selected ? \"bg-brand-subtle\" : \"hover:bg-surface\",\n className,\n )}\n {...props}\n >\n {selected && (\n <span\n aria-hidden\n className=\"absolute inset-y-0 left-0 w-0.75 bg-text-brand\"\n />\n )}\n {avatar}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center gap-2\">\n <div className=\"min-w-0 flex-1 text-label-strong text-text-primary\">\n {target}\n </div>\n <time\n dateTime={dateTime}\n className={cn(\n \"shrink-0 text-caption-default\",\n selected\n ? \"text-text-primary\"\n : unread\n ? \"text-text-brand\"\n : \"text-text-secondary\",\n )}\n >\n {time}\n </time>\n </div>\n <div className=\"flex items-center gap-2\">\n <p\n className={cn(\n \"min-w-0 flex-1 truncate\",\n unread ? \"text-label-strong\" : \"text-body-small\",\n unread || selected ? \"text-text-primary\" : \"text-text-secondary\",\n )}\n >\n {preview}\n </p>\n {unread && (\n <Badge variant=\"brand\" aria-hidden>\n {unreadCount}\n </Badge>\n )}\n </div>\n </div>\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;AAiDO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,QAAM,SAAS,kBAAA;AACf,QAAM,SAAS,cAAc;AAC7B,QAAM,QAA+B,WACjC,aACA,SACE,WACA;AACN,QAAM,SAAS,QAAQ,QAAQ,MAAM;AAErC,QAAM,SAAS,UAAU;AAAA,IACvB;AAAA,IACA,gBAAgB,SAAS,MAAM;AAAA,IAC/B,OAAO;AAAA,MACL,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY;AAAA,MAC3B,gBAAgB,WAAW,SAAS;AAAA,MACpC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,UACE,qBAAA,UAAA,EACG,UAAA;AAAA,QAAA;AAAA,QACA,UACC,qBAAC,QAAA,EAAK,WAAU,WAAU,UAAA;AAAA,UAAA;AAAA,UACrB,OAAO,mBAAmB,WAAW;AAAA,QAAA,EAAA,CAC1C;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAEJ,CACD;AAED,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY;AAAA,MACZ,WAAW;AAAA,QACT;AAAA,QACA,WAAW,oBAAoB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGb;AAAA,QACD,qBAAC,OAAA,EAAI,WAAU,wCACb,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAI,WAAU,sDACZ,UAAA,QACH;AAAA,YACA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC;AAAA,gBACA,WAAW;AAAA,kBACT;AAAA,kBACA,WACI,sBACA,SACE,oBACA;AAAA,gBAAA;AAAA,gBAGP,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH,GACF;AAAA,UACA,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,sBAAsB;AAAA,kBAC/B,UAAU,WAAW,sBAAsB;AAAA,gBAAA;AAAA,gBAG5C,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,YAEF,UACC,oBAAC,OAAA,EAAM,SAAQ,SAAQ,eAAW,MAC/B,UAAA,YAAA,CACH;AAAA,UAAA,EAAA,CAEJ;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -7,7 +7,8 @@ import { cn } from "../../lib/cn.js";
7
7
  import { useFieldGroupLabel } from "../radio/use-field-group-label.js";
8
8
  const checkboxVariants = cva([
9
9
  "inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-2",
10
- "border-border-input bg-background text-text-inverse transition-colors",
10
+ "border-border-input bg-background text-text-inverse interactive",
11
+ "not-data-checked:not-data-disabled:hover:border-text-brand",
11
12
  // The control is 20px; the hit area is 44 (and the whole label when there is one).
12
13
  "focus-ring touch-target",
13
14
  "data-checked:border-brand data-checked:bg-brand",
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","sources":["../../../src/components/checkbox/checkbox.tsx"],"sourcesContent":["import { Checkbox as BaseCheckbox } from \"@base-ui/react/checkbox\";\nimport { CheckboxGroup as BaseCheckboxGroup } from \"@base-ui/react/checkbox-group\";\nimport { cva } from \"class-variance-authority\";\n\nimport { CheckIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldGroupLabel } from \"../radio/use-field-group-label\";\n\nexport const checkboxVariants = cva([\n \"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-2\",\n \"border-border-input bg-background text-text-inverse transition-colors\",\n // The control is 20px; the hit area is 44 (and the whole label when there is one).\n \"focus-ring touch-target\",\n \"data-checked:border-brand data-checked:bg-brand\",\n \"data-indeterminate:border-brand data-indeterminate:bg-brand\",\n // Error only paints the empty box: a ticked one already says it is filled in.\n \"aria-invalid:data-unchecked:border-error 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 \"data-disabled:data-indeterminate:border-border-strong data-disabled:data-indeterminate:bg-border-strong\",\n]);\n\nexport interface CheckboxProps\n extends Omit<BaseCheckbox.Root.Props, \"className\" | \"children\"> {\n /** Red border on the empty box. The message goes under the group, not on each box. */\n invalid?: boolean;\n className?: string;\n}\n\n/**\n * Checkbox, without a label: the text goes outside so it can be Body/Small or\n * Label/Default depending on the context. With a label, use `CheckboxField` (or wrap\n * both in a `<label>`); without a visible one, pass `aria-label`.\n *\n * - `indeterminate` is for the parent of a partially ticked group, never a third\n * option the user picks.\n * - The drawing is 20×20; the hit area is 44×44, and the whole label when there is one.\n */\nexport function Checkbox({\n invalid = false,\n className,\n ...props\n}: CheckboxProps): React.JSX.Element {\n return (\n <BaseCheckbox.Root\n data-slot=\"checkbox\"\n aria-invalid={invalid || undefined}\n className={cn(checkboxVariants(), className)}\n {...props}\n >\n <BaseCheckbox.Indicator\n className=\"flex items-center justify-center\"\n render={(indicatorProps, state) => (\n <span {...indicatorProps}>\n {state.indeterminate ? (\n <span className=\"h-0.5 w-2.5 rounded-full bg-current\" />\n ) : (\n <CheckIcon size=\"sm\" />\n )}\n </span>\n )}\n />\n </BaseCheckbox.Root>\n );\n}\n\nexport interface CheckboxGroupProps\n extends Omit<BaseCheckboxGroup.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * Shared state for a list of checkboxes (`value` is the array of ticked `value`s).\n * Name it with `aria-labelledby` or put it inside `Field` (Control=Custom), whose\n * message goes under the group. On submit with an error, move focus to the first\n * unticked box.\n */\nexport function CheckboxGroup({\n className,\n ...props\n}: CheckboxGroupProps): React.JSX.Element {\n useFieldGroupLabel();\n\n return (\n <BaseCheckboxGroup\n data-slot=\"checkbox-group\"\n className={cn(\"flex flex-col\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseCheckbox","BaseCheckboxGroup"],"mappings":";;;;;;;AAQO,MAAM,mBAAmB,IAAI;AAAA,EAClC;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAkBM,SAAS,SAAS;AAAA,EACvB,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAqC;AACnC,SACE;AAAA,IAACA,WAAa;AAAA,IAAb;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,WAAW;AAAA,MACzB,WAAW,GAAG,iBAAA,GAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA,MAEJ,UAAA;AAAA,QAACA,WAAa;AAAA,QAAb;AAAA,UACC,WAAU;AAAA,UACV,QAAQ,CAAC,gBAAgB,UACvB,oBAAC,QAAA,EAAM,GAAG,gBACP,UAAA,MAAM,gBACL,oBAAC,QAAA,EAAK,WAAU,uCAAsC,wBAErD,WAAA,EAAU,MAAK,MAAK,EAAA,CAEzB;AAAA,QAAA;AAAA,MAAA;AAAA,IAEJ;AAAA,EAAA;AAGN;AAaO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,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":"checkbox.js","sources":["../../../src/components/checkbox/checkbox.tsx"],"sourcesContent":["import { Checkbox as BaseCheckbox } from \"@base-ui/react/checkbox\";\nimport { CheckboxGroup as BaseCheckboxGroup } from \"@base-ui/react/checkbox-group\";\nimport { cva } from \"class-variance-authority\";\n\nimport { CheckIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldGroupLabel } from \"../radio/use-field-group-label\";\n\nexport const checkboxVariants = cva([\n \"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-2\",\n \"border-border-input bg-background text-text-inverse interactive\",\n \"not-data-checked:not-data-disabled:hover:border-text-brand\",\n // The control is 20px; the hit area is 44 (and the whole label when there is one).\n \"focus-ring touch-target\",\n \"data-checked:border-brand data-checked:bg-brand\",\n \"data-indeterminate:border-brand data-indeterminate:bg-brand\",\n // Error only paints the empty box: a ticked one already says it is filled in.\n \"aria-invalid:data-unchecked:border-error 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 \"data-disabled:data-indeterminate:border-border-strong data-disabled:data-indeterminate:bg-border-strong\",\n]);\n\nexport interface CheckboxProps\n extends Omit<BaseCheckbox.Root.Props, \"className\" | \"children\"> {\n /** Red border on the empty box. The message goes under the group, not on each box. */\n invalid?: boolean;\n className?: string;\n}\n\n/**\n * Checkbox, without a label: the text goes outside so it can be Body/Small or\n * Label/Default depending on the context. With a label, use `CheckboxField` (or wrap\n * both in a `<label>`); without a visible one, pass `aria-label`.\n *\n * - `indeterminate` is for the parent of a partially ticked group, never a third\n * option the user picks.\n * - The drawing is 20×20; the hit area is 44×44, and the whole label when there is one.\n */\nexport function Checkbox({\n invalid = false,\n className,\n ...props\n}: CheckboxProps): React.JSX.Element {\n return (\n <BaseCheckbox.Root\n data-slot=\"checkbox\"\n aria-invalid={invalid || undefined}\n className={cn(checkboxVariants(), className)}\n {...props}\n >\n <BaseCheckbox.Indicator\n className=\"flex items-center justify-center\"\n render={(indicatorProps, state) => (\n <span {...indicatorProps}>\n {state.indeterminate ? (\n <span className=\"h-0.5 w-2.5 rounded-full bg-current\" />\n ) : (\n <CheckIcon size=\"sm\" />\n )}\n </span>\n )}\n />\n </BaseCheckbox.Root>\n );\n}\n\nexport interface CheckboxGroupProps\n extends Omit<BaseCheckboxGroup.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * Shared state for a list of checkboxes (`value` is the array of ticked `value`s).\n * Name it with `aria-labelledby` or put it inside `Field` (Control=Custom), whose\n * message goes under the group. On submit with an error, move focus to the first\n * unticked box.\n */\nexport function CheckboxGroup({\n className,\n ...props\n}: CheckboxGroupProps): React.JSX.Element {\n useFieldGroupLabel();\n\n return (\n <BaseCheckboxGroup\n data-slot=\"checkbox-group\"\n className={cn(\"flex flex-col\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseCheckbox","BaseCheckboxGroup"],"mappings":";;;;;;;AAQO,MAAM,mBAAmB,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAkBM,SAAS,SAAS;AAAA,EACvB,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAqC;AACnC,SACE;AAAA,IAACA,WAAa;AAAA,IAAb;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,WAAW;AAAA,MACzB,WAAW,GAAG,iBAAA,GAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA,MAEJ,UAAA;AAAA,QAACA,WAAa;AAAA,QAAb;AAAA,UACC,WAAU;AAAA,UACV,QAAQ,CAAC,gBAAgB,UACvB,oBAAC,QAAA,EAAM,GAAG,gBACP,UAAA,MAAM,gBACL,oBAAC,QAAA,EAAK,WAAU,uCAAsC,wBAErD,WAAA,EAAU,MAAK,MAAK,EAAA,CAEzB;AAAA,QAAA;AAAA,MAAA;AAAA,IAEJ;AAAA,EAAA;AAGN;AAaO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,qBAAA;AAEA,SACE;AAAA,IAACC;AAAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iBAAiB,SAAS;AAAA,MACvC,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
@@ -10,7 +10,7 @@ import { useMediCoreLabels } from "../../provider/medicore-provider.js";
10
10
  const chipVariants = cva(
11
11
  [
12
12
  "group/chip inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap",
13
- "rounded-full border border-border bg-background text-text-primary shadow-xs transition-[color,background-color,border-color,box-shadow]",
13
+ "rounded-full border border-border bg-background text-text-primary shadow-xs interactive pressable",
14
14
  // 32 (24 small) drawn, 44 hit area.
15
15
  "focus-ring touch-target",
16
16
  "[&_svg]:shrink-0",
@@ -33,7 +33,7 @@ const chipVariants = cva(
33
33
  );
34
34
  const chipCountVariants = cva(
35
35
  [
36
- "inline-flex shrink-0 items-center justify-center rounded-full bg-surface-raised text-caption-default text-text-secondary tabular-nums transition-colors",
36
+ "inline-flex shrink-0 items-center justify-center rounded-full bg-surface-raised text-caption-default text-text-secondary tabular-nums interactive",
37
37
  "group-data-pressed/chip:bg-brand group-data-pressed/chip:text-text-inverse",
38
38
  "group-data-disabled/chip:bg-transparent group-data-disabled/chip:text-text-disabled"
39
39
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.tsx"],"sourcesContent":["import { Button as BaseButton } from \"@base-ui/react/button\";\nimport { Toggle } from \"@base-ui/react/toggle\";\nimport { ToggleGroup } from \"@base-ui/react/toggle-group\";\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { CheckIcon, XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\nexport const chipVariants = cva(\n [\n \"group/chip inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap\",\n \"rounded-full border border-border bg-background text-text-primary shadow-xs transition-[color,background-color,border-color,box-shadow]\",\n // 32 (24 small) drawn, 44 hit area.\n \"focus-ring touch-target\",\n \"[&_svg]:shrink-0\",\n \"not-data-disabled:not-data-pressed:hover:border-border-strong not-data-disabled:not-data-pressed:hover:bg-surface\",\n // Selected is not just a colour: check in front, brand border, Medium label.\n \"not-data-disabled:data-pressed:border-brand not-data-disabled:data-pressed:bg-brand-subtle not-data-disabled:data-pressed:text-text-brand not-data-disabled:data-pressed:shadow-none\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n ],\n {\n variants: {\n size: {\n default:\n \"h-8 gap-1.5 px-3 text-label-default data-pressed:text-label-strong [&_svg]:size-icon-sm\",\n small: \"h-6 gap-1 px-2 text-label-small [&_svg]:size-icon-xs\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nconst chipCountVariants = cva(\n [\n \"inline-flex shrink-0 items-center justify-center rounded-full bg-surface-raised text-caption-default text-text-secondary tabular-nums transition-colors\",\n \"group-data-pressed/chip:bg-brand group-data-pressed/chip:text-text-inverse\",\n \"group-data-disabled/chip:bg-transparent group-data-disabled/chip:text-text-disabled\",\n ],\n {\n variants: {\n size: {\n default: \"-me-1 h-5 min-w-5 px-1.5\",\n small: \"-me-0.5 h-4 min-w-4 px-1\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nexport type ChipSize = \"default\" | \"small\";\n\ninterface ChipOwnProps {\n /**\n * `small` only for dense metadata, never as the main filter: at 24px it relies on\n * the invisible hit area to reach 44.\n */\n size?: ChipSize;\n /** A MediCore icon before the label. While selected the check takes its place. */\n leadingIcon?: ReactNode;\n /** Counter after the label (\"Abiertas 5\"): how many items the filter would show. */\n count?: ReactNode;\n className?: string;\n}\n\nexport interface ChipToggleProps\n extends Omit<Toggle.Props<string>, \"className\">,\n ChipOwnProps {\n onRemove?: undefined;\n}\n\nexport interface ChipRemovableProps\n extends Omit<BaseButton.Props, \"className\" | \"onClick\">,\n ChipOwnProps {\n /**\n * Turns the chip into a remove button for an applied filter: pressing anywhere on\n * it calls this. Its accessible name becomes \"Quitar <label>\" (`remove` label).\n * A removable chip is not a toggle.\n */\n onRemove: () => void;\n pressed?: undefined;\n defaultPressed?: undefined;\n onPressedChange?: undefined;\n}\n\nexport type ChipProps = ChipToggleProps | ChipRemovableProps;\n\nfunction ChipCount({\n size,\n count,\n}: {\n size: ChipSize | undefined;\n count: ReactNode;\n}): React.JSX.Element | null {\n if (count === undefined || count === null) {\n return null;\n }\n return (\n <>\n {\" \"}\n <span data-slot=\"chip-count\" className={chipCountVariants({ size })}>\n {count}\n </span>\n </>\n );\n}\n\n/**\n * Pressable filter (\"Dermatología\"). The filter row of lists and the admin.\n *\n * - **A Chip is not a Badge.** A Badge tells a state and can't be pressed\n * (\"Receta\", \"Cancelada\"); a Chip filters and is pressed. If it doesn't respond to\n * a tap, it's a Badge.\n * - It is a `<button aria-pressed>` (Base UI Toggle): `pressed` / `defaultPressed` /\n * `onPressedChange`, or `value` inside a `ChipGroup`.\n * - With `onRemove` it is a remove button for an applied filter instead.\n * - `count` adds a counter pill at the end, brand-coloured while selected.\n */\nexport function Chip(props: ChipProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n\n if (props.onRemove) {\n const { size, leadingIcon, count, className, children, onRemove, ...rest } =\n props;\n\n return (\n <BaseButton\n data-slot=\"chip\"\n className={cn(chipVariants({ size }), className)}\n onClick={onRemove}\n {...rest}\n >\n {leadingIcon}\n <span className=\"sr-only\">{labels.remove} </span>\n {children}\n <ChipCount size={size} count={count} />\n <XCloseIcon />\n </BaseButton>\n );\n }\n\n const { size, leadingIcon, count, className, children, onRemove, ...rest } =\n props;\n\n return (\n <Toggle\n data-slot=\"chip\"\n className={cn(chipVariants({ size }), className)}\n {...rest}\n >\n <CheckIcon className=\"hidden group-data-pressed/chip:block\" />\n {leadingIcon && (\n <span className=\"contents group-data-pressed/chip:hidden\">\n {leadingIcon}\n </span>\n )}\n {children}\n <ChipCount size={size} count={count} />\n </Toggle>\n );\n}\n\nexport interface ChipGroupProps<Value extends string = string>\n extends Omit<ToggleGroup.Props<Value>, \"className\"> {\n /**\n * `wrap` (default) breaks into lines; `scroll` keeps one line that scrolls\n * sideways — the usual filter row on mobile.\n */\n overflow?: \"wrap\" | \"scroll\";\n className?: string;\n}\n\n/**\n * A row of filter chips with shared state: `value` is the array of pressed chips.\n * Multi-select by default (`multiple={false}` for one at a time — but an exclusive\n * choice between 2–3 options is a `SegmentedControl`). One tab stop; ←/→ move\n * between chips. Name it with `aria-label` (\"Filtrar por especialidad\").\n */\nexport function ChipGroup<Value extends string>({\n overflow = \"wrap\",\n multiple = true,\n className,\n ...props\n}: ChipGroupProps<Value>): React.JSX.Element {\n return (\n <ToggleGroup\n data-slot=\"chip-group\"\n multiple={multiple}\n className={cn(\n \"flex gap-2\",\n overflow === \"wrap\"\n ? \"flex-wrap\"\n : // Room for the hit area and the focus ring inside the scroll box.\n \"-m-1.5 overflow-x-auto p-1.5 [scrollbar-width:none]\",\n className,\n )}\n {...props}\n />\n );\n}\n"],"names":["size","leadingIcon","count","className","children","onRemove","rest","BaseButton"],"mappings":";;;;;;;;;AAUO,MAAM,eAAe;AAAA,EAC1B;AAAA,IACE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SACE;AAAA,QACF,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAEA,MAAM,oBAAoB;AAAA,EACxB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAuCA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AACF,GAG6B;AAC3B,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA;AAAA,IACD,oBAAC,QAAA,EAAK,aAAU,cAAa,WAAW,kBAAkB,EAAE,KAAA,CAAM,GAC/D,UAAA,MAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAaO,SAAS,KAAK,OAAqC;AACxD,QAAM,SAAS,kBAAA;AAEf,MAAI,MAAM,UAAU;AAClB,UAAM,EAAE,MAAAA,OAAM,aAAAC,cAAa,OAAAC,QAAO,WAAAC,YAAW,UAAAC,WAAU,UAAAC,WAAU,GAAGC,UAClE;AAEF,WACE;AAAA,MAACC;AAAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAW,GAAG,aAAa,EAAE,MAAAP,MAAAA,CAAM,GAAGG,UAAS;AAAA,QAC/C,SAASE;AAAAA,QACR,GAAGC;AAAAA,QAEH,UAAA;AAAA,UAAAL;AAAAA,UACD,qBAAC,QAAA,EAAK,WAAU,WAAW,UAAA;AAAA,YAAA,OAAO;AAAA,YAAO;AAAA,UAAA,GAAC;AAAA,UACzCG;AAAAA,UACD,oBAAC,WAAA,EAAU,MAAMJ,OAAM,OAAOE,QAAO;AAAA,8BACpC,YAAA,CAAA,CAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlB;AAEA,QAAM,EAAE,MAAM,aAAa,OAAO,WAAW,UAAU,UAAU,GAAG,KAAA,IAClE;AAEF,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,aAAa,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MAC9C,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,WAAA,EAAU,WAAU,uCAAA,CAAuC;AAAA,QAC3D,eACC,oBAAC,QAAA,EAAK,WAAU,2CACb,UAAA,aACH;AAAA,QAED;AAAA,QACD,oBAAC,WAAA,EAAU,MAAY,MAAA,CAAc;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG3C;AAkBO,SAAS,UAAgC;AAAA,EAC9C,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAA6C;AAC3C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,aAAa,SACT;AAAA;AAAA,UAEA;AAAA;AAAA,QACJ;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
1
+ {"version":3,"file":"chip.js","sources":["../../../src/components/chip/chip.tsx"],"sourcesContent":["import { Button as BaseButton } from \"@base-ui/react/button\";\nimport { Toggle } from \"@base-ui/react/toggle\";\nimport { ToggleGroup } from \"@base-ui/react/toggle-group\";\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { CheckIcon, XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\nexport const chipVariants = cva(\n [\n \"group/chip inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap\",\n \"rounded-full border border-border bg-background text-text-primary shadow-xs interactive pressable\",\n // 32 (24 small) drawn, 44 hit area.\n \"focus-ring touch-target\",\n \"[&_svg]:shrink-0\",\n \"not-data-disabled:not-data-pressed:hover:border-border-strong not-data-disabled:not-data-pressed:hover:bg-surface\",\n // Selected is not just a colour: check in front, brand border, Medium label.\n \"not-data-disabled:data-pressed:border-brand not-data-disabled:data-pressed:bg-brand-subtle not-data-disabled:data-pressed:text-text-brand not-data-disabled:data-pressed:shadow-none\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n ],\n {\n variants: {\n size: {\n default:\n \"h-8 gap-1.5 px-3 text-label-default data-pressed:text-label-strong [&_svg]:size-icon-sm\",\n small: \"h-6 gap-1 px-2 text-label-small [&_svg]:size-icon-xs\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nconst chipCountVariants = cva(\n [\n \"inline-flex shrink-0 items-center justify-center rounded-full bg-surface-raised text-caption-default text-text-secondary tabular-nums interactive\",\n \"group-data-pressed/chip:bg-brand group-data-pressed/chip:text-text-inverse\",\n \"group-data-disabled/chip:bg-transparent group-data-disabled/chip:text-text-disabled\",\n ],\n {\n variants: {\n size: {\n default: \"-me-1 h-5 min-w-5 px-1.5\",\n small: \"-me-0.5 h-4 min-w-4 px-1\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nexport type ChipSize = \"default\" | \"small\";\n\ninterface ChipOwnProps {\n /**\n * `small` only for dense metadata, never as the main filter: at 24px it relies on\n * the invisible hit area to reach 44.\n */\n size?: ChipSize;\n /** A MediCore icon before the label. While selected the check takes its place. */\n leadingIcon?: ReactNode;\n /** Counter after the label (\"Abiertas 5\"): how many items the filter would show. */\n count?: ReactNode;\n className?: string;\n}\n\nexport interface ChipToggleProps\n extends Omit<Toggle.Props<string>, \"className\">,\n ChipOwnProps {\n onRemove?: undefined;\n}\n\nexport interface ChipRemovableProps\n extends Omit<BaseButton.Props, \"className\" | \"onClick\">,\n ChipOwnProps {\n /**\n * Turns the chip into a remove button for an applied filter: pressing anywhere on\n * it calls this. Its accessible name becomes \"Quitar <label>\" (`remove` label).\n * A removable chip is not a toggle.\n */\n onRemove: () => void;\n pressed?: undefined;\n defaultPressed?: undefined;\n onPressedChange?: undefined;\n}\n\nexport type ChipProps = ChipToggleProps | ChipRemovableProps;\n\nfunction ChipCount({\n size,\n count,\n}: {\n size: ChipSize | undefined;\n count: ReactNode;\n}): React.JSX.Element | null {\n if (count === undefined || count === null) {\n return null;\n }\n return (\n <>\n {\" \"}\n <span data-slot=\"chip-count\" className={chipCountVariants({ size })}>\n {count}\n </span>\n </>\n );\n}\n\n/**\n * Pressable filter (\"Dermatología\"). The filter row of lists and the admin.\n *\n * - **A Chip is not a Badge.** A Badge tells a state and can't be pressed\n * (\"Receta\", \"Cancelada\"); a Chip filters and is pressed. If it doesn't respond to\n * a tap, it's a Badge.\n * - It is a `<button aria-pressed>` (Base UI Toggle): `pressed` / `defaultPressed` /\n * `onPressedChange`, or `value` inside a `ChipGroup`.\n * - With `onRemove` it is a remove button for an applied filter instead.\n * - `count` adds a counter pill at the end, brand-coloured while selected.\n */\nexport function Chip(props: ChipProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n\n if (props.onRemove) {\n const { size, leadingIcon, count, className, children, onRemove, ...rest } =\n props;\n\n return (\n <BaseButton\n data-slot=\"chip\"\n className={cn(chipVariants({ size }), className)}\n onClick={onRemove}\n {...rest}\n >\n {leadingIcon}\n <span className=\"sr-only\">{labels.remove} </span>\n {children}\n <ChipCount size={size} count={count} />\n <XCloseIcon />\n </BaseButton>\n );\n }\n\n const { size, leadingIcon, count, className, children, onRemove, ...rest } =\n props;\n\n return (\n <Toggle\n data-slot=\"chip\"\n className={cn(chipVariants({ size }), className)}\n {...rest}\n >\n <CheckIcon className=\"hidden group-data-pressed/chip:block\" />\n {leadingIcon && (\n <span className=\"contents group-data-pressed/chip:hidden\">\n {leadingIcon}\n </span>\n )}\n {children}\n <ChipCount size={size} count={count} />\n </Toggle>\n );\n}\n\nexport interface ChipGroupProps<Value extends string = string>\n extends Omit<ToggleGroup.Props<Value>, \"className\"> {\n /**\n * `wrap` (default) breaks into lines; `scroll` keeps one line that scrolls\n * sideways — the usual filter row on mobile.\n */\n overflow?: \"wrap\" | \"scroll\";\n className?: string;\n}\n\n/**\n * A row of filter chips with shared state: `value` is the array of pressed chips.\n * Multi-select by default (`multiple={false}` for one at a time — but an exclusive\n * choice between 2–3 options is a `SegmentedControl`). One tab stop; ←/→ move\n * between chips. Name it with `aria-label` (\"Filtrar por especialidad\").\n */\nexport function ChipGroup<Value extends string>({\n overflow = \"wrap\",\n multiple = true,\n className,\n ...props\n}: ChipGroupProps<Value>): React.JSX.Element {\n return (\n <ToggleGroup\n data-slot=\"chip-group\"\n multiple={multiple}\n className={cn(\n \"flex gap-2\",\n overflow === \"wrap\"\n ? \"flex-wrap\"\n : // Room for the hit area and the focus ring inside the scroll box.\n \"-m-1.5 overflow-x-auto p-1.5 [scrollbar-width:none]\",\n className,\n )}\n {...props}\n />\n );\n}\n"],"names":["size","leadingIcon","count","className","children","onRemove","rest","BaseButton"],"mappings":";;;;;;;;;AAUO,MAAM,eAAe;AAAA,EAC1B;AAAA,IACE;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SACE;AAAA,QACF,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAEA,MAAM,oBAAoB;AAAA,EACxB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAuCA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AACF,GAG6B;AAC3B,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA;AAAA,IACD,oBAAC,QAAA,EAAK,aAAU,cAAa,WAAW,kBAAkB,EAAE,KAAA,CAAM,GAC/D,UAAA,MAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAaO,SAAS,KAAK,OAAqC;AACxD,QAAM,SAAS,kBAAA;AAEf,MAAI,MAAM,UAAU;AAClB,UAAM,EAAE,MAAAA,OAAM,aAAAC,cAAa,OAAAC,QAAO,WAAAC,YAAW,UAAAC,WAAU,UAAAC,WAAU,GAAGC,UAClE;AAEF,WACE;AAAA,MAACC;AAAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAW,GAAG,aAAa,EAAE,MAAAP,MAAAA,CAAM,GAAGG,UAAS;AAAA,QAC/C,SAASE;AAAAA,QACR,GAAGC;AAAAA,QAEH,UAAA;AAAA,UAAAL;AAAAA,UACD,qBAAC,QAAA,EAAK,WAAU,WAAW,UAAA;AAAA,YAAA,OAAO;AAAA,YAAO;AAAA,UAAA,GAAC;AAAA,UACzCG;AAAAA,UACD,oBAAC,WAAA,EAAU,MAAMJ,OAAM,OAAOE,QAAO;AAAA,8BACpC,YAAA,CAAA,CAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlB;AAEA,QAAM,EAAE,MAAM,aAAa,OAAO,WAAW,UAAU,UAAU,GAAG,KAAA,IAClE;AAEF,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,aAAa,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MAC9C,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,WAAA,EAAU,WAAU,uCAAA,CAAuC;AAAA,QAC3D,eACC,oBAAC,QAAA,EAAK,WAAU,2CACb,UAAA,aACH;AAAA,QAED;AAAA,QACD,oBAAC,WAAA,EAAU,MAAY,MAAA,CAAc;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG3C;AAkBO,SAAS,UAAgC;AAAA,EAC9C,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAA6C;AAC3C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,aAAa,SACT;AAAA;AAAA,UAEA;AAAA;AAAA,QACJ;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
@@ -94,7 +94,8 @@ function DayCell(props) {
94
94
  disabled,
95
95
  className: cn(
96
96
  dayCellClasses,
97
- "cursor-pointer transition-colors focus-ring",
97
+ "cursor-pointer interactive pressable focus-ring",
98
+ "not-data-checked:not-data-disabled:hover:border-border-strong",
98
99
  "data-checked:border-text-brand data-checked:bg-brand-subtle",
99
100
  "data-disabled:cursor-not-allowed",
100
101
  className
@@ -1 +1 @@
1
- {"version":3,"file":"day-cell.js","sources":["../../../src/components/day-cell/day-cell.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { RadioGroup, type RadioGroupProps } from \"../radio\";\n\ninterface DayParts {\n weekday: string;\n day: string;\n full: string;\n}\n\nfunction formatDay(date: Date, locale: string, timeZone?: string): DayParts {\n const weekday = new Intl.DateTimeFormat(locale, {\n weekday: \"short\",\n timeZone,\n })\n .format(date)\n .replace(\".\", \"\");\n\n return {\n // Lowercase: in Spanish the name of the day doesn't take a capital (\"mié\").\n weekday: weekday.toLocaleLowerCase(locale),\n day: new Intl.DateTimeFormat(locale, { day: \"numeric\", timeZone }).format(\n date,\n ),\n full: new Intl.DateTimeFormat(locale, {\n weekday: \"long\",\n day: \"numeric\",\n month: \"long\",\n timeZone,\n }).format(date),\n };\n}\n\ninterface DayCellOwnProps {\n /** The day. The weekday, the number and the full name are formatted from it. */\n date: Date;\n /** Locale for the names (\"mié\", \"miércoles, 24 de septiembre\"). Default `\"es\"`. */\n locale?: string;\n /** Time zone to read `date` in (the patient's), e.g. `\"Atlantic/Canary\"`. */\n timeZone?: string;\n /** Today gets a dot, not a colour: it survives the day being selected as well. */\n today?: boolean;\n /** Unavailable: a day with no slots, in `text-disabled`. */\n disabled?: boolean;\n className?: string;\n}\n\nexport interface DayCellStaticProps\n extends DayCellOwnProps,\n Omit<ComponentProps<\"div\">, keyof DayCellOwnProps | \"children\"> {\n value?: undefined;\n}\n\nexport interface DayCellSelectableProps<Value = string>\n extends DayCellOwnProps,\n Omit<\n BaseRadio.Root.Props<Value>,\n keyof DayCellOwnProps | \"children\" | \"value\"\n > {\n /** Makes it a radio of a `DayCellGroup` (the day picker). */\n value: Value;\n}\n\nexport type DayCellProps<Value = string> =\n | DayCellStaticProps\n | DayCellSelectableProps<Value>;\n\nconst dayCellClasses =\n \"group/day-cell flex h-14 w-16 shrink-0 select-none flex-col items-center justify-center gap-0.5 rounded-lg border-[1.5px] border-transparent py-1.5\";\n\n/**\n * The header of a day in the agenda: weekday in lowercase (\"mié\") and the number.\n *\n * - Without `value` it is the static header of an `AvailabilityColumn` /\n * `AvailabilityRow`. The visible \"mié 24\" is hidden from screen readers, which get\n * the full date instead (\"miércoles, 24 de septiembre\").\n * - With `value` it is a radio inside `DayCellGroup` (the day picker), named with the\n * full date. Selected: `brand-subtle` fill, 1.5px brand border, brand text.\n *\n * `today` and the selection are independent: today can be unselected and the\n * selected day can be next Thursday.\n */\nexport function DayCell<Value = string>(\n props: DayCellProps<Value>,\n): React.JSX.Element {\n const labels = useMediCoreLabels();\n const {\n date,\n locale = \"es\",\n timeZone,\n today = false,\n disabled = false,\n className,\n ...rest\n } = props;\n const { weekday, day, full } = formatDay(date, locale, timeZone);\n const name = today ? `${full}, ${labels.today}` : full;\n\n const content = (\n <>\n <span\n aria-hidden\n className=\"text-label-small text-text-secondary group-data-checked/day-cell:text-text-brand group-data-disabled/day-cell:text-text-disabled\"\n >\n {weekday}\n </span>\n <span\n aria-hidden\n className=\"text-body-default-strong text-text-primary group-data-checked/day-cell:text-text-brand group-data-disabled/day-cell:text-text-disabled\"\n >\n {day}\n </span>\n {today && (\n <span\n aria-hidden\n className=\"size-1 shrink-0 rounded-full bg-text-brand\"\n />\n )}\n </>\n );\n\n if (rest.value === undefined) {\n const { value: _value, ...divProps } = rest as Omit<\n DayCellStaticProps,\n keyof DayCellOwnProps\n >;\n\n return (\n <div\n data-slot=\"day-cell\"\n data-disabled={disabled || undefined}\n className={cn(dayCellClasses, className)}\n {...divProps}\n >\n {/*\n * A day without slots is an inactive part of the agenda (WCAG 1.4.3 exempts\n * it from contrast): aria-disabled on the hidden visual copy says so to\n * checkers, while screen readers get the full date.\n */}\n <span\n aria-hidden\n aria-disabled={disabled || undefined}\n className=\"contents\"\n >\n {content}\n </span>\n <span className=\"sr-only\">{name}</span>\n </div>\n );\n }\n\n const radioProps = rest as Omit<\n DayCellSelectableProps<Value>,\n keyof DayCellOwnProps\n >;\n\n return (\n <BaseRadio.Root<Value>\n data-slot=\"day-cell\"\n aria-label={name}\n disabled={disabled}\n className={cn(\n dayCellClasses,\n \"cursor-pointer transition-colors focus-ring\",\n \"data-checked:border-text-brand data-checked:bg-brand-subtle\",\n \"data-disabled:cursor-not-allowed\",\n className,\n )}\n {...radioProps}\n >\n {content}\n </BaseRadio.Root>\n );\n}\n\nexport interface DayCellGroupProps<Value = string>\n extends RadioGroupProps<Value> {}\n\n/**\n * The day picker: a row of selectable `DayCell`s (with `value`), one choice, arrow\n * keys to move. Name it with `aria-label` (\"Elige un día\").\n */\nexport function DayCellGroup<Value = string>({\n className,\n ...props\n}: DayCellGroupProps<Value>): React.JSX.Element {\n return (\n <RadioGroup<Value>\n data-slot=\"day-cell-group\"\n className={cn(\"flex-row flex-wrap gap-2\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseRadio"],"mappings":";;;;;AAaA,SAAS,UAAU,MAAY,QAAgB,UAA6B;AAC1E,QAAM,UAAU,IAAI,KAAK,eAAe,QAAQ;AAAA,IAC9C,SAAS;AAAA,IACT;AAAA,EAAA,CACD,EACE,OAAO,IAAI,EACX,QAAQ,KAAK,EAAE;AAElB,SAAO;AAAA;AAAA,IAEL,SAAS,QAAQ,kBAAkB,MAAM;AAAA,IACzC,KAAK,IAAI,KAAK,eAAe,QAAQ,EAAE,KAAK,WAAW,SAAA,CAAU,EAAE;AAAA,MACjE;AAAA,IAAA;AAAA,IAEF,MAAM,IAAI,KAAK,eAAe,QAAQ;AAAA,MACpC,SAAS;AAAA,MACT,KAAK;AAAA,MACL,OAAO;AAAA,MACP;AAAA,IAAA,CACD,EAAE,OAAO,IAAI;AAAA,EAAA;AAElB;AAoCA,MAAM,iBACJ;AAcK,SAAS,QACd,OACmB;AACnB,QAAM,SAAS,kBAAA;AACf,QAAM;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AACJ,QAAM,EAAE,SAAS,KAAK,KAAA,IAAS,UAAU,MAAM,QAAQ,QAAQ;AAC/D,QAAM,OAAO,QAAQ,GAAG,IAAI,KAAK,OAAO,KAAK,KAAK;AAElD,QAAM,UACJ,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEH;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GAEJ;AAGF,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,EAAE,OAAO,QAAQ,GAAG,aAAa;AAKvC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,iBAAe,YAAY;AAAA,QAC3B,WAAW,GAAG,gBAAgB,SAAS;AAAA,QACtC,GAAG;AAAA,QAOJ,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAW;AAAA,cACX,iBAAe,YAAY;AAAA,cAC3B,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAEH,oBAAC,QAAA,EAAK,WAAU,WAAW,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGtC;AAEA,QAAM,aAAa;AAKnB,SACE;AAAA,IAACA,MAAU;AAAA,IAAV;AAAA,MACC,aAAU;AAAA,MACV,cAAY;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AASO,SAAS,aAA6B;AAAA,EAC3C;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,4BAA4B,SAAS;AAAA,MAClD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
1
+ {"version":3,"file":"day-cell.js","sources":["../../../src/components/day-cell/day-cell.tsx"],"sourcesContent":["import { Radio as BaseRadio } from \"@base-ui/react/radio\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { RadioGroup, type RadioGroupProps } from \"../radio\";\n\ninterface DayParts {\n weekday: string;\n day: string;\n full: string;\n}\n\nfunction formatDay(date: Date, locale: string, timeZone?: string): DayParts {\n const weekday = new Intl.DateTimeFormat(locale, {\n weekday: \"short\",\n timeZone,\n })\n .format(date)\n .replace(\".\", \"\");\n\n return {\n // Lowercase: in Spanish the name of the day doesn't take a capital (\"mié\").\n weekday: weekday.toLocaleLowerCase(locale),\n day: new Intl.DateTimeFormat(locale, { day: \"numeric\", timeZone }).format(\n date,\n ),\n full: new Intl.DateTimeFormat(locale, {\n weekday: \"long\",\n day: \"numeric\",\n month: \"long\",\n timeZone,\n }).format(date),\n };\n}\n\ninterface DayCellOwnProps {\n /** The day. The weekday, the number and the full name are formatted from it. */\n date: Date;\n /** Locale for the names (\"mié\", \"miércoles, 24 de septiembre\"). Default `\"es\"`. */\n locale?: string;\n /** Time zone to read `date` in (the patient's), e.g. `\"Atlantic/Canary\"`. */\n timeZone?: string;\n /** Today gets a dot, not a colour: it survives the day being selected as well. */\n today?: boolean;\n /** Unavailable: a day with no slots, in `text-disabled`. */\n disabled?: boolean;\n className?: string;\n}\n\nexport interface DayCellStaticProps\n extends DayCellOwnProps,\n Omit<ComponentProps<\"div\">, keyof DayCellOwnProps | \"children\"> {\n value?: undefined;\n}\n\nexport interface DayCellSelectableProps<Value = string>\n extends DayCellOwnProps,\n Omit<\n BaseRadio.Root.Props<Value>,\n keyof DayCellOwnProps | \"children\" | \"value\"\n > {\n /** Makes it a radio of a `DayCellGroup` (the day picker). */\n value: Value;\n}\n\nexport type DayCellProps<Value = string> =\n | DayCellStaticProps\n | DayCellSelectableProps<Value>;\n\nconst dayCellClasses =\n \"group/day-cell flex h-14 w-16 shrink-0 select-none flex-col items-center justify-center gap-0.5 rounded-lg border-[1.5px] border-transparent py-1.5\";\n\n/**\n * The header of a day in the agenda: weekday in lowercase (\"mié\") and the number.\n *\n * - Without `value` it is the static header of an `AvailabilityColumn` /\n * `AvailabilityRow`. The visible \"mié 24\" is hidden from screen readers, which get\n * the full date instead (\"miércoles, 24 de septiembre\").\n * - With `value` it is a radio inside `DayCellGroup` (the day picker), named with the\n * full date. Selected: `brand-subtle` fill, 1.5px brand border, brand text.\n *\n * `today` and the selection are independent: today can be unselected and the\n * selected day can be next Thursday.\n */\nexport function DayCell<Value = string>(\n props: DayCellProps<Value>,\n): React.JSX.Element {\n const labels = useMediCoreLabels();\n const {\n date,\n locale = \"es\",\n timeZone,\n today = false,\n disabled = false,\n className,\n ...rest\n } = props;\n const { weekday, day, full } = formatDay(date, locale, timeZone);\n const name = today ? `${full}, ${labels.today}` : full;\n\n const content = (\n <>\n <span\n aria-hidden\n className=\"text-label-small text-text-secondary group-data-checked/day-cell:text-text-brand group-data-disabled/day-cell:text-text-disabled\"\n >\n {weekday}\n </span>\n <span\n aria-hidden\n className=\"text-body-default-strong text-text-primary group-data-checked/day-cell:text-text-brand group-data-disabled/day-cell:text-text-disabled\"\n >\n {day}\n </span>\n {today && (\n <span\n aria-hidden\n className=\"size-1 shrink-0 rounded-full bg-text-brand\"\n />\n )}\n </>\n );\n\n if (rest.value === undefined) {\n const { value: _value, ...divProps } = rest as Omit<\n DayCellStaticProps,\n keyof DayCellOwnProps\n >;\n\n return (\n <div\n data-slot=\"day-cell\"\n data-disabled={disabled || undefined}\n className={cn(dayCellClasses, className)}\n {...divProps}\n >\n {/*\n * A day without slots is an inactive part of the agenda (WCAG 1.4.3 exempts\n * it from contrast): aria-disabled on the hidden visual copy says so to\n * checkers, while screen readers get the full date.\n */}\n <span\n aria-hidden\n aria-disabled={disabled || undefined}\n className=\"contents\"\n >\n {content}\n </span>\n <span className=\"sr-only\">{name}</span>\n </div>\n );\n }\n\n const radioProps = rest as Omit<\n DayCellSelectableProps<Value>,\n keyof DayCellOwnProps\n >;\n\n return (\n <BaseRadio.Root<Value>\n data-slot=\"day-cell\"\n aria-label={name}\n disabled={disabled}\n className={cn(\n dayCellClasses,\n \"cursor-pointer interactive pressable focus-ring\",\n \"not-data-checked:not-data-disabled:hover:border-border-strong\",\n \"data-checked:border-text-brand data-checked:bg-brand-subtle\",\n \"data-disabled:cursor-not-allowed\",\n className,\n )}\n {...radioProps}\n >\n {content}\n </BaseRadio.Root>\n );\n}\n\nexport interface DayCellGroupProps<Value = string>\n extends RadioGroupProps<Value> {}\n\n/**\n * The day picker: a row of selectable `DayCell`s (with `value`), one choice, arrow\n * keys to move. Name it with `aria-label` (\"Elige un día\").\n */\nexport function DayCellGroup<Value = string>({\n className,\n ...props\n}: DayCellGroupProps<Value>): React.JSX.Element {\n return (\n <RadioGroup<Value>\n data-slot=\"day-cell-group\"\n className={cn(\"flex-row flex-wrap gap-2\", className)}\n {...props}\n />\n );\n}\n"],"names":["BaseRadio"],"mappings":";;;;;AAaA,SAAS,UAAU,MAAY,QAAgB,UAA6B;AAC1E,QAAM,UAAU,IAAI,KAAK,eAAe,QAAQ;AAAA,IAC9C,SAAS;AAAA,IACT;AAAA,EAAA,CACD,EACE,OAAO,IAAI,EACX,QAAQ,KAAK,EAAE;AAElB,SAAO;AAAA;AAAA,IAEL,SAAS,QAAQ,kBAAkB,MAAM;AAAA,IACzC,KAAK,IAAI,KAAK,eAAe,QAAQ,EAAE,KAAK,WAAW,SAAA,CAAU,EAAE;AAAA,MACjE;AAAA,IAAA;AAAA,IAEF,MAAM,IAAI,KAAK,eAAe,QAAQ;AAAA,MACpC,SAAS;AAAA,MACT,KAAK;AAAA,MACL,OAAO;AAAA,MACP;AAAA,IAAA,CACD,EAAE,OAAO,IAAI;AAAA,EAAA;AAElB;AAoCA,MAAM,iBACJ;AAcK,SAAS,QACd,OACmB;AACnB,QAAM,SAAS,kBAAA;AACf,QAAM;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AACJ,QAAM,EAAE,SAAS,KAAK,KAAA,IAAS,UAAU,MAAM,QAAQ,QAAQ;AAC/D,QAAM,OAAO,QAAQ,GAAG,IAAI,KAAK,OAAO,KAAK,KAAK;AAElD,QAAM,UACJ,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEH;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GAEJ;AAGF,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,EAAE,OAAO,QAAQ,GAAG,aAAa;AAKvC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,iBAAe,YAAY;AAAA,QAC3B,WAAW,GAAG,gBAAgB,SAAS;AAAA,QACtC,GAAG;AAAA,QAOJ,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAW;AAAA,cACX,iBAAe,YAAY;AAAA,cAC3B,WAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAEH,oBAAC,QAAA,EAAK,WAAU,WAAW,UAAA,KAAA,CAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGtC;AAEA,QAAM,aAAa;AAKnB,SACE;AAAA,IAACA,MAAU;AAAA,IAAV;AAAA,MACC,aAAU;AAAA,MACV,cAAY;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AASO,SAAS,aAA6B;AAAA,EAC3C;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,4BAA4B,SAAS;AAAA,MAClD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
@@ -0,0 +1,4 @@
1
+ export interface HoverArrowProps {
2
+ className?: string;
3
+ }
4
+ export declare function HoverArrow({ className }: HoverArrowProps): React.JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ArrowNarrowRightIcon } from "../../icons/generated/arrow-narrow-right.js";
3
+ import { cn } from "../../lib/cn.js";
4
+ function HoverArrow({ className }) {
5
+ return /* @__PURE__ */ jsx(
6
+ "span",
7
+ {
8
+ "data-slot": "hover-arrow",
9
+ "aria-hidden": true,
10
+ className: cn("hover-arrow", className),
11
+ children: /* @__PURE__ */ jsx(ArrowNarrowRightIcon, { className: "size-(--hover-arrow-size,1em)" })
12
+ }
13
+ );
14
+ }
15
+ export {
16
+ HoverArrow
17
+ };
18
+ //# sourceMappingURL=hover-arrow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover-arrow.js","sources":["../../../src/components/hover-arrow/hover-arrow.tsx"],"sourcesContent":["import { ArrowNarrowRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\n\nexport interface HoverArrowProps {\n className?: string;\n}\n\nexport function HoverArrow({ className }: HoverArrowProps): React.JSX.Element {\n return (\n <span\n data-slot=\"hover-arrow\"\n aria-hidden\n className={cn(\"hover-arrow\", className)}\n >\n <ArrowNarrowRightIcon className=\"size-(--hover-arrow-size,1em)\" />\n </span>\n );\n}\n"],"names":[],"mappings":";;;AAOO,SAAS,WAAW,EAAE,aAAiD;AAC5E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAW;AAAA,MACX,WAAW,GAAG,eAAe,SAAS;AAAA,MAEtC,UAAA,oBAAC,sBAAA,EAAqB,WAAU,gCAAA,CAAgC;AAAA,IAAA;AAAA,EAAA;AAGtE;"}
@@ -0,0 +1 @@
1
+ export { HoverArrow, type HoverArrowProps } from "./hover-arrow";
@@ -1,5 +1,5 @@
1
1
  /** Border, radius, fill and text colour of the box. */
2
- export declare const controlBox = "w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset transition-[border-color,box-shadow]";
2
+ export declare const controlBox = "w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset interactive hover:border-border-strong";
3
3
  /** One-line content: 12px side padding, 8px between icon and text, Label/Default (14). */
4
4
  export declare const controlRow = "flex items-center gap-2 px-3 text-label-default";
5
5
  /** control-md (40) and control-sm (36): the same tokens as Button, so they line up. */
@@ -31,4 +31,4 @@ export declare const controlPlaceholder = "placeholder:text-text-secondary disab
31
31
  */
32
32
  export declare const controlIcon = "flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled";
33
33
  /** Small icon button inside the box (reveal password, clear). 44px hit area. */
34
- export declare const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
34
+ export declare const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
@@ -1,4 +1,4 @@
1
- const controlBox = "w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset transition-[border-color,box-shadow]";
1
+ const controlBox = "w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset interactive hover:border-border-strong";
2
2
  const controlRow = "flex items-center gap-2 px-3 text-label-default";
3
3
  const controlHeight = {
4
4
  default: "h-control-md",
@@ -21,7 +21,7 @@ const triggerControlStates = [
21
21
  ].join(" ");
22
22
  const controlPlaceholder = "placeholder:text-text-secondary disabled:placeholder:text-text-disabled";
23
23
  const controlIcon = "flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled";
24
- const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
24
+ const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
25
25
  export {
26
26
  controlAction,
27
27
  controlBox,
@@ -1 +1 @@
1
- {"version":3,"file":"control-styles.js","sources":["../../../src/components/input/control-styles.ts"],"sourcesContent":["/*\n * The box shared by Input, Textarea, Select, NativeSelect and PhoneInput. Figma draws\n * them as the same object on purpose: same radius, padding, border and states, so a\n * field and a dropdown in the same row only differ by the chevron.\n *\n * Focus and Error are a 2px border (border-thick), not just a colour change. The\n * second pixel is an inset ring, so the content never moves when the state changes.\n * Focus wins over Error: the message under the field keeps saying what is wrong.\n */\n\n/** Border, radius, fill and text colour of the box. */\nexport const controlBox =\n \"w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset transition-[border-color,box-shadow]\";\n\n/** One-line content: 12px side padding, 8px between icon and text, Label/Default (14). */\nexport const controlRow = \"flex items-center gap-2 px-3 text-label-default\";\n\n/** control-md (40) and control-sm (36): the same tokens as Button, so they line up. */\nexport const controlHeight = {\n default: \"h-control-md\",\n small: \"h-control-sm\",\n} as const;\n\nexport type ControlSize = keyof typeof controlHeight;\n\n/**\n * States for a box that WRAPS the real control (Input, NativeSelect, the phone\n * prefix). Read from the control inside: `aria-invalid` (set by `invalid` or by\n * Field), `:focus` and `:disabled` (of the input/select only: a native select's\n * placeholder `<option disabled>` must not grey the box out). Buttons inside the box\n * (reveal password, clear) keep their own focus ring and don't light the box up.\n */\nexport const wrappedControlStates = [\n \"has-[[aria-invalid=true]]:border-error has-[[aria-invalid=true]]:ring-error\",\n \"has-[:focus:not(button)]:border-border-focus has-[:focus:not(button)]:ring-border-focus\",\n \"has-[:is(input,select):disabled]:cursor-not-allowed has-[:is(input,select):disabled]:border-border-subtle has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled has-[:is(input,select):disabled]:shadow-none\",\n].join(\" \");\n\n/** States for a box that IS the control (Textarea). */\nexport const selfControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus\",\n \"aria-invalid:not-focus:border-error aria-invalid:not-focus:ring-error\",\n \"disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface disabled:text-text-disabled disabled:shadow-none\",\n].join(\" \");\n\n/**\n * States for a button box (Select trigger). It keeps the focus border while its popup\n * is open, because focus is inside the list then.\n */\nexport const triggerControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus data-popup-open:border-border-focus data-popup-open:ring-border-focus\",\n \"aria-invalid:not-focus:not-data-popup-open:border-error aria-invalid:not-focus:not-data-popup-open:ring-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n].join(\" \");\n\n/** Placeholder: the same Value node in text-secondary, never a variant. */\nexport const controlPlaceholder =\n \"placeholder:text-text-secondary disabled:placeholder:text-text-disabled\";\n\n/**\n * Leading / trailing icon slot: 16px in text-secondary (5.07:1), disabled with the\n * box. The box carries `group/control`.\n */\nexport const controlIcon =\n \"flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled\";\n\n/** Small icon button inside the box (reveal password, clear). 44px hit area. */\nexport const controlAction =\n \"focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm\";\n"],"names":[],"mappings":"AAWO,MAAM,aACX;AAGK,MAAM,aAAa;AAGnB,MAAM,gBAAgB;AAAA,EAC3B,SAAS;AAAA,EACT,OAAO;AACT;AAWO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAMH,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,qBACX;AAMK,MAAM,cACX;AAGK,MAAM,gBACX;"}
1
+ {"version":3,"file":"control-styles.js","sources":["../../../src/components/input/control-styles.ts"],"sourcesContent":["/*\n * The box shared by Input, Textarea, Select, NativeSelect and PhoneInput. Figma draws\n * them as the same object on purpose: same radius, padding, border and states, so a\n * field and a dropdown in the same row only differ by the chevron.\n *\n * Focus and Error are a 2px border (border-thick), not just a colour change. The\n * second pixel is an inset ring, so the content never moves when the state changes.\n * Focus wins over Error: the message under the field keeps saying what is wrong.\n */\n\n/** Border, radius, fill and text colour of the box. */\nexport const controlBox =\n \"w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset interactive hover:border-border-strong\";\n\n/** One-line content: 12px side padding, 8px between icon and text, Label/Default (14). */\nexport const controlRow = \"flex items-center gap-2 px-3 text-label-default\";\n\n/** control-md (40) and control-sm (36): the same tokens as Button, so they line up. */\nexport const controlHeight = {\n default: \"h-control-md\",\n small: \"h-control-sm\",\n} as const;\n\nexport type ControlSize = keyof typeof controlHeight;\n\n/**\n * States for a box that WRAPS the real control (Input, NativeSelect, the phone\n * prefix). Read from the control inside: `aria-invalid` (set by `invalid` or by\n * Field), `:focus` and `:disabled` (of the input/select only: a native select's\n * placeholder `<option disabled>` must not grey the box out). Buttons inside the box\n * (reveal password, clear) keep their own focus ring and don't light the box up.\n */\nexport const wrappedControlStates = [\n \"has-[[aria-invalid=true]]:border-error has-[[aria-invalid=true]]:ring-error\",\n \"has-[:focus:not(button)]:border-border-focus has-[:focus:not(button)]:ring-border-focus\",\n \"has-[:is(input,select):disabled]:cursor-not-allowed has-[:is(input,select):disabled]:border-border-subtle has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled has-[:is(input,select):disabled]:shadow-none\",\n].join(\" \");\n\n/** States for a box that IS the control (Textarea). */\nexport const selfControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus\",\n \"aria-invalid:not-focus:border-error aria-invalid:not-focus:ring-error\",\n \"disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface disabled:text-text-disabled disabled:shadow-none\",\n].join(\" \");\n\n/**\n * States for a button box (Select trigger). It keeps the focus border while its popup\n * is open, because focus is inside the list then.\n */\nexport const triggerControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus data-popup-open:border-border-focus data-popup-open:ring-border-focus\",\n \"aria-invalid:not-focus:not-data-popup-open:border-error aria-invalid:not-focus:not-data-popup-open:ring-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n].join(\" \");\n\n/** Placeholder: the same Value node in text-secondary, never a variant. */\nexport const controlPlaceholder =\n \"placeholder:text-text-secondary disabled:placeholder:text-text-disabled\";\n\n/**\n * Leading / trailing icon slot: 16px in text-secondary (5.07:1), disabled with the\n * box. The box carries `group/control`.\n */\nexport const controlIcon =\n \"flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled\";\n\n/** Small icon button inside the box (reveal password, clear). 44px hit area. */\nexport const controlAction =\n \"focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm\";\n"],"names":[],"mappings":"AAWO,MAAM,aACX;AAGK,MAAM,aAAa;AAGnB,MAAM,gBAAgB;AAAA,EAC3B,SAAS;AAAA,EACT,OAAO;AACT;AAWO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAMH,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,qBACX;AAMK,MAAM,cACX;AAGK,MAAM,gBACX;"}
@@ -35,6 +35,7 @@ export interface LinkProps extends Omit<useRender.ComponentProps<"a", LinkState>
35
35
  * paragraph it must stay off (it would overlap the lines around it).
36
36
  */
37
37
  standalone?: boolean;
38
+ hoverArrow?: boolean;
38
39
  className?: string;
39
40
  }
40
41
  /**
@@ -49,4 +50,4 @@ export interface LinkProps extends Omit<useRender.ComponentProps<"a", LinkState>
49
50
  * - A standalone link that is the main action of a block is a `Button` with
50
51
  * `variant="ghost"`, not a Link.
51
52
  */
52
- export declare function Link({ size, disabled, trailingIcon, external, newTabLabel, standalone, className, render, href, children, ...props }: LinkProps): React.JSX.Element;
53
+ export declare function Link({ size, disabled, trailingIcon, external, newTabLabel, standalone, hoverArrow, className, render, href, children, ...props }: LinkProps): React.JSX.Element;
@@ -4,25 +4,26 @@ import { useRender } from "@base-ui/react/use-render";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { LinkExternal01Icon } from "../../icons/generated/link-external-01.js";
6
6
  import { cn } from "../../lib/cn.js";
7
+ import { HoverArrow } from "../hover-arrow/hover-arrow.js";
7
8
  const linkVariants = cva(
8
9
  [
9
10
  // Always underlined: colour can't be the only cue that it is a link (WCAG 1.4.1).
10
- "rounded-xs underline underline-offset-2 focus-ring",
11
+ "hover-trigger rounded-xs underline underline-offset-2 interactive focus-ring",
11
12
  "[&_svg]:shrink-0"
12
13
  ],
13
14
  {
14
15
  variants: {
15
16
  // Follows the body ramp: an inline link measures the same as the text around it.
16
17
  size: {
17
- default: "text-body-small [&_svg]:size-icon-sm",
18
- large: "text-body-default [&_svg]:size-icon-md"
18
+ default: "text-body-small [--hover-arrow-size:var(--spacing-icon-sm)] [&_svg]:size-icon-sm",
19
+ large: "text-body-default [--hover-arrow-size:var(--spacing-icon-md)] [&_svg]:size-icon-md"
19
20
  },
20
21
  disabled: {
21
22
  true: "cursor-not-allowed text-text-disabled",
22
23
  false: "cursor-pointer text-text-brand decoration-border hover:decoration-current"
23
24
  },
24
25
  withIcon: {
25
- true: "inline-flex items-center gap-1",
26
+ true: "inline-flex items-center gap-1 [--hover-arrow-gap:--spacing(1)] [--hover-arrow-space:--spacing(1)]",
26
27
  false: ""
27
28
  },
28
29
  standalone: {
@@ -48,6 +49,7 @@ function Link({
48
49
  external = false,
49
50
  newTabLabel,
50
51
  standalone = false,
52
+ hoverArrow = false,
51
53
  className,
52
54
  render,
53
55
  href,
@@ -76,7 +78,8 @@ function Link({
76
78
  " ",
77
79
  newTabLabel
78
80
  ] }),
79
- icon
81
+ icon,
82
+ hoverArrow && !disabled && /* @__PURE__ */ jsx(HoverArrow, {})
80
83
  ] })
81
84
  };
82
85
  const disabledProps = disabled ? { role: "link", "aria-disabled": true, onClick: preventNavigation } : {};
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","sources":["../../../src/components/link/link.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva } from \"class-variance-authority\";\nimport type { MouseEvent, ReactNode } from \"react\";\n\nimport { LinkExternal01Icon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\n\nexport type LinkSize = \"default\" | \"large\";\n\nexport const linkVariants = cva(\n [\n // Always underlined: colour can't be the only cue that it is a link (WCAG 1.4.1).\n \"rounded-xs underline underline-offset-2 focus-ring\",\n \"[&_svg]:shrink-0\",\n ],\n {\n variants: {\n // Follows the body ramp: an inline link measures the same as the text around it.\n size: {\n default: \"text-body-small [&_svg]:size-icon-sm\",\n large: \"text-body-default [&_svg]:size-icon-md\",\n },\n disabled: {\n true: \"cursor-not-allowed text-text-disabled\",\n false:\n \"cursor-pointer text-text-brand decoration-border hover:decoration-current\",\n },\n withIcon: {\n true: \"inline-flex items-center gap-1\",\n false: \"\",\n },\n standalone: {\n true: \"touch-target\",\n false: \"\",\n },\n },\n defaultVariants: {\n size: \"default\",\n disabled: false,\n withIcon: false,\n standalone: false,\n },\n },\n);\n\nexport interface LinkState extends Record<string, unknown> {\n disabled: boolean;\n}\n\nexport interface LinkProps\n extends Omit<useRender.ComponentProps<\"a\", LinkState>, \"className\"> {\n size?: LinkSize;\n /**\n * Not focusable and not clickable. An `<a>` can't be disabled natively, so the\n * `href` is dropped and it is exposed as `aria-disabled`. With `render`, pass the\n * router's own disabled prop too.\n */\n disabled?: boolean;\n /** Icon after the label, e.g. `<ArrowRightIcon />`. `external` puts `link-external-01` here. */\n trailingIcon?: ReactNode;\n /**\n * Opens in a new tab (`target=\"_blank\"`, `rel=\"noopener noreferrer\"`) and shows the\n * external icon. For documents: privacy policy, terms, a PDF.\n */\n external?: boolean;\n /**\n * Screen-reader text appended when `external`, e.g. \"(se abre en una pestaña nueva)\":\n * the icon alone isn't announced.\n */\n newTabLabel?: string;\n /**\n * On its own row, not inside running text: extends the hit area to 44px. Inside a\n * paragraph it must stay off (it would overlap the lines around it).\n */\n standalone?: boolean;\n className?: string;\n}\n\nconst preventNavigation = (event: MouseEvent<HTMLAnchorElement>): void => {\n event.preventDefault();\n};\n\n/**\n * Text link. A link NAVIGATES; a button ACTS (`Button`). Renders an `<a>`; pass\n * `render` to use the router's link, e.g. `render={<RouterLink to=\"/citas\" />}`.\n *\n * - Always underlined. Only on a row of its own, never inside a paragraph, can a\n * screen drop the underline (`className=\"no-underline\"`): there the position already\n * says it is a link.\n * - `default` (14) and `large` (16) follow the body text; if the paragraph uses\n * another size, it isn't this component but text styled as a link.\n * - A standalone link that is the main action of a block is a `Button` with\n * `variant=\"ghost\"`, not a Link.\n */\nexport function Link({\n size,\n disabled = false,\n trailingIcon,\n external = false,\n newTabLabel,\n standalone = false,\n className,\n render,\n href,\n children,\n ...props\n}: LinkProps): React.JSX.Element {\n const icon = trailingIcon ?? (external ? <LinkExternal01Icon /> : undefined);\n\n const defaultProps: useRender.ElementProps<\"a\"> & { \"data-slot\": string } = {\n \"data-slot\": \"link\",\n className: cn(\n linkVariants({\n size,\n disabled,\n withIcon: icon !== undefined,\n standalone,\n }),\n className,\n ),\n // Only when there is one: an explicit `href: undefined` would reach (and could\n // override) the href a router link computes.\n ...(href !== undefined && !disabled ? { href } : {}),\n ...(external ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {}),\n children: (\n <>\n {children}\n {external && newTabLabel && (\n <span className=\"sr-only\"> {newTabLabel}</span>\n )}\n {icon}\n </>\n ),\n };\n\n // Last, so they win over whatever came in `props`.\n const disabledProps: useRender.ElementProps<\"a\"> = disabled\n ? { role: \"link\", \"aria-disabled\": true, onClick: preventNavigation }\n : {};\n\n return useRender({\n defaultTagName: \"a\",\n render,\n state: { disabled },\n props: mergeProps<\"a\">(defaultProps, props, disabledProps),\n });\n}\n"],"names":[],"mappings":";;;;;;AAUO,MAAM,eAAe;AAAA,EAC1B;AAAA;AAAA,IAEE;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA;AAAA,MAER,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,MAET,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OACE;AAAA,MAAA;AAAA,MAEJ,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,MAET,YAAY;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU;AAAA,MACV,YAAY;AAAA,IAAA;AAAA,EACd;AAEJ;AAmCA,MAAM,oBAAoB,CAAC,UAA+C;AACxE,QAAM,eAAA;AACR;AAcO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiC;AAC/B,QAAM,OAAO,iBAAiB,WAAW,oBAAC,sBAAmB,IAAK;AAElE,QAAM,eAAsE;AAAA,IAC1E,aAAa;AAAA,IACb,WAAW;AAAA,MACT,aAAa;AAAA,QACX;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,QACnB;AAAA,MAAA,CACD;AAAA,MACD;AAAA,IAAA;AAAA;AAAA;AAAA,IAIF,GAAI,SAAS,UAAa,CAAC,WAAW,EAAE,KAAA,IAAS,CAAA;AAAA,IACjD,GAAI,WAAW,EAAE,QAAQ,UAAU,KAAK,sBAAA,IAA0B,CAAA;AAAA,IAClE,UACE,qBAAA,UAAA,EACG,UAAA;AAAA,MAAA;AAAA,MACA,YAAY,eACX,qBAAC,QAAA,EAAK,WAAU,WAAU,UAAA;AAAA,QAAA;AAAA,QAAE;AAAA,MAAA,GAAY;AAAA,MAEzC;AAAA,IAAA,EAAA,CACH;AAAA,EAAA;AAKJ,QAAM,gBAA6C,WAC/C,EAAE,MAAM,QAAQ,iBAAiB,MAAM,SAAS,kBAAA,IAChD,CAAA;AAEJ,SAAO,UAAU;AAAA,IACf,gBAAgB;AAAA,IAChB;AAAA,IACA,OAAO,EAAE,SAAA;AAAA,IACT,OAAO,WAAgB,cAAc,OAAO,aAAa;AAAA,EAAA,CAC1D;AACH;"}
1
+ {"version":3,"file":"link.js","sources":["../../../src/components/link/link.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva } from \"class-variance-authority\";\nimport type { MouseEvent, ReactNode } from \"react\";\n\nimport { LinkExternal01Icon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { HoverArrow } from \"../hover-arrow\";\n\nexport type LinkSize = \"default\" | \"large\";\n\nexport const linkVariants = cva(\n [\n // Always underlined: colour can't be the only cue that it is a link (WCAG 1.4.1).\n \"hover-trigger rounded-xs underline underline-offset-2 interactive focus-ring\",\n \"[&_svg]:shrink-0\",\n ],\n {\n variants: {\n // Follows the body ramp: an inline link measures the same as the text around it.\n size: {\n default:\n \"text-body-small [--hover-arrow-size:var(--spacing-icon-sm)] [&_svg]:size-icon-sm\",\n large:\n \"text-body-default [--hover-arrow-size:var(--spacing-icon-md)] [&_svg]:size-icon-md\",\n },\n disabled: {\n true: \"cursor-not-allowed text-text-disabled\",\n false:\n \"cursor-pointer text-text-brand decoration-border hover:decoration-current\",\n },\n withIcon: {\n true: \"inline-flex items-center gap-1 [--hover-arrow-gap:--spacing(1)] [--hover-arrow-space:--spacing(1)]\",\n false: \"\",\n },\n standalone: {\n true: \"touch-target\",\n false: \"\",\n },\n },\n defaultVariants: {\n size: \"default\",\n disabled: false,\n withIcon: false,\n standalone: false,\n },\n },\n);\n\nexport interface LinkState extends Record<string, unknown> {\n disabled: boolean;\n}\n\nexport interface LinkProps\n extends Omit<useRender.ComponentProps<\"a\", LinkState>, \"className\"> {\n size?: LinkSize;\n /**\n * Not focusable and not clickable. An `<a>` can't be disabled natively, so the\n * `href` is dropped and it is exposed as `aria-disabled`. With `render`, pass the\n * router's own disabled prop too.\n */\n disabled?: boolean;\n /** Icon after the label, e.g. `<ArrowRightIcon />`. `external` puts `link-external-01` here. */\n trailingIcon?: ReactNode;\n /**\n * Opens in a new tab (`target=\"_blank\"`, `rel=\"noopener noreferrer\"`) and shows the\n * external icon. For documents: privacy policy, terms, a PDF.\n */\n external?: boolean;\n /**\n * Screen-reader text appended when `external`, e.g. \"(se abre en una pestaña nueva)\":\n * the icon alone isn't announced.\n */\n newTabLabel?: string;\n /**\n * On its own row, not inside running text: extends the hit area to 44px. Inside a\n * paragraph it must stay off (it would overlap the lines around it).\n */\n standalone?: boolean;\n hoverArrow?: boolean;\n className?: string;\n}\n\nconst preventNavigation = (event: MouseEvent<HTMLAnchorElement>): void => {\n event.preventDefault();\n};\n\n/**\n * Text link. A link NAVIGATES; a button ACTS (`Button`). Renders an `<a>`; pass\n * `render` to use the router's link, e.g. `render={<RouterLink to=\"/citas\" />}`.\n *\n * - Always underlined. Only on a row of its own, never inside a paragraph, can a\n * screen drop the underline (`className=\"no-underline\"`): there the position already\n * says it is a link.\n * - `default` (14) and `large` (16) follow the body text; if the paragraph uses\n * another size, it isn't this component but text styled as a link.\n * - A standalone link that is the main action of a block is a `Button` with\n * `variant=\"ghost\"`, not a Link.\n */\nexport function Link({\n size,\n disabled = false,\n trailingIcon,\n external = false,\n newTabLabel,\n standalone = false,\n hoverArrow = false,\n className,\n render,\n href,\n children,\n ...props\n}: LinkProps): React.JSX.Element {\n const icon = trailingIcon ?? (external ? <LinkExternal01Icon /> : undefined);\n\n const defaultProps: useRender.ElementProps<\"a\"> & { \"data-slot\": string } = {\n \"data-slot\": \"link\",\n className: cn(\n linkVariants({\n size,\n disabled,\n withIcon: icon !== undefined,\n standalone,\n }),\n className,\n ),\n // Only when there is one: an explicit `href: undefined` would reach (and could\n // override) the href a router link computes.\n ...(href !== undefined && !disabled ? { href } : {}),\n ...(external ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {}),\n children: (\n <>\n {children}\n {external && newTabLabel && (\n <span className=\"sr-only\"> {newTabLabel}</span>\n )}\n {icon}\n {hoverArrow && !disabled && <HoverArrow />}\n </>\n ),\n };\n\n // Last, so they win over whatever came in `props`.\n const disabledProps: useRender.ElementProps<\"a\"> = disabled\n ? { role: \"link\", \"aria-disabled\": true, onClick: preventNavigation }\n : {};\n\n return useRender({\n defaultTagName: \"a\",\n render,\n state: { disabled },\n props: mergeProps<\"a\">(defaultProps, props, disabledProps),\n });\n}\n"],"names":[],"mappings":";;;;;;;AAWO,MAAM,eAAe;AAAA,EAC1B;AAAA;AAAA,IAEE;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA;AAAA,MAER,MAAM;AAAA,QACJ,SACE;AAAA,QACF,OACE;AAAA,MAAA;AAAA,MAEJ,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OACE;AAAA,MAAA;AAAA,MAEJ,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,MAET,YAAY;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU;AAAA,MACV,YAAY;AAAA,IAAA;AAAA,EACd;AAEJ;AAoCA,MAAM,oBAAoB,CAAC,UAA+C;AACxE,QAAM,eAAA;AACR;AAcO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,aAAa;AAAA,EACb,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiC;AAC/B,QAAM,OAAO,iBAAiB,WAAW,oBAAC,sBAAmB,IAAK;AAElE,QAAM,eAAsE;AAAA,IAC1E,aAAa;AAAA,IACb,WAAW;AAAA,MACT,aAAa;AAAA,QACX;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,QACnB;AAAA,MAAA,CACD;AAAA,MACD;AAAA,IAAA;AAAA;AAAA;AAAA,IAIF,GAAI,SAAS,UAAa,CAAC,WAAW,EAAE,KAAA,IAAS,CAAA;AAAA,IACjD,GAAI,WAAW,EAAE,QAAQ,UAAU,KAAK,sBAAA,IAA0B,CAAA;AAAA,IAClE,UACE,qBAAA,UAAA,EACG,UAAA;AAAA,MAAA;AAAA,MACA,YAAY,eACX,qBAAC,QAAA,EAAK,WAAU,WAAU,UAAA;AAAA,QAAA;AAAA,QAAE;AAAA,MAAA,GAAY;AAAA,MAEzC;AAAA,MACA,cAAc,CAAC,YAAY,oBAAC,YAAA,CAAA,CAAW;AAAA,IAAA,EAAA,CAC1C;AAAA,EAAA;AAKJ,QAAM,gBAA6C,WAC/C,EAAE,MAAM,QAAQ,iBAAiB,MAAM,SAAS,kBAAA,IAChD,CAAA;AAEJ,SAAO,UAAU;AAAA,IACf,gBAAgB;AAAA,IAChB;AAAA,IACA,OAAO,EAAE,SAAA;AAAA,IACT,OAAO,WAAgB,cAAc,OAAO,aAAa;AAAA,EAAA,CAC1D;AACH;"}
@@ -44,7 +44,7 @@ function ListRow({
44
44
  "data-interactive": isInteractive || void 0,
45
45
  className: cn(
46
46
  "relative flex min-h-16 items-center gap-3 rounded-lg px-4 py-3",
47
- "data-interactive:transition-colors data-interactive:hover:bg-surface",
47
+ "data-interactive:interactive data-interactive:hover:bg-surface",
48
48
  className
49
49
  ),
50
50
  ...props,
@@ -1 +1 @@
1
- {"version":3,"file":"list-row.js","sources":["../../../src/components/list-row/list-row.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, MouseEventHandler, ReactNode } from \"react\";\n\nimport { ChevronRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { IconContainer, type IconContainerTone } from \"../icon-container\";\n\nexport interface ListRowProps\n extends Omit<ComponentProps<\"li\">, \"title\" | \"onClick\"> {\n /** Up to two lines, then \"…\". A three-line title means the data is wrong, not that room is missing. */\n title: ReactNode;\n /** Content (14px), one line. */\n subtitle?: ReactNode;\n /** Metadata above the title (12px), one line: \"Pérdida de peso\". */\n overline?: ReactNode;\n /** Leading=Icon: a MediCore icon, drawn inside a 40px `IconContainer`. */\n icon?: ReactNode;\n /** Tone of the icon's container. Default `brand` (an entry to a service). */\n iconTone?: IconContainerTone;\n /** Leading=Avatar: an `<Avatar size={40} />`. */\n avatar?: ReactNode;\n /** Leading=Thumbnail: an `<img alt=\"\" />`, cropped to 80×60. */\n thumbnail?: ReactNode;\n /**\n * Trailing=Action: something that acts right here without leaving (download a\n * document, a \"⋮\" menu) — a ghost icon button with its OWN `aria-label`\n * (\"Descargar informe del 5 de enero\"). It is a second interactive element next\n * to the row's link, never nested inside it.\n */\n action?: ReactNode;\n /** The row navigates: it becomes a link and shows the chevron. */\n href?: string;\n /** Router links: `render={<Link to=\"…\" />}`. Same as `href`: link + chevron. */\n render?: useRender.RenderProp;\n /** The row acts without navigating (opens a sheet, picks an option): it becomes a `<button>`. */\n onClick?: MouseEventHandler<HTMLElement>;\n}\n\n/**\n * The portal's list row: leading · overline + title + subtitle · trailing.\n *\n * - It is an `<li>`: put it in a `<ul>` (`CardList` inside a card).\n * - Pressable rows (`href`, `render` or `onClick`) are pressable from edge to edge:\n * the title is the link/button and it stretches over the whole row. Its\n * accessible name is the title.\n * - Trailing follows from what the row does: a chevron when it navigates (link),\n * `action` when something happens right here, nothing when it's static. If the\n * whole row navigates, don't add a button inside as well.\n * - Minimum height 64 so even a one-word row has a big enough target.\n * - No fill of its own: it lives inside a Card or a surface. Separate rows with\n * `Divider`, never a border on each row.\n */\nexport function ListRow({\n title,\n subtitle,\n overline,\n icon,\n iconTone = \"brand\",\n avatar,\n thumbnail,\n action,\n href,\n render,\n onClick,\n className,\n ...props\n}: ListRowProps): React.JSX.Element {\n const isLink = Boolean(href || render);\n const isInteractive = isLink || Boolean(onClick);\n\n const target = useRender({\n enabled: isInteractive,\n render,\n defaultTagName: isLink ? \"a\" : \"button\",\n props: {\n \"data-slot\": \"list-row-target\",\n href,\n onClick,\n type: isLink ? undefined : \"button\",\n className: cn(\n \"block w-full cursor-pointer text-left outline-none\",\n \"after:absolute after:inset-0 after:rounded-lg\",\n \"focus-visible:after:outline-2 focus-visible:after:outline-border-focus focus-visible:after:outline-offset-2\",\n ),\n children: <span className=\"line-clamp-2\">{title}</span>,\n },\n });\n\n return (\n <li\n data-slot=\"list-row\"\n data-interactive={isInteractive || undefined}\n className={cn(\n \"relative flex min-h-16 items-center gap-3 rounded-lg px-4 py-3\",\n \"data-interactive:transition-colors data-interactive:hover:bg-surface\",\n className,\n )}\n {...props}\n >\n {icon && <IconContainer icon={icon} size={40} tone={iconTone} />}\n {avatar}\n {thumbnail && (\n <span className=\"flex h-15 w-20 shrink-0 overflow-hidden rounded-lg bg-surface-raised *:size-full *:object-cover\">\n {thumbnail}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n {overline && (\n <p className=\"truncate text-caption-default text-text-secondary\">\n {overline}\n </p>\n )}\n <div className=\"text-body-default text-text-primary\">\n {target ?? <p className=\"line-clamp-2\">{title}</p>}\n </div>\n {subtitle && (\n <p className=\"truncate text-body-small text-text-secondary\">\n {subtitle}\n </p>\n )}\n </div>\n\n {action ? (\n // Above the stretched link so it gets its own clicks.\n <div className=\"relative z-10 flex shrink-0 items-center gap-2\">\n {action}\n </div>\n ) : (\n isLink && <ChevronRightIcon size=\"md\" className=\"text-text-secondary\" />\n )}\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;AAoDO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoC;AAClC,QAAM,SAAS,QAAQ,QAAQ,MAAM;AACrC,QAAM,gBAAgB,UAAU,QAAQ,OAAO;AAE/C,QAAM,SAAS,UAAU;AAAA,IACvB,SAAS;AAAA,IACT;AAAA,IACA,gBAAgB,SAAS,MAAM;AAAA,IAC/B,OAAO;AAAA,MACL,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,UAAU,oBAAC,QAAA,EAAK,WAAU,gBAAgB,UAAA,MAAA,CAAM;AAAA,IAAA;AAAA,EAClD,CACD;AAED,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,oBAAkB,iBAAiB;AAAA,MACnC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,4BAAS,eAAA,EAAc,MAAY,MAAM,IAAI,MAAM,UAAU;AAAA,QAC7D;AAAA,QACA,aACC,oBAAC,QAAA,EAAK,WAAU,mGACb,UAAA,WACH;AAAA,QAGF,qBAAC,OAAA,EAAI,WAAU,wCACZ,UAAA;AAAA,UAAA,YACC,oBAAC,KAAA,EAAE,WAAU,qDACV,UAAA,UACH;AAAA,UAEF,oBAAC,OAAA,EAAI,WAAU,uCACZ,UAAA,8BAAW,KAAA,EAAE,WAAU,gBAAgB,UAAA,MAAA,CAAM,EAAA,CAChD;AAAA,UACC,YACC,oBAAC,KAAA,EAAE,WAAU,gDACV,UAAA,SAAA,CACH;AAAA,QAAA,GAEJ;AAAA,QAEC;AAAA;AAAA,UAEC,oBAAC,OAAA,EAAI,WAAU,kDACZ,UAAA,OAAA,CACH;AAAA,YAEA,UAAU,oBAAC,kBAAA,EAAiB,MAAK,MAAK,WAAU,sBAAA,CAAsB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAI9E;"}
1
+ {"version":3,"file":"list-row.js","sources":["../../../src/components/list-row/list-row.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, MouseEventHandler, ReactNode } from \"react\";\n\nimport { ChevronRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { IconContainer, type IconContainerTone } from \"../icon-container\";\n\nexport interface ListRowProps\n extends Omit<ComponentProps<\"li\">, \"title\" | \"onClick\"> {\n /** Up to two lines, then \"…\". A three-line title means the data is wrong, not that room is missing. */\n title: ReactNode;\n /** Content (14px), one line. */\n subtitle?: ReactNode;\n /** Metadata above the title (12px), one line: \"Pérdida de peso\". */\n overline?: ReactNode;\n /** Leading=Icon: a MediCore icon, drawn inside a 40px `IconContainer`. */\n icon?: ReactNode;\n /** Tone of the icon's container. Default `brand` (an entry to a service). */\n iconTone?: IconContainerTone;\n /** Leading=Avatar: an `<Avatar size={40} />`. */\n avatar?: ReactNode;\n /** Leading=Thumbnail: an `<img alt=\"\" />`, cropped to 80×60. */\n thumbnail?: ReactNode;\n /**\n * Trailing=Action: something that acts right here without leaving (download a\n * document, a \"⋮\" menu) — a ghost icon button with its OWN `aria-label`\n * (\"Descargar informe del 5 de enero\"). It is a second interactive element next\n * to the row's link, never nested inside it.\n */\n action?: ReactNode;\n /** The row navigates: it becomes a link and shows the chevron. */\n href?: string;\n /** Router links: `render={<Link to=\"…\" />}`. Same as `href`: link + chevron. */\n render?: useRender.RenderProp;\n /** The row acts without navigating (opens a sheet, picks an option): it becomes a `<button>`. */\n onClick?: MouseEventHandler<HTMLElement>;\n}\n\n/**\n * The portal's list row: leading · overline + title + subtitle · trailing.\n *\n * - It is an `<li>`: put it in a `<ul>` (`CardList` inside a card).\n * - Pressable rows (`href`, `render` or `onClick`) are pressable from edge to edge:\n * the title is the link/button and it stretches over the whole row. Its\n * accessible name is the title.\n * - Trailing follows from what the row does: a chevron when it navigates (link),\n * `action` when something happens right here, nothing when it's static. If the\n * whole row navigates, don't add a button inside as well.\n * - Minimum height 64 so even a one-word row has a big enough target.\n * - No fill of its own: it lives inside a Card or a surface. Separate rows with\n * `Divider`, never a border on each row.\n */\nexport function ListRow({\n title,\n subtitle,\n overline,\n icon,\n iconTone = \"brand\",\n avatar,\n thumbnail,\n action,\n href,\n render,\n onClick,\n className,\n ...props\n}: ListRowProps): React.JSX.Element {\n const isLink = Boolean(href || render);\n const isInteractive = isLink || Boolean(onClick);\n\n const target = useRender({\n enabled: isInteractive,\n render,\n defaultTagName: isLink ? \"a\" : \"button\",\n props: {\n \"data-slot\": \"list-row-target\",\n href,\n onClick,\n type: isLink ? undefined : \"button\",\n className: cn(\n \"block w-full cursor-pointer text-left outline-none\",\n \"after:absolute after:inset-0 after:rounded-lg\",\n \"focus-visible:after:outline-2 focus-visible:after:outline-border-focus focus-visible:after:outline-offset-2\",\n ),\n children: <span className=\"line-clamp-2\">{title}</span>,\n },\n });\n\n return (\n <li\n data-slot=\"list-row\"\n data-interactive={isInteractive || undefined}\n className={cn(\n \"relative flex min-h-16 items-center gap-3 rounded-lg px-4 py-3\",\n \"data-interactive:interactive data-interactive:hover:bg-surface\",\n className,\n )}\n {...props}\n >\n {icon && <IconContainer icon={icon} size={40} tone={iconTone} />}\n {avatar}\n {thumbnail && (\n <span className=\"flex h-15 w-20 shrink-0 overflow-hidden rounded-lg bg-surface-raised *:size-full *:object-cover\">\n {thumbnail}\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n {overline && (\n <p className=\"truncate text-caption-default text-text-secondary\">\n {overline}\n </p>\n )}\n <div className=\"text-body-default text-text-primary\">\n {target ?? <p className=\"line-clamp-2\">{title}</p>}\n </div>\n {subtitle && (\n <p className=\"truncate text-body-small text-text-secondary\">\n {subtitle}\n </p>\n )}\n </div>\n\n {action ? (\n // Above the stretched link so it gets its own clicks.\n <div className=\"relative z-10 flex shrink-0 items-center gap-2\">\n {action}\n </div>\n ) : (\n isLink && <ChevronRightIcon size=\"md\" className=\"text-text-secondary\" />\n )}\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;AAoDO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoC;AAClC,QAAM,SAAS,QAAQ,QAAQ,MAAM;AACrC,QAAM,gBAAgB,UAAU,QAAQ,OAAO;AAE/C,QAAM,SAAS,UAAU;AAAA,IACvB,SAAS;AAAA,IACT;AAAA,IACA,gBAAgB,SAAS,MAAM;AAAA,IAC/B,OAAO;AAAA,MACL,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,UAAU,oBAAC,QAAA,EAAK,WAAU,gBAAgB,UAAA,MAAA,CAAM;AAAA,IAAA;AAAA,EAClD,CACD;AAED,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,oBAAkB,iBAAiB;AAAA,MACnC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,4BAAS,eAAA,EAAc,MAAY,MAAM,IAAI,MAAM,UAAU;AAAA,QAC7D;AAAA,QACA,aACC,oBAAC,QAAA,EAAK,WAAU,mGACb,UAAA,WACH;AAAA,QAGF,qBAAC,OAAA,EAAI,WAAU,wCACZ,UAAA;AAAA,UAAA,YACC,oBAAC,KAAA,EAAE,WAAU,qDACV,UAAA,UACH;AAAA,UAEF,oBAAC,OAAA,EAAI,WAAU,uCACZ,UAAA,8BAAW,KAAA,EAAE,WAAU,gBAAgB,UAAA,MAAA,CAAM,EAAA,CAChD;AAAA,UACC,YACC,oBAAC,KAAA,EAAE,WAAU,gDACV,UAAA,SAAA,CACH;AAAA,QAAA,GAEJ;AAAA,QAEC;AAAA;AAAA,UAEC,oBAAC,OAAA,EAAI,WAAU,kDACZ,UAAA,OAAA,CACH;AAAA,YAEA,UAAU,oBAAC,kBAAA,EAAiB,MAAK,MAAK,WAAU,sBAAA,CAAsB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAI9E;"}
@@ -118,7 +118,7 @@ function MenuContent({
118
118
  // The shadow separates it from the content below; the border, where the
119
119
  // shadow doesn't show (dark backgrounds, poor screens).
120
120
  "flex w-60 flex-col rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-hidden",
121
- "origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out",
121
+ "origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out-soft",
122
122
  "data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0",
123
123
  "motion-reduce:transition-none",
124
124
  className
@@ -1 +1 @@
1
- {"version":3,"file":"menu.js","sources":["../../../src/components/menu/menu.tsx"],"sourcesContent":["import { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { cva } from \"class-variance-authority\";\nimport {\n type CSSProperties,\n createContext,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n useContext,\n} from \"react\";\n\nimport { DotsHorizontalIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Divider } from \"../divider\";\nimport { IconButton } from \"../icon-button\";\nimport {\n Modal,\n type ModalChangeEventDetails,\n ModalClose,\n ModalContent,\n ModalTrigger,\n} from \"../modal/modal\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"../modal/use-media-query\";\n\n/**\n * `popover`: the floating 240px menu next to the trigger. `sheet`: a bottom sheet\n * (`Modal layout=\"sheet\"`) with the same options in 48px rows.\n */\nexport type MenuLayout = \"popover\" | \"sheet\";\n\nexport type MenuChangeEventDetails =\n | BaseMenu.Root.ChangeEventDetails\n | ModalChangeEventDetails;\n\nconst MenuLayoutContext = createContext<MenuLayout | null>(null);\n\nfunction useMenuLayout(part: string): MenuLayout {\n const layout = useContext(MenuLayoutContext);\n if (layout === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Menu>.`);\n }\n return layout;\n}\n\nexport interface MenuProps {\n /**\n * Leave it unset and it follows the screen: a sheet below `md` (a 240px menu next\n * to the thumb covers what the person is looking at) and a popover from `md` up.\n */\n layout?: MenuLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: MenuChangeEventDetails) => void;\n /** Disables the whole menu (popover only). */\n disabled?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Contextual menu: what the \"⋯\" of a Card, an appointment or the chat header opens.\n * It is where everything that isn't the block's single primary action lives.\n *\n * Up to four options and, if there is one, the destructive option LAST, after a\n * `MenuSeparator` — never in the middle, where a thumb going down the list would\n * cross \"Cancelar cita\" on its way to \"Compartir\". If you need more than five, those\n * things deserve their own screen.\n *\n * Keyboard: arrow keys move between options, Enter picks, Escape closes and focus\n * returns to the trigger.\n */\nexport function Menu({\n layout,\n open,\n defaultOpen,\n onOpenChange,\n disabled,\n children,\n}: MenuProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: MenuLayout =\n layout ?? (isDesktop ? \"popover\" : \"sheet\");\n\n return (\n <MenuLayoutContext.Provider value={resolvedLayout}>\n {resolvedLayout === \"sheet\" ? (\n <Modal\n layout=\"sheet\"\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n >\n {children}\n </Modal>\n ) : (\n <BaseMenu.Root\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n disabled={disabled}\n >\n {children}\n </BaseMenu.Root>\n )}\n </MenuLayoutContext.Provider>\n );\n}\n\nexport interface MenuTriggerProps\n extends Omit<\n BaseMenu.Trigger.Props,\n | \"className\"\n | \"style\"\n | \"render\"\n | \"handle\"\n | \"payload\"\n | \"openOnHover\"\n | \"delay\"\n | \"closeDelay\"\n > {\n className?: string;\n style?: CSSProperties;\n /** Your own trigger element. Without `render` and `children` you get the \"⋯\" button. */\n render?: ReactElement;\n}\n\n/**\n * Opens the menu. With no `children` or `render` it is the \"⋯\" ghost icon button,\n * named with the MediCore `moreOptions` label — pass `aria-label` to be more\n * specific (\"Opciones de la cita\").\n */\nexport function MenuTrigger({\n render,\n children,\n \"aria-label\": ariaLabel,\n ...props\n}: MenuTriggerProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuTrigger\");\n const labels = useMediCoreLabels();\n const Trigger = layout === \"sheet\" ? ModalTrigger : BaseMenu.Trigger;\n\n if (render === undefined && children === undefined) {\n return (\n <Trigger\n data-slot=\"menu-trigger\"\n render={\n <IconButton\n variant=\"ghost\"\n icon={<DotsHorizontalIcon />}\n aria-label={ariaLabel ?? labels.moreOptions}\n />\n }\n {...props}\n />\n );\n }\n\n return (\n <Trigger\n data-slot=\"menu-trigger\"\n aria-label={ariaLabel}\n render={render}\n {...props}\n >\n {children}\n </Trigger>\n );\n}\n\nexport interface MenuContentProps {\n children?: ReactNode;\n className?: string;\n /** Popover only: which side of the trigger it opens on. Flips when there is no room. */\n side?: \"top\" | \"bottom\";\n /** Popover only. `end` by default: the \"⋯\" usually sits at the right edge. */\n align?: \"start\" | \"center\" | \"end\";\n /** Sheet only: accessible name of the sheet. Defaults to the `moreOptions` label. */\n label?: string;\n}\n\n/** The list of options. 240px popover with `shadow-lg` and a border, or a bottom sheet. */\nexport function MenuContent({\n children,\n className,\n side = \"bottom\",\n align = \"end\",\n label,\n}: MenuContentProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuContent\");\n const labels = useMediCoreLabels();\n\n if (layout === \"sheet\") {\n return (\n <ModalContent aria-label={label ?? labels.moreOptions}>\n <div\n data-slot=\"menu-content\"\n className={cn(\"-mx-3 flex flex-col\", className)}\n >\n {children}\n </div>\n </ModalContent>\n );\n }\n\n return (\n <BaseMenu.Portal>\n <BaseMenu.Positioner\n side={side}\n align={align}\n sideOffset={4}\n className=\"z-50 outline-hidden\"\n >\n <BaseMenu.Popup\n data-slot=\"menu-content\"\n className={cn(\n // The shadow separates it from the content below; the border, where the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"flex w-60 flex-col rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-hidden\",\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:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n >\n {children}\n </BaseMenu.Popup>\n </BaseMenu.Positioner>\n </BaseMenu.Portal>\n );\n}\n\nconst menuItemVariants = cva(\n [\n \"flex w-full shrink-0 cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-left text-label-default\",\n \"outline-hidden focus-visible:outline-2 focus-visible:outline-border-focus focus-visible:outline-solid focus-visible:-outline-offset-2\",\n \"[&_svg]:size-icon-md [&_svg]:shrink-0\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled disabled:cursor-not-allowed disabled:text-text-disabled\",\n ],\n {\n variants: {\n type: {\n default: \"text-text-primary\",\n destructive: \"text-text-error\",\n },\n layout: {\n popover: \"h-control-md\",\n sheet: \"h-12\",\n },\n },\n // Base UI marks the pointer/keyboard position with data-highlighted in the popover;\n // the sheet's rows are plain buttons, so they use :hover.\n compoundVariants: [\n {\n type: \"default\",\n layout: \"popover\",\n className: \"not-data-disabled:data-highlighted:bg-surface\",\n },\n {\n type: \"destructive\",\n layout: \"popover\",\n className: \"not-data-disabled:data-highlighted:bg-error-subtle\",\n },\n {\n type: \"default\",\n layout: \"sheet\",\n className: \"enabled:hover:bg-surface\",\n },\n {\n type: \"destructive\",\n layout: \"sheet\",\n className: \"enabled:hover:bg-error-subtle\",\n },\n ],\n defaultVariants: {\n type: \"default\",\n layout: \"popover\",\n },\n },\n);\n\nexport interface MenuItemProps {\n /** The option's label. */\n children: ReactNode;\n /** 20px icon before the label. */\n icon?: ReactNode;\n /** `destructive` goes in text-error and always last, after a `MenuSeparator`. */\n type?: \"default\" | \"destructive\";\n disabled?: boolean;\n onClick?: (event: MouseEvent<HTMLElement>) => void;\n /** Closes the menu after the click (default). */\n closeOnClick?: boolean;\n className?: string;\n}\n\n/** One option. 40px tall in the popover (48 in the sheet) so neighbours aren't tapped by mistake. */\nexport function MenuItem({\n children,\n icon,\n type = \"default\",\n disabled = false,\n onClick,\n closeOnClick = true,\n className,\n}: MenuItemProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuItem\");\n const classes = cn(menuItemVariants({ type, layout }), className);\n\n if (layout === \"sheet\") {\n return (\n <ModalClose\n data-slot=\"menu-item\"\n data-type={type}\n disabled={disabled}\n className={classes}\n onClick={(event) => {\n onClick?.(event);\n if (!closeOnClick) {\n event.preventBaseUIHandler();\n }\n }}\n >\n {icon}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </ModalClose>\n );\n }\n\n return (\n <BaseMenu.Item\n data-slot=\"menu-item\"\n data-type={type}\n disabled={disabled}\n closeOnClick={closeOnClick}\n onClick={onClick}\n className={classes}\n >\n {icon}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </BaseMenu.Item>\n );\n}\n\nexport interface MenuSeparatorProps {\n className?: string;\n}\n\n/** The line before the destructive option. */\nexport function MenuSeparator({\n className,\n}: MenuSeparatorProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuSeparator\");\n\n if (layout === \"sheet\") {\n return <Divider className={cn(\"my-1\", className)} />;\n }\n\n return (\n <BaseMenu.Separator\n data-slot=\"menu-separator\"\n className={cn(\"my-1 h-px shrink-0 bg-border-subtle\", className)}\n />\n );\n}\n"],"names":["BaseMenu"],"mappings":"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"}
1
+ {"version":3,"file":"menu.js","sources":["../../../src/components/menu/menu.tsx"],"sourcesContent":["import { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { cva } from \"class-variance-authority\";\nimport {\n type CSSProperties,\n createContext,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n useContext,\n} from \"react\";\n\nimport { DotsHorizontalIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { Divider } from \"../divider\";\nimport { IconButton } from \"../icon-button\";\nimport {\n Modal,\n type ModalChangeEventDetails,\n ModalClose,\n ModalContent,\n ModalTrigger,\n} from \"../modal/modal\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"../modal/use-media-query\";\n\n/**\n * `popover`: the floating 240px menu next to the trigger. `sheet`: a bottom sheet\n * (`Modal layout=\"sheet\"`) with the same options in 48px rows.\n */\nexport type MenuLayout = \"popover\" | \"sheet\";\n\nexport type MenuChangeEventDetails =\n | BaseMenu.Root.ChangeEventDetails\n | ModalChangeEventDetails;\n\nconst MenuLayoutContext = createContext<MenuLayout | null>(null);\n\nfunction useMenuLayout(part: string): MenuLayout {\n const layout = useContext(MenuLayoutContext);\n if (layout === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Menu>.`);\n }\n return layout;\n}\n\nexport interface MenuProps {\n /**\n * Leave it unset and it follows the screen: a sheet below `md` (a 240px menu next\n * to the thumb covers what the person is looking at) and a popover from `md` up.\n */\n layout?: MenuLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: MenuChangeEventDetails) => void;\n /** Disables the whole menu (popover only). */\n disabled?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Contextual menu: what the \"⋯\" of a Card, an appointment or the chat header opens.\n * It is where everything that isn't the block's single primary action lives.\n *\n * Up to four options and, if there is one, the destructive option LAST, after a\n * `MenuSeparator` — never in the middle, where a thumb going down the list would\n * cross \"Cancelar cita\" on its way to \"Compartir\". If you need more than five, those\n * things deserve their own screen.\n *\n * Keyboard: arrow keys move between options, Enter picks, Escape closes and focus\n * returns to the trigger.\n */\nexport function Menu({\n layout,\n open,\n defaultOpen,\n onOpenChange,\n disabled,\n children,\n}: MenuProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: MenuLayout =\n layout ?? (isDesktop ? \"popover\" : \"sheet\");\n\n return (\n <MenuLayoutContext.Provider value={resolvedLayout}>\n {resolvedLayout === \"sheet\" ? (\n <Modal\n layout=\"sheet\"\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n >\n {children}\n </Modal>\n ) : (\n <BaseMenu.Root\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n disabled={disabled}\n >\n {children}\n </BaseMenu.Root>\n )}\n </MenuLayoutContext.Provider>\n );\n}\n\nexport interface MenuTriggerProps\n extends Omit<\n BaseMenu.Trigger.Props,\n | \"className\"\n | \"style\"\n | \"render\"\n | \"handle\"\n | \"payload\"\n | \"openOnHover\"\n | \"delay\"\n | \"closeDelay\"\n > {\n className?: string;\n style?: CSSProperties;\n /** Your own trigger element. Without `render` and `children` you get the \"⋯\" button. */\n render?: ReactElement;\n}\n\n/**\n * Opens the menu. With no `children` or `render` it is the \"⋯\" ghost icon button,\n * named with the MediCore `moreOptions` label — pass `aria-label` to be more\n * specific (\"Opciones de la cita\").\n */\nexport function MenuTrigger({\n render,\n children,\n \"aria-label\": ariaLabel,\n ...props\n}: MenuTriggerProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuTrigger\");\n const labels = useMediCoreLabels();\n const Trigger = layout === \"sheet\" ? ModalTrigger : BaseMenu.Trigger;\n\n if (render === undefined && children === undefined) {\n return (\n <Trigger\n data-slot=\"menu-trigger\"\n render={\n <IconButton\n variant=\"ghost\"\n icon={<DotsHorizontalIcon />}\n aria-label={ariaLabel ?? labels.moreOptions}\n />\n }\n {...props}\n />\n );\n }\n\n return (\n <Trigger\n data-slot=\"menu-trigger\"\n aria-label={ariaLabel}\n render={render}\n {...props}\n >\n {children}\n </Trigger>\n );\n}\n\nexport interface MenuContentProps {\n children?: ReactNode;\n className?: string;\n /** Popover only: which side of the trigger it opens on. Flips when there is no room. */\n side?: \"top\" | \"bottom\";\n /** Popover only. `end` by default: the \"⋯\" usually sits at the right edge. */\n align?: \"start\" | \"center\" | \"end\";\n /** Sheet only: accessible name of the sheet. Defaults to the `moreOptions` label. */\n label?: string;\n}\n\n/** The list of options. 240px popover with `shadow-lg` and a border, or a bottom sheet. */\nexport function MenuContent({\n children,\n className,\n side = \"bottom\",\n align = \"end\",\n label,\n}: MenuContentProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuContent\");\n const labels = useMediCoreLabels();\n\n if (layout === \"sheet\") {\n return (\n <ModalContent aria-label={label ?? labels.moreOptions}>\n <div\n data-slot=\"menu-content\"\n className={cn(\"-mx-3 flex flex-col\", className)}\n >\n {children}\n </div>\n </ModalContent>\n );\n }\n\n return (\n <BaseMenu.Portal>\n <BaseMenu.Positioner\n side={side}\n align={align}\n sideOffset={4}\n className=\"z-50 outline-hidden\"\n >\n <BaseMenu.Popup\n data-slot=\"menu-content\"\n className={cn(\n // The shadow separates it from the content below; the border, where the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"flex w-60 flex-col rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-hidden\",\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:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n >\n {children}\n </BaseMenu.Popup>\n </BaseMenu.Positioner>\n </BaseMenu.Portal>\n );\n}\n\nconst menuItemVariants = cva(\n [\n \"flex w-full shrink-0 cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-left text-label-default\",\n \"outline-hidden focus-visible:outline-2 focus-visible:outline-border-focus focus-visible:outline-solid focus-visible:-outline-offset-2\",\n \"[&_svg]:size-icon-md [&_svg]:shrink-0\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled disabled:cursor-not-allowed disabled:text-text-disabled\",\n ],\n {\n variants: {\n type: {\n default: \"text-text-primary\",\n destructive: \"text-text-error\",\n },\n layout: {\n popover: \"h-control-md\",\n sheet: \"h-12\",\n },\n },\n // Base UI marks the pointer/keyboard position with data-highlighted in the popover;\n // the sheet's rows are plain buttons, so they use :hover.\n compoundVariants: [\n {\n type: \"default\",\n layout: \"popover\",\n className: \"not-data-disabled:data-highlighted:bg-surface\",\n },\n {\n type: \"destructive\",\n layout: \"popover\",\n className: \"not-data-disabled:data-highlighted:bg-error-subtle\",\n },\n {\n type: \"default\",\n layout: \"sheet\",\n className: \"enabled:hover:bg-surface\",\n },\n {\n type: \"destructive\",\n layout: \"sheet\",\n className: \"enabled:hover:bg-error-subtle\",\n },\n ],\n defaultVariants: {\n type: \"default\",\n layout: \"popover\",\n },\n },\n);\n\nexport interface MenuItemProps {\n /** The option's label. */\n children: ReactNode;\n /** 20px icon before the label. */\n icon?: ReactNode;\n /** `destructive` goes in text-error and always last, after a `MenuSeparator`. */\n type?: \"default\" | \"destructive\";\n disabled?: boolean;\n onClick?: (event: MouseEvent<HTMLElement>) => void;\n /** Closes the menu after the click (default). */\n closeOnClick?: boolean;\n className?: string;\n}\n\n/** One option. 40px tall in the popover (48 in the sheet) so neighbours aren't tapped by mistake. */\nexport function MenuItem({\n children,\n icon,\n type = \"default\",\n disabled = false,\n onClick,\n closeOnClick = true,\n className,\n}: MenuItemProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuItem\");\n const classes = cn(menuItemVariants({ type, layout }), className);\n\n if (layout === \"sheet\") {\n return (\n <ModalClose\n data-slot=\"menu-item\"\n data-type={type}\n disabled={disabled}\n className={classes}\n onClick={(event) => {\n onClick?.(event);\n if (!closeOnClick) {\n event.preventBaseUIHandler();\n }\n }}\n >\n {icon}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </ModalClose>\n );\n }\n\n return (\n <BaseMenu.Item\n data-slot=\"menu-item\"\n data-type={type}\n disabled={disabled}\n closeOnClick={closeOnClick}\n onClick={onClick}\n className={classes}\n >\n {icon}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </BaseMenu.Item>\n );\n}\n\nexport interface MenuSeparatorProps {\n className?: string;\n}\n\n/** The line before the destructive option. */\nexport function MenuSeparator({\n className,\n}: MenuSeparatorProps): React.JSX.Element {\n const layout = useMenuLayout(\"MenuSeparator\");\n\n if (layout === \"sheet\") {\n return <Divider className={cn(\"my-1\", className)} />;\n }\n\n return (\n <BaseMenu.Separator\n data-slot=\"menu-separator\"\n className={cn(\"my-1 h-px shrink-0 bg-border-subtle\", className)}\n />\n );\n}\n"],"names":["BaseMenu"],"mappings":"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"}
@@ -135,7 +135,7 @@ function ModalContent({
135
135
  className: cn(
136
136
  "flex max-h-full w-120 max-w-full flex-col gap-4 overflow-y-auto overscroll-contain",
137
137
  "rounded-2xl bg-background p-6 text-text-primary shadow-xl outline-hidden",
138
- "transition-[opacity,scale] duration-200 ease-out",
138
+ "transition-[opacity,scale] duration-200 ease-out-soft",
139
139
  "data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0",
140
140
  "motion-reduce:transition-none",
141
141
  className