@mediquo/medicore 0.2.0 → 0.3.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 (31) hide show
  1. package/dist/components/badge/badge.d.ts +7 -3
  2. package/dist/components/badge/badge.js +27 -4
  3. package/dist/components/badge/badge.js.map +1 -1
  4. package/dist/components/breadcrumb/breadcrumb.d.ts +2 -2
  5. package/dist/components/breadcrumb/breadcrumb.js +4 -4
  6. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  7. package/dist/components/button/button.js +9 -9
  8. package/dist/components/button/button.js.map +1 -1
  9. package/dist/components/chip/chip.d.ts +3 -0
  10. package/dist/components/chip/chip.js +41 -10
  11. package/dist/components/chip/chip.js.map +1 -1
  12. package/dist/components/empty-state/empty-state.d.ts +1 -1
  13. package/dist/components/empty-state/empty-state.js +10 -1
  14. package/dist/components/empty-state/empty-state.js.map +1 -1
  15. package/dist/components/fact-card/fact-card.js +3 -3
  16. package/dist/components/fact-card/fact-card.js.map +1 -1
  17. package/dist/components/input/control-styles.d.ts +1 -1
  18. package/dist/components/input/control-styles.js +4 -4
  19. package/dist/components/input/control-styles.js.map +1 -1
  20. package/dist/components/link/link.js +1 -1
  21. package/dist/components/link/link.js.map +1 -1
  22. package/dist/components/menu/menu.d.ts +1 -1
  23. package/dist/components/menu/menu.js +2 -2
  24. package/dist/components/menu/menu.js.map +1 -1
  25. package/dist/components/modal/modal.js +2 -2
  26. package/dist/components/modal/modal.js.map +1 -1
  27. package/dist/components/phone-input/phone-input.js +1 -1
  28. package/dist/components/phone-input/phone-input.js.map +1 -1
  29. package/dist/components/select/select.js +2 -2
  30. package/dist/components/select/select.js.map +1 -1
  31. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
1
  import type { ComponentProps } from "react";
2
2
  export declare const badgeVariants: (props?: ({
3
- variant?: "error" | "brand" | "neutral" | "success" | "warning" | "info" | null | undefined;
3
+ variant?: "outline" | "error" | "brand" | "neutral" | "success" | "warning" | "info" | null | undefined;
4
4
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
- export type BadgeVariant = "neutral" | "brand" | "success" | "warning" | "error" | "info";
5
+ export type BadgeVariant = "neutral" | "outline" | "brand" | "success" | "warning" | "error" | "info";
6
6
  export interface BadgeProps extends ComponentProps<"span"> {
7
7
  variant?: BadgeVariant;
8
+ /** A coloured dot in front: for statuses ("Abierta", "Pagada"), not for categories. */
9
+ dot?: boolean;
8
10
  }
9
11
  /**
10
12
  * Category or status tag (documents: Receta, Informe, Orden médica; program cards).
@@ -12,9 +14,11 @@ export interface BadgeProps extends ComponentProps<"span"> {
12
14
  * it isn't a badge, it's text.
13
15
  *
14
16
  * - It is NOT a button: no click, no focus, no close icon. Removable → `Chip`.
17
+ * - `outline` is the quiet one for plain metadata (a clinic's name next to a
18
+ * professional); the tinted variants tell a state.
15
19
  * - On a photo use `neutral` (solid). Never a translucent pill: its contrast depends
16
20
  * on the photo.
17
21
  * - `warning` keeps its text in text-primary: the orange-on-orange pair doesn't reach
18
22
  * 4.5:1 with the current tokens.
19
23
  */
20
- export declare function Badge({ variant, className, ...props }: BadgeProps): React.JSX.Element;
24
+ export declare function Badge({ variant, dot, className, children, ...props }: BadgeProps): React.JSX.Element;
@@ -1,12 +1,13 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { cva } from "class-variance-authority";
3
3
  import { cn } from "../../lib/cn.js";
4
4
  const badgeVariants = cva(
5
- "inline-flex h-6 w-fit shrink-0 items-center gap-1 whitespace-nowrap rounded-full px-2.5 text-label-small [&_svg]:size-icon-xs",
5
+ "inline-flex h-6 w-fit shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-2.5 text-label-small font-medium [&_svg]:size-icon-xs",
6
6
  {
7
7
  variants: {
8
8
  variant: {
9
9
  neutral: "bg-surface-raised text-text-primary",
10
+ outline: "bg-background text-text-secondary ring-1 ring-border ring-inset",
10
11
  brand: "bg-brand-subtle text-text-brand",
11
12
  success: "bg-success-subtle text-text-success",
12
13
  // Token gap, not an aesthetic choice: warning-strong on warning-subtle is
@@ -21,17 +22,39 @@ const badgeVariants = cva(
21
22
  }
22
23
  }
23
24
  );
25
+ const badgeDotVariants = cva("size-1.5 shrink-0 rounded-full", {
26
+ variants: {
27
+ variant: {
28
+ neutral: "bg-text-secondary",
29
+ outline: "bg-text-secondary",
30
+ brand: "bg-brand",
31
+ success: "bg-success",
32
+ warning: "bg-warning",
33
+ error: "bg-error",
34
+ info: "bg-info"
35
+ }
36
+ },
37
+ defaultVariants: {
38
+ variant: "neutral"
39
+ }
40
+ });
24
41
  function Badge({
25
42
  variant,
43
+ dot = false,
26
44
  className,
45
+ children,
27
46
  ...props
28
47
  }) {
29
- return /* @__PURE__ */ jsx(
48
+ return /* @__PURE__ */ jsxs(
30
49
  "span",
31
50
  {
32
51
  "data-slot": "badge",
33
52
  className: cn(badgeVariants({ variant }), className),
34
- ...props
53
+ ...props,
54
+ children: [
55
+ dot && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: badgeDotVariants({ variant }) }),
56
+ children
57
+ ]
35
58
  }
36
59
  );
37
60
  }
@@ -1 +1 @@
1
- {"version":3,"file":"badge.js","sources":["../../../src/components/badge/badge.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport const badgeVariants = cva(\n \"inline-flex h-6 w-fit shrink-0 items-center gap-1 whitespace-nowrap rounded-full px-2.5 text-label-small [&_svg]:size-icon-xs\",\n {\n variants: {\n variant: {\n neutral: \"bg-surface-raised text-text-primary\",\n brand: \"bg-brand-subtle text-text-brand\",\n success: \"bg-success-subtle text-text-success\",\n // Token gap, not an aesthetic choice: warning-strong on warning-subtle is\n // 3.93:1, so the text stays in text-primary and the orange lives in the fill.\n warning: \"bg-warning-subtle text-text-primary\",\n error: \"bg-error-subtle text-text-error\",\n info: \"bg-info-subtle text-text-info\",\n },\n },\n defaultVariants: {\n variant: \"neutral\",\n },\n },\n);\n\nexport type BadgeVariant =\n | \"neutral\"\n | \"brand\"\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"info\";\n\nexport interface BadgeProps extends ComponentProps<\"span\"> {\n variant?: BadgeVariant;\n}\n\n/**\n * Category or status tag (documents: Receta, Informe, Orden médica; program cards).\n * 12px (Label/Small) because it is metadata: if people need to read it to decide,\n * it isn't a badge, it's text.\n *\n * - It is NOT a button: no click, no focus, no close icon. Removable → `Chip`.\n * - On a photo use `neutral` (solid). Never a translucent pill: its contrast depends\n * on the photo.\n * - `warning` keeps its text in text-primary: the orange-on-orange pair doesn't reach\n * 4.5:1 with the current tokens.\n */\nexport function Badge({\n variant,\n className,\n ...props\n}: BadgeProps): React.JSX.Element {\n return (\n <span\n data-slot=\"badge\"\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n );\n}\n"],"names":[],"mappings":";;;AAKO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS;AAAA;AAAA;AAAA,QAGT,SAAS;AAAA,QACT,OAAO;AAAA,QACP,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;AAyBO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkC;AAChC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,EAAE,QAAA,CAAS,GAAG,SAAS;AAAA,MAClD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
1
+ {"version":3,"file":"badge.js","sources":["../../../src/components/badge/badge.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport const badgeVariants = cva(\n \"inline-flex h-6 w-fit shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-2.5 text-label-small font-medium [&_svg]:size-icon-xs\",\n {\n variants: {\n variant: {\n neutral: \"bg-surface-raised text-text-primary\",\n outline:\n \"bg-background text-text-secondary ring-1 ring-border ring-inset\",\n brand: \"bg-brand-subtle text-text-brand\",\n success: \"bg-success-subtle text-text-success\",\n // Token gap, not an aesthetic choice: warning-strong on warning-subtle is\n // 3.93:1, so the text stays in text-primary and the orange lives in the fill.\n warning: \"bg-warning-subtle text-text-primary\",\n error: \"bg-error-subtle text-text-error\",\n info: \"bg-info-subtle text-text-info\",\n },\n },\n defaultVariants: {\n variant: \"neutral\",\n },\n },\n);\n\nconst badgeDotVariants = cva(\"size-1.5 shrink-0 rounded-full\", {\n variants: {\n variant: {\n neutral: \"bg-text-secondary\",\n outline: \"bg-text-secondary\",\n brand: \"bg-brand\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n error: \"bg-error\",\n info: \"bg-info\",\n },\n },\n defaultVariants: {\n variant: \"neutral\",\n },\n});\n\nexport type BadgeVariant =\n | \"neutral\"\n | \"outline\"\n | \"brand\"\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"info\";\n\nexport interface BadgeProps extends ComponentProps<\"span\"> {\n variant?: BadgeVariant;\n /** A coloured dot in front: for statuses (\"Abierta\", \"Pagada\"), not for categories. */\n dot?: boolean;\n}\n\n/**\n * Category or status tag (documents: Receta, Informe, Orden médica; program cards).\n * 12px (Label/Small) because it is metadata: if people need to read it to decide,\n * it isn't a badge, it's text.\n *\n * - It is NOT a button: no click, no focus, no close icon. Removable → `Chip`.\n * - `outline` is the quiet one for plain metadata (a clinic's name next to a\n * professional); the tinted variants tell a state.\n * - On a photo use `neutral` (solid). Never a translucent pill: its contrast depends\n * on the photo.\n * - `warning` keeps its text in text-primary: the orange-on-orange pair doesn't reach\n * 4.5:1 with the current tokens.\n */\nexport function Badge({\n variant,\n dot = false,\n className,\n children,\n ...props\n}: BadgeProps): React.JSX.Element {\n return (\n <span\n data-slot=\"badge\"\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n >\n {dot && (\n <span aria-hidden=\"true\" className={badgeDotVariants({ variant })} />\n )}\n {children}\n </span>\n );\n}\n"],"names":[],"mappings":";;;AAKO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,SACE;AAAA,QACF,OAAO;AAAA,QACP,SAAS;AAAA;AAAA;AAAA,QAGT,SAAS;AAAA,QACT,OAAO;AAAA,QACP,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;AAEA,MAAM,mBAAmB,IAAI,kCAAkC;AAAA,EAC7D,UAAU;AAAA,IACR,SAAS;AAAA,MACP,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,MAAM;AAAA,IAAA;AAAA,EACR;AAAA,EAEF,iBAAiB;AAAA,IACf,SAAS;AAAA,EAAA;AAEb,CAAC;AA8BM,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkC;AAChC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,EAAE,QAAA,CAAS,GAAG,SAAS;AAAA,MAClD,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,OACC,oBAAC,UAAK,eAAY,QAAO,WAAW,iBAAiB,EAAE,QAAA,CAAS,GAAG;AAAA,QAEpE;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -17,8 +17,8 @@ export interface BreadcrumbProps extends ComponentProps<"nav"> {
17
17
  * </Breadcrumb>
18
18
  * ```
19
19
  *
20
- * The levels above are quiet (Label/Small in text-secondary, darker on hover), with
21
- * a chevron in text-disabled between them, so the trail never competes with the
20
+ * The levels above are quiet (Label/Default in text-secondary, brand on hover), with
21
+ * a chevron in border-strong between them, so the trail never competes with the
22
22
  * page title under it. The links are not underlined: inside the breadcrumb landmark
23
23
  * and its list, their position already tells they are links (WCAG 1.4.1 applies to
24
24
  * links in running text).
@@ -22,7 +22,7 @@ function Breadcrumb({
22
22
  "ol",
23
23
  {
24
24
  className: cn(
25
- "flex flex-wrap items-center gap-1.5",
25
+ "flex flex-wrap items-center gap-2",
26
26
  // Mobile: only the parent level (the second-to-last item) stays.
27
27
  "max-lg:[&>li:not(:nth-last-child(2))]:hidden"
28
28
  ),
@@ -46,21 +46,21 @@ function BreadcrumbItem({
46
46
  props: {
47
47
  "data-slot": "breadcrumb-link",
48
48
  className: cn(
49
- "focus-ring touch-target inline-flex items-center gap-1 rounded-xs text-label-small text-text-secondary transition-colors hover:text-text-primary",
49
+ "focus-ring touch-target inline-flex items-center gap-1 rounded-xs text-label-default text-text-secondary transition-colors hover:text-text-brand",
50
50
  className
51
51
  ),
52
52
  children,
53
53
  ...props
54
54
  }
55
55
  });
56
- return /* @__PURE__ */ jsxs("li", { "data-slot": "breadcrumb-item", className: "flex items-center gap-1.5", children: [
56
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "breadcrumb-item", className: "flex items-center gap-2", children: [
57
57
  /* @__PURE__ */ jsx(ChevronLeftIcon, { size: "sm", className: "text-text-secondary lg:hidden" }),
58
58
  link,
59
59
  /* @__PURE__ */ jsx(
60
60
  ChevronRightIcon,
61
61
  {
62
62
  size: "sm",
63
- className: "text-text-disabled max-lg:hidden"
63
+ className: "text-border-strong max-lg:hidden"
64
64
  }
65
65
  )
66
66
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { ChevronLeftIcon, ChevronRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\nexport interface BreadcrumbProps extends ComponentProps<\"nav\"> {\n /** `BreadcrumbItem`s for the levels above, then one `BreadcrumbPage` for the current one. */\n children: ReactNode;\n}\n\n/**\n * Breadcrumb for pages two or three levels deep (a programme inside \"Programas\n * especializados\"). A `<nav>` named \"Migas de pan\" (`breadcrumb` label) with an\n * `<ol>`: the levels above are links, the last one is plain text with\n * `aria-current=\"page\"`.\n *\n * ```tsx\n * <Breadcrumb>\n * <BreadcrumbItem render={<Link to=\"/programas\" />}>Programas especializados</BreadcrumbItem>\n * <BreadcrumbPage>Pérdida de peso</BreadcrumbPage>\n * </Breadcrumb>\n * ```\n *\n * The levels above are quiet (Label/Small in text-secondary, darker on hover), with\n * a chevron in text-disabled between them, so the trail never competes with the\n * page title under it. The links are not underlined: inside the breadcrumb landmark\n * and its list, their position already tells they are links (WCAG 1.4.1 applies to\n * links in running text).\n *\n * Below the `lg` breakpoint only the level right above shows, as a way back with an\n * arrow (\"‹ Programas especializados\").\n */\nexport function Breadcrumb({\n \"aria-label\": ariaLabel,\n className,\n children,\n ...props\n}: BreadcrumbProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n\n return (\n <nav\n data-slot=\"breadcrumb\"\n aria-label={ariaLabel ?? labels.breadcrumb}\n className={className}\n {...props}\n >\n <ol\n className={cn(\n \"flex flex-wrap items-center gap-1.5\",\n // Mobile: only the parent level (the second-to-last item) stays.\n \"max-lg:[&>li:not(:nth-last-child(2))]:hidden\",\n )}\n >\n {children}\n </ol>\n </nav>\n );\n}\n\nexport interface BreadcrumbItemProps extends useRender.ComponentProps<\"a\"> {\n /** The level's name, the same as the section or page it links to. */\n children: ReactNode;\n}\n\n/**\n * A level above the current page: a link (`href`, or the router's with `render`)\n * followed by a chevron. On mobile, the parent level gets a back arrow in front\n * instead.\n */\nexport function BreadcrumbItem({\n render,\n ref,\n className,\n children,\n ...props\n}: BreadcrumbItemProps): React.JSX.Element {\n const link = useRender({\n render,\n ref,\n defaultTagName: \"a\",\n props: {\n \"data-slot\": \"breadcrumb-link\",\n className: cn(\n \"focus-ring touch-target inline-flex items-center gap-1 rounded-xs text-label-small text-text-secondary transition-colors hover:text-text-primary\",\n className,\n ),\n children,\n ...props,\n },\n });\n\n return (\n <li data-slot=\"breadcrumb-item\" className=\"flex items-center gap-1.5\">\n <ChevronLeftIcon size=\"sm\" className=\"text-text-secondary lg:hidden\" />\n {link}\n <ChevronRightIcon\n size=\"sm\"\n className=\"text-text-disabled max-lg:hidden\"\n />\n </li>\n );\n}\n\nexport interface BreadcrumbPageProps extends ComponentProps<\"span\"> {}\n\n/** The current level: text, not a link, with `aria-current=\"page\"`. Hidden on mobile. */\nexport function BreadcrumbPage({\n className,\n ...props\n}: BreadcrumbPageProps): React.JSX.Element {\n return (\n <li data-slot=\"breadcrumb-page\" className=\"flex items-center\">\n <span\n aria-current=\"page\"\n className={cn(\"text-label-strong text-text-primary\", className)}\n {...props}\n />\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;;AAkCO,SAAS,WAAW;AAAA,EACzB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,QAAM,SAAS,kBAAA;AAEf,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY,aAAa,OAAO;AAAA,MAChC;AAAA,MACC,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA;AAAA,YAEA;AAAA,UAAA;AAAA,UAGD;AAAA,QAAA;AAAA,MAAA;AAAA,IACH;AAAA,EAAA;AAGN;AAYO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA2C;AACzC,QAAM,OAAO,UAAU;AAAA,IACrB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,OAAO;AAAA,MACL,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAEF;AAAA,MACA,GAAG;AAAA,IAAA;AAAA,EACL,CACD;AAED,SACE,qBAAC,MAAA,EAAG,aAAU,mBAAkB,WAAU,6BACxC,UAAA;AAAA,IAAA,oBAAC,iBAAA,EAAgB,MAAK,MAAK,WAAU,iCAAgC;AAAA,IACpE;AAAA,IACD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GACF;AAEJ;AAKO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,GAAG;AACL,GAA2C;AACzC,SACE,oBAAC,MAAA,EAAG,aAAU,mBAAkB,WAAU,qBACxC,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,gBAAa;AAAA,MACb,WAAW,GAAG,uCAAuC,SAAS;AAAA,MAC7D,GAAG;AAAA,IAAA;AAAA,EAAA,GAER;AAEJ;"}
1
+ {"version":3,"file":"breadcrumb.js","sources":["../../../src/components/breadcrumb/breadcrumb.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { ChevronLeftIcon, ChevronRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\n\nexport interface BreadcrumbProps extends ComponentProps<\"nav\"> {\n /** `BreadcrumbItem`s for the levels above, then one `BreadcrumbPage` for the current one. */\n children: ReactNode;\n}\n\n/**\n * Breadcrumb for pages two or three levels deep (a programme inside \"Programas\n * especializados\"). A `<nav>` named \"Migas de pan\" (`breadcrumb` label) with an\n * `<ol>`: the levels above are links, the last one is plain text with\n * `aria-current=\"page\"`.\n *\n * ```tsx\n * <Breadcrumb>\n * <BreadcrumbItem render={<Link to=\"/programas\" />}>Programas especializados</BreadcrumbItem>\n * <BreadcrumbPage>Pérdida de peso</BreadcrumbPage>\n * </Breadcrumb>\n * ```\n *\n * The levels above are quiet (Label/Default in text-secondary, brand on hover), with\n * a chevron in border-strong between them, so the trail never competes with the\n * page title under it. The links are not underlined: inside the breadcrumb landmark\n * and its list, their position already tells they are links (WCAG 1.4.1 applies to\n * links in running text).\n *\n * Below the `lg` breakpoint only the level right above shows, as a way back with an\n * arrow (\"‹ Programas especializados\").\n */\nexport function Breadcrumb({\n \"aria-label\": ariaLabel,\n className,\n children,\n ...props\n}: BreadcrumbProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n\n return (\n <nav\n data-slot=\"breadcrumb\"\n aria-label={ariaLabel ?? labels.breadcrumb}\n className={className}\n {...props}\n >\n <ol\n className={cn(\n \"flex flex-wrap items-center gap-2\",\n // Mobile: only the parent level (the second-to-last item) stays.\n \"max-lg:[&>li:not(:nth-last-child(2))]:hidden\",\n )}\n >\n {children}\n </ol>\n </nav>\n );\n}\n\nexport interface BreadcrumbItemProps extends useRender.ComponentProps<\"a\"> {\n /** The level's name, the same as the section or page it links to. */\n children: ReactNode;\n}\n\n/**\n * A level above the current page: a link (`href`, or the router's with `render`)\n * followed by a chevron. On mobile, the parent level gets a back arrow in front\n * instead.\n */\nexport function BreadcrumbItem({\n render,\n ref,\n className,\n children,\n ...props\n}: BreadcrumbItemProps): React.JSX.Element {\n const link = useRender({\n render,\n ref,\n defaultTagName: \"a\",\n props: {\n \"data-slot\": \"breadcrumb-link\",\n className: cn(\n \"focus-ring touch-target inline-flex items-center gap-1 rounded-xs text-label-default text-text-secondary transition-colors hover:text-text-brand\",\n className,\n ),\n children,\n ...props,\n },\n });\n\n return (\n <li data-slot=\"breadcrumb-item\" className=\"flex items-center gap-2\">\n <ChevronLeftIcon size=\"sm\" className=\"text-text-secondary lg:hidden\" />\n {link}\n <ChevronRightIcon\n size=\"sm\"\n className=\"text-border-strong max-lg:hidden\"\n />\n </li>\n );\n}\n\nexport interface BreadcrumbPageProps extends ComponentProps<\"span\"> {}\n\n/** The current level: text, not a link, with `aria-current=\"page\"`. Hidden on mobile. */\nexport function BreadcrumbPage({\n className,\n ...props\n}: BreadcrumbPageProps): React.JSX.Element {\n return (\n <li data-slot=\"breadcrumb-page\" className=\"flex items-center\">\n <span\n aria-current=\"page\"\n className={cn(\"text-label-strong text-text-primary\", className)}\n {...props}\n />\n </li>\n );\n}\n"],"names":[],"mappings":";;;;;;AAkCO,SAAS,WAAW;AAAA,EACzB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,QAAM,SAAS,kBAAA;AAEf,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY,aAAa,OAAO;AAAA,MAChC;AAAA,MACC,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA;AAAA,YAEA;AAAA,UAAA;AAAA,UAGD;AAAA,QAAA;AAAA,MAAA;AAAA,IACH;AAAA,EAAA;AAGN;AAYO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA2C;AACzC,QAAM,OAAO,UAAU;AAAA,IACrB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,OAAO;AAAA,MACL,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAEF;AAAA,MACA,GAAG;AAAA,IAAA;AAAA,EACL,CACD;AAED,SACE,qBAAC,MAAA,EAAG,aAAU,mBAAkB,WAAU,2BACxC,UAAA;AAAA,IAAA,oBAAC,iBAAA,EAAgB,MAAK,MAAK,WAAU,iCAAgC;AAAA,IACpE;AAAA,IACD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EACZ,GACF;AAEJ;AAKO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,GAAG;AACL,GAA2C;AACzC,SACE,oBAAC,MAAA,EAAG,aAAU,mBAAkB,WAAU,qBACxC,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,gBAAa;AAAA,MACb,WAAW,GAAG,uCAAuC,SAAS;AAAA,MAC7D,GAAG;AAAA,IAAA;AAAA,EAAA,GAER;AAEJ;"}
@@ -6,20 +6,20 @@ import { Spinner } from "../spinner/spinner.js";
6
6
  const buttonVariants = cva(
7
7
  [
8
8
  "inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap",
9
- "rounded-md border border-transparent text-label-strong transition-colors",
9
+ "rounded-md border border-transparent text-label-strong transition-[color,background-color,border-color,box-shadow]",
10
10
  "focus-ring touch-target",
11
11
  "[&_svg]:shrink-0 [&_svg]:size-icon-sm",
12
- "data-disabled:cursor-not-allowed aria-busy:cursor-progress"
12
+ "data-disabled:cursor-not-allowed data-disabled:shadow-none aria-busy:cursor-progress"
13
13
  ],
14
14
  {
15
15
  variants: {
16
16
  variant: {
17
- primary: "bg-brand text-text-inverse not-data-disabled:hover:bg-brand-strong",
18
- secondary: "border-text-brand bg-brand-subtle text-text-brand not-data-disabled:hover:bg-surface",
19
- outline: "border-border-input bg-background text-text-primary not-data-disabled:hover:bg-surface",
17
+ primary: "bg-brand text-text-inverse shadow-xs not-data-disabled:hover:bg-brand-strong",
18
+ secondary: "border-text-brand bg-brand-subtle text-text-brand not-data-disabled:hover:bg-background",
19
+ outline: "border-border bg-background text-text-primary shadow-xs not-data-disabled:hover:border-border-strong not-data-disabled:hover:bg-surface",
20
20
  ghost: "bg-transparent text-text-primary not-data-disabled:hover:bg-surface",
21
- destructive: "bg-error-strong text-text-inverse",
22
- inverse: "bg-background text-text-brand not-data-disabled:hover:bg-surface",
21
+ destructive: "bg-error-strong text-text-inverse shadow-xs",
22
+ inverse: "bg-background text-text-brand shadow-xs not-data-disabled:hover:bg-surface",
23
23
  "inverse-ghost": "bg-transparent text-text-inverse not-data-disabled:hover:bg-overlay-light"
24
24
  },
25
25
  size: {
@@ -37,12 +37,12 @@ const buttonVariants = cva(
37
37
  {
38
38
  variant: ["primary", "destructive"],
39
39
  disabled: true,
40
- className: "bg-surface text-text-disabled"
40
+ className: "bg-surface-raised text-text-disabled"
41
41
  },
42
42
  {
43
43
  variant: ["secondary", "outline"],
44
44
  disabled: true,
45
- className: "border-border bg-surface text-text-disabled"
45
+ className: "border-border-subtle bg-surface text-text-disabled"
46
46
  },
47
47
  {
48
48
  variant: "ghost",
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sources":["../../../src/components/button/button.tsx"],"sourcesContent":["import { Button as BaseButton } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Spinner } from \"../spinner\";\n\nexport const buttonVariants = cva(\n [\n \"inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap\",\n \"rounded-md border border-transparent text-label-strong transition-colors\",\n \"focus-ring touch-target\",\n \"[&_svg]:shrink-0 [&_svg]:size-icon-sm\",\n \"data-disabled:cursor-not-allowed aria-busy:cursor-progress\",\n ],\n {\n variants: {\n variant: {\n primary:\n \"bg-brand text-text-inverse not-data-disabled:hover:bg-brand-strong\",\n secondary:\n \"border-text-brand bg-brand-subtle text-text-brand not-data-disabled:hover:bg-surface\",\n outline:\n \"border-border-input bg-background text-text-primary not-data-disabled:hover:bg-surface\",\n ghost:\n \"bg-transparent text-text-primary not-data-disabled:hover:bg-surface\",\n destructive: \"bg-error-strong text-text-inverse\",\n inverse:\n \"bg-background text-text-brand not-data-disabled:hover:bg-surface\",\n \"inverse-ghost\":\n \"bg-transparent text-text-inverse not-data-disabled:hover:bg-overlay-light\",\n },\n size: {\n default: \"h-control-md px-4\",\n small: \"h-control-sm px-3\",\n },\n disabled: {\n true: \"\",\n false: \"\",\n },\n },\n // Disabled uses explicit colours, not opacity: an opacity can't be a token and it\n // also dims the border.\n compoundVariants: [\n {\n variant: [\"primary\", \"destructive\"],\n disabled: true,\n className: \"bg-surface text-text-disabled\",\n },\n {\n variant: [\"secondary\", \"outline\"],\n disabled: true,\n className: \"border-border bg-surface text-text-disabled\",\n },\n {\n variant: \"ghost\",\n disabled: true,\n className: \"text-text-disabled\",\n },\n {\n variant: \"inverse\",\n disabled: true,\n className: \"bg-border-inverse text-text-inverse-secondary\",\n },\n {\n variant: \"inverse-ghost\",\n disabled: true,\n className: \"text-text-inverse-secondary\",\n },\n ],\n defaultVariants: {\n variant: \"primary\",\n size: \"default\",\n disabled: false,\n },\n },\n);\n\ntype ButtonVariantProps = Omit<VariantProps<typeof buttonVariants>, \"disabled\">;\n\nexport interface ButtonProps\n extends Omit<BaseButton.Props, \"className\">,\n ButtonVariantProps {\n /** Keeps the variant colour, shows a spinner and ignores clicks without losing focus. */\n loading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n className?: string;\n}\n\n/**\n * Action button. A button ACTS; to navigate use `Link` (or `render={<a href />}` when\n * it has to look like a button).\n *\n * Hierarchy, from most to least weight: primary → secondary → outline → ghost.\n * `destructive` is a warning, not a level. `inverse` and `inverse-ghost` are only\n * for dark surfaces (`bg-brand-dark`). One primary action per block.\n *\n * The drawing is 40px (36 small) but the hit area is always 44px. Full width: pass\n * `className=\"w-full\"`.\n */\nexport function Button({\n variant,\n size,\n disabled = false,\n loading = false,\n leadingIcon,\n trailingIcon,\n className,\n children,\n type = \"button\",\n ...props\n}: ButtonProps): React.JSX.Element {\n return (\n <BaseButton\n data-slot=\"button\"\n type={type}\n disabled={disabled || loading}\n focusableWhenDisabled={loading}\n aria-busy={loading || undefined}\n className={cn(buttonVariants({ variant, size, disabled }), className)}\n {...props}\n >\n {loading ? (\n <Spinner size={16} label={null} className=\"text-current\" />\n ) : (\n leadingIcon\n )}\n {children}\n {!loading && trailingIcon}\n </BaseButton>\n );\n}\n"],"names":["BaseButton"],"mappings":";;;;;AAOO,MAAM,iBAAiB;AAAA,EAC5B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SACE;AAAA,QACF,WACE;AAAA,QACF,SACE;AAAA,QACF,OACE;AAAA,QACF,aAAa;AAAA,QACb,SACE;AAAA,QACF,iBACE;AAAA,MAAA;AAAA,MAEJ,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,MAET,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA;AAAA;AAAA,IAIF,kBAAkB;AAAA,MAChB;AAAA,QACE,SAAS,CAAC,WAAW,aAAa;AAAA,QAClC,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS,CAAC,aAAa,SAAS;AAAA,QAChC,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,IACb;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,EACZ;AAEJ;AAyBO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,GAAG;AACL,GAAmC;AACjC,SACE;AAAA,IAACA;AAAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,uBAAuB;AAAA,MACvB,aAAW,WAAW;AAAA,MACtB,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,SAAA,CAAU,GAAG,SAAS;AAAA,MACnE,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,UACC,oBAAC,WAAQ,MAAM,IAAI,OAAO,MAAM,WAAU,gBAAe,IAEzD;AAAA,QAED;AAAA,QACA,CAAC,WAAW;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGnB;"}
1
+ {"version":3,"file":"button.js","sources":["../../../src/components/button/button.tsx"],"sourcesContent":["import { Button as BaseButton } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Spinner } from \"../spinner\";\n\nexport const buttonVariants = cva(\n [\n \"inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap\",\n \"rounded-md border border-transparent text-label-strong transition-[color,background-color,border-color,box-shadow]\",\n \"focus-ring touch-target\",\n \"[&_svg]:shrink-0 [&_svg]:size-icon-sm\",\n \"data-disabled:cursor-not-allowed data-disabled:shadow-none aria-busy:cursor-progress\",\n ],\n {\n variants: {\n variant: {\n primary:\n \"bg-brand text-text-inverse shadow-xs not-data-disabled:hover:bg-brand-strong\",\n secondary:\n \"border-text-brand bg-brand-subtle text-text-brand not-data-disabled:hover:bg-background\",\n outline:\n \"border-border bg-background text-text-primary shadow-xs not-data-disabled:hover:border-border-strong not-data-disabled:hover:bg-surface\",\n ghost:\n \"bg-transparent text-text-primary not-data-disabled:hover:bg-surface\",\n destructive: \"bg-error-strong text-text-inverse shadow-xs\",\n inverse:\n \"bg-background text-text-brand shadow-xs not-data-disabled:hover:bg-surface\",\n \"inverse-ghost\":\n \"bg-transparent text-text-inverse not-data-disabled:hover:bg-overlay-light\",\n },\n size: {\n default: \"h-control-md px-4\",\n small: \"h-control-sm px-3\",\n },\n disabled: {\n true: \"\",\n false: \"\",\n },\n },\n // Disabled uses explicit colours, not opacity: an opacity can't be a token and it\n // also dims the border.\n compoundVariants: [\n {\n variant: [\"primary\", \"destructive\"],\n disabled: true,\n className: \"bg-surface-raised text-text-disabled\",\n },\n {\n variant: [\"secondary\", \"outline\"],\n disabled: true,\n className: \"border-border-subtle bg-surface text-text-disabled\",\n },\n {\n variant: \"ghost\",\n disabled: true,\n className: \"text-text-disabled\",\n },\n {\n variant: \"inverse\",\n disabled: true,\n className: \"bg-border-inverse text-text-inverse-secondary\",\n },\n {\n variant: \"inverse-ghost\",\n disabled: true,\n className: \"text-text-inverse-secondary\",\n },\n ],\n defaultVariants: {\n variant: \"primary\",\n size: \"default\",\n disabled: false,\n },\n },\n);\n\ntype ButtonVariantProps = Omit<VariantProps<typeof buttonVariants>, \"disabled\">;\n\nexport interface ButtonProps\n extends Omit<BaseButton.Props, \"className\">,\n ButtonVariantProps {\n /** Keeps the variant colour, shows a spinner and ignores clicks without losing focus. */\n loading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n className?: string;\n}\n\n/**\n * Action button. A button ACTS; to navigate use `Link` (or `render={<a href />}` when\n * it has to look like a button).\n *\n * Hierarchy, from most to least weight: primary → secondary → outline → ghost.\n * `destructive` is a warning, not a level. `inverse` and `inverse-ghost` are only\n * for dark surfaces (`bg-brand-dark`). One primary action per block.\n *\n * The drawing is 40px (36 small) but the hit area is always 44px. Full width: pass\n * `className=\"w-full\"`.\n */\nexport function Button({\n variant,\n size,\n disabled = false,\n loading = false,\n leadingIcon,\n trailingIcon,\n className,\n children,\n type = \"button\",\n ...props\n}: ButtonProps): React.JSX.Element {\n return (\n <BaseButton\n data-slot=\"button\"\n type={type}\n disabled={disabled || loading}\n focusableWhenDisabled={loading}\n aria-busy={loading || undefined}\n className={cn(buttonVariants({ variant, size, disabled }), className)}\n {...props}\n >\n {loading ? (\n <Spinner size={16} label={null} className=\"text-current\" />\n ) : (\n leadingIcon\n )}\n {children}\n {!loading && trailingIcon}\n </BaseButton>\n );\n}\n"],"names":["BaseButton"],"mappings":";;;;;AAOO,MAAM,iBAAiB;AAAA,EAC5B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SACE;AAAA,QACF,WACE;AAAA,QACF,SACE;AAAA,QACF,OACE;AAAA,QACF,aAAa;AAAA,QACb,SACE;AAAA,QACF,iBACE;AAAA,MAAA;AAAA,MAEJ,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,MAET,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MAAA;AAAA,IACT;AAAA;AAAA;AAAA,IAIF,kBAAkB;AAAA,MAChB;AAAA,QACE,SAAS,CAAC,WAAW,aAAa;AAAA,QAClC,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS,CAAC,aAAa,SAAS;AAAA,QAChC,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,MAEb;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,MAAA;AAAA,IACb;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,EACZ;AAEJ;AAyBO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,GAAG;AACL,GAAmC;AACjC,SACE;AAAA,IAACA;AAAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,uBAAuB;AAAA,MACvB,aAAW,WAAW;AAAA,MACtB,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,SAAA,CAAU,GAAG,SAAS;AAAA,MACnE,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,UACC,oBAAC,WAAQ,MAAM,IAAI,OAAO,MAAM,WAAU,gBAAe,IAEzD;AAAA,QAED;AAAA,QACA,CAAC,WAAW;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGnB;"}
@@ -14,6 +14,8 @@ interface ChipOwnProps {
14
14
  size?: ChipSize;
15
15
  /** A MediCore icon before the label. While selected the check takes its place. */
16
16
  leadingIcon?: ReactNode;
17
+ /** Counter after the label ("Abiertas 5"): how many items the filter would show. */
18
+ count?: ReactNode;
17
19
  className?: string;
18
20
  }
19
21
  export interface ChipToggleProps extends Omit<Toggle.Props<string>, "className">, ChipOwnProps {
@@ -40,6 +42,7 @@ export type ChipProps = ChipToggleProps | ChipRemovableProps;
40
42
  * - It is a `<button aria-pressed>` (Base UI Toggle): `pressed` / `defaultPressed` /
41
43
  * `onPressedChange`, or `value` inside a `ChipGroup`.
42
44
  * - With `onRemove` it is a remove button for an applied filter instead.
45
+ * - `count` adds a counter pill at the end, brand-coloured while selected.
43
46
  */
44
47
  export declare function Chip(props: ChipProps): React.JSX.Element;
45
48
  export interface ChipGroupProps<Value extends string = string> extends Omit<ToggleGroup.Props<Value>, "className"> {
@@ -1,4 +1,4 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { Button } from "@base-ui/react/button";
3
3
  import { Toggle } from "@base-ui/react/toggle";
4
4
  import { ToggleGroup } from "@base-ui/react/toggle-group";
@@ -10,15 +10,14 @@ 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
- // border-input, not border: a chip is a control and its edge needs 3:1 (1.4.11).
14
- "rounded-full border border-border-input bg-surface text-text-primary transition-colors",
13
+ "rounded-full border border-border bg-background text-text-primary shadow-xs transition-[color,background-color,border-color,box-shadow]",
15
14
  // 32 (24 small) drawn, 44 hit area.
16
15
  "focus-ring touch-target",
17
16
  "[&_svg]:shrink-0",
18
- "not-data-disabled:not-data-pressed:hover:bg-surface-raised",
19
- // Selected is not just a colour: check in front, 1.5px border, Medium label.
20
- "not-data-disabled:data-pressed:border-[1.5px] not-data-disabled:data-pressed:border-text-brand not-data-disabled:data-pressed:bg-brand-subtle not-data-disabled:data-pressed:text-text-brand",
21
- "data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:text-text-disabled"
17
+ "not-data-disabled:not-data-pressed:hover:border-border-strong not-data-disabled:not-data-pressed:hover:bg-surface",
18
+ // Selected is not just a colour: check in front, brand border, Medium label.
19
+ "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",
20
+ "data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none"
22
21
  ],
23
22
  {
24
23
  variants: {
@@ -32,10 +31,40 @@ const chipVariants = cva(
32
31
  }
33
32
  }
34
33
  );
34
+ const chipCountVariants = cva(
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",
37
+ "group-data-pressed/chip:bg-brand group-data-pressed/chip:text-text-inverse",
38
+ "group-data-disabled/chip:bg-transparent group-data-disabled/chip:text-text-disabled"
39
+ ],
40
+ {
41
+ variants: {
42
+ size: {
43
+ default: "-me-1 h-5 min-w-5 px-1.5",
44
+ small: "-me-0.5 h-4 min-w-4 px-1"
45
+ }
46
+ },
47
+ defaultVariants: {
48
+ size: "default"
49
+ }
50
+ }
51
+ );
52
+ function ChipCount({
53
+ size,
54
+ count
55
+ }) {
56
+ if (count === void 0 || count === null) {
57
+ return null;
58
+ }
59
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ " ",
61
+ /* @__PURE__ */ jsx("span", { "data-slot": "chip-count", className: chipCountVariants({ size }), children: count })
62
+ ] });
63
+ }
35
64
  function Chip(props) {
36
65
  const labels = useMediCoreLabels();
37
66
  if (props.onRemove) {
38
- const { size: size2, leadingIcon: leadingIcon2, className: className2, children: children2, onRemove: onRemove2, ...rest2 } = props;
67
+ const { size: size2, leadingIcon: leadingIcon2, count: count2, className: className2, children: children2, onRemove: onRemove2, ...rest2 } = props;
39
68
  return /* @__PURE__ */ jsxs(
40
69
  Button,
41
70
  {
@@ -50,12 +79,13 @@ function Chip(props) {
50
79
  " "
51
80
  ] }),
52
81
  children2,
82
+ /* @__PURE__ */ jsx(ChipCount, { size: size2, count: count2 }),
53
83
  /* @__PURE__ */ jsx(XCloseIcon, {})
54
84
  ]
55
85
  }
56
86
  );
57
87
  }
58
- const { size, leadingIcon, className, children, onRemove, ...rest } = props;
88
+ const { size, leadingIcon, count, className, children, onRemove, ...rest } = props;
59
89
  return /* @__PURE__ */ jsxs(
60
90
  Toggle,
61
91
  {
@@ -65,7 +95,8 @@ function Chip(props) {
65
95
  children: [
66
96
  /* @__PURE__ */ jsx(CheckIcon, { className: "hidden group-data-pressed/chip:block" }),
67
97
  leadingIcon && /* @__PURE__ */ jsx("span", { className: "contents group-data-pressed/chip:hidden", children: leadingIcon }),
68
- children
98
+ children,
99
+ /* @__PURE__ */ jsx(ChipCount, { size, count })
69
100
  ]
70
101
  }
71
102
  );
@@ -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 // border-input, not border: a chip is a control and its edge needs 3:1 (1.4.11).\n \"rounded-full border border-border-input bg-surface text-text-primary transition-colors\",\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:bg-surface-raised\",\n // Selected is not just a colour: check in front, 1.5px border, Medium label.\n \"not-data-disabled:data-pressed:border-[1.5px] not-data-disabled:data-pressed:border-text-brand not-data-disabled:data-pressed:bg-brand-subtle not-data-disabled:data-pressed:text-text-brand\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:text-text-disabled\",\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\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 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\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 */\nexport function Chip(props: ChipProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n\n if (props.onRemove) {\n const { size, leadingIcon, className, children, onRemove, ...rest } = 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 <XCloseIcon />\n </BaseButton>\n );\n }\n\n const { size, leadingIcon, className, children, onRemove, ...rest } = 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 </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","className","children","onRemove","rest","BaseButton"],"mappings":";;;;;;;;;AAUO,MAAM,eAAe;AAAA,EAC1B;AAAA,IACE;AAAA;AAAA,IAEA;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;AA+CO,SAAS,KAAK,OAAqC;AACxD,QAAM,SAAS,kBAAA;AAEf,MAAI,MAAM,UAAU;AAClB,UAAM,EAAE,MAAAA,OAAM,aAAAC,cAAa,WAAAC,YAAW,UAAAC,WAAU,UAAAC,WAAU,GAAGC,UAAS;AAEtE,WACE;AAAA,MAACC;AAAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAW,GAAG,aAAa,EAAE,MAAAN,MAAAA,CAAM,GAAGE,UAAS;AAAA,QAC/C,SAASE;AAAAA,QACR,GAAGC;AAAAA,QAEH,UAAA;AAAA,UAAAJ;AAAAA,UACD,qBAAC,QAAA,EAAK,WAAU,WAAW,UAAA;AAAA,YAAA,OAAO;AAAA,YAAO;AAAA,UAAA,GAAC;AAAA,UACzCE;AAAAA,8BACA,YAAA,CAAA,CAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlB;AAEA,QAAM,EAAE,MAAM,aAAa,WAAW,UAAU,UAAU,GAAG,SAAS;AAEtE,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,MAAA;AAAA,IAAA;AAAA,EAAA;AAGP;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 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;"}
@@ -16,7 +16,7 @@ export interface EmptyStateProps extends Omit<ComponentProps<"div">, "title"> {
16
16
  * no "sube tus informes" if documents can't be uploaded.
17
17
  */
18
18
  description?: ReactNode;
19
- /** A MediCore icon (24, `text-brand`), decorative. `calendar` for appointments. */
19
+ /** A MediCore icon, decorative, inside a brand `IconContainer`. `calendar` for appointments. */
20
20
  icon?: ReactNode;
21
21
  /**
22
22
  * The next step, a `Button` with the closed naming ("Pedir cita", never "Nueva
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { cva } from "class-variance-authority";
3
3
  import { cn } from "../../lib/cn.js";
4
+ import { IconContainer } from "../icon-container/icon-container.js";
4
5
  const emptyStateVariants = cva(
5
6
  "flex w-full flex-col items-center text-center",
6
7
  {
@@ -33,7 +34,15 @@ function EmptyState({
33
34
  className: cn(emptyStateVariants({ size }), className),
34
35
  ...props,
35
36
  children: [
36
- icon && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex text-text-brand [&_svg]:size-icon-lg", children: icon }),
37
+ icon && /* @__PURE__ */ jsx(
38
+ IconContainer,
39
+ {
40
+ "aria-hidden": true,
41
+ icon,
42
+ tone: "brand",
43
+ size: size === "compact" ? 40 : 48
44
+ }
45
+ ),
37
46
  /* @__PURE__ */ jsx(
38
47
  Title,
39
48
  {
@@ -1 +1 @@
1
- {"version":3,"file":"empty-state.js","sources":["../../../src/components/empty-state/empty-state.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport const emptyStateVariants = cva(\n \"flex w-full flex-col items-center text-center\",\n {\n variants: {\n size: {\n default: \"gap-4 p-12\",\n compact: \"gap-2 p-6\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nexport type EmptyStateSize = \"default\" | \"compact\";\n\nexport type EmptyStateHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\nexport interface EmptyStateProps extends Omit<ComponentProps<\"div\">, \"title\"> {\n /**\n * Describes, never blames: \"Todavía no tienes citas\", not \"No has pedido ninguna\n * cita\".\n */\n title: ReactNode;\n /**\n * What will happen and when this fills up (\"Cuando pidas una cita aparecerá aquí\n * con la hora y el profesional que te atiende\"). Never promise what doesn't exist:\n * no \"sube tus informes\" if documents can't be uploaded.\n */\n description?: ReactNode;\n /** A MediCore icon (24, `text-brand`), decorative. `calendar` for appointments. */\n icon?: ReactNode;\n /**\n * The next step, a `Button` with the closed naming (\"Pedir cita\", never \"Nueva\n * cita\"). Leave it out only when there really is none (a history that fills up\n * after the first consultation) and let the description say when it will appear.\n */\n action?: ReactNode;\n /**\n * `default` for a whole page. `compact` inside a card or a column: less air and a\n * Body/Default Strong title, because an h4 look inside a card competes with the\n * card's own title.\n */\n size?: EmptyStateSize;\n /** Renders the title as h2–h6. Without it the title is a paragraph. */\n headingLevel?: EmptyStateHeadingLevel;\n}\n\n/**\n * Empty state. It is NOT a sign saying \"there's nothing\": it's where the product\n * explains what will happen and offers the next step — empty space turned into\n * real use or education.\n *\n * Three things it can't do:\n * - Promise what doesn't exist.\n * - Blame the person (\"No has pedido…\" accuses; \"Todavía no tienes…\" describes).\n * - Leave no way out: if there is a sensible action, it goes here.\n *\n * An empty state is NOT an error state. If the list is empty because the request\n * failed, show an error with a retry that says something failed — mixing them makes\n * people think they lost their data.\n *\n * Its text is centred, the only time this system centres text: a short, isolated\n * block with nothing around to align with.\n */\nexport function EmptyState({\n title,\n description,\n icon,\n action,\n size = \"default\",\n headingLevel,\n className,\n ...props\n}: EmptyStateProps): React.JSX.Element {\n const Title = headingLevel ? (`h${headingLevel}` as const) : \"p\";\n\n return (\n <div\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants({ size }), className)}\n {...props}\n >\n {icon && (\n <span aria-hidden className=\"flex text-text-brand [&_svg]:size-icon-lg\">\n {icon}\n </span>\n )}\n <Title\n className={cn(\n \"max-w-sm text-text-primary\",\n size === \"compact\" ? \"text-body-default-strong\" : \"text-heading-h4\",\n )}\n >\n {title}\n </Title>\n {description && (\n <p className=\"max-w-sm text-body-small text-text-secondary\">\n {description}\n </p>\n )}\n {action && <div className=\"flex justify-center\">{action}</div>}\n </div>\n );\n}\n"],"names":[],"mappings":";;;AAKO,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IACX;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAqDO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,QAAM,QAAQ,eAAgB,IAAI,YAAY,KAAe;AAE7D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,mBAAmB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MACpD,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,4BACE,QAAA,EAAK,eAAW,MAAC,WAAU,6CACzB,UAAA,MACH;AAAA,QAEF;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,YAAY,6BAA6B;AAAA,YAAA;AAAA,YAGnD,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,eACC,oBAAC,KAAA,EAAE,WAAU,gDACV,UAAA,aACH;AAAA,QAED,UAAU,oBAAC,OAAA,EAAI,WAAU,uBAAuB,UAAA,OAAA,CAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG9D;"}
1
+ {"version":3,"file":"empty-state.js","sources":["../../../src/components/empty-state/empty-state.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { IconContainer } from \"../icon-container\";\n\nexport const emptyStateVariants = cva(\n \"flex w-full flex-col items-center text-center\",\n {\n variants: {\n size: {\n default: \"gap-4 p-12\",\n compact: \"gap-2 p-6\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n },\n);\n\nexport type EmptyStateSize = \"default\" | \"compact\";\n\nexport type EmptyStateHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\nexport interface EmptyStateProps extends Omit<ComponentProps<\"div\">, \"title\"> {\n /**\n * Describes, never blames: \"Todavía no tienes citas\", not \"No has pedido ninguna\n * cita\".\n */\n title: ReactNode;\n /**\n * What will happen and when this fills up (\"Cuando pidas una cita aparecerá aquí\n * con la hora y el profesional que te atiende\"). Never promise what doesn't exist:\n * no \"sube tus informes\" if documents can't be uploaded.\n */\n description?: ReactNode;\n /** A MediCore icon, decorative, inside a brand `IconContainer`. `calendar` for appointments. */\n icon?: ReactNode;\n /**\n * The next step, a `Button` with the closed naming (\"Pedir cita\", never \"Nueva\n * cita\"). Leave it out only when there really is none (a history that fills up\n * after the first consultation) and let the description say when it will appear.\n */\n action?: ReactNode;\n /**\n * `default` for a whole page. `compact` inside a card or a column: less air and a\n * Body/Default Strong title, because an h4 look inside a card competes with the\n * card's own title.\n */\n size?: EmptyStateSize;\n /** Renders the title as h2–h6. Without it the title is a paragraph. */\n headingLevel?: EmptyStateHeadingLevel;\n}\n\n/**\n * Empty state. It is NOT a sign saying \"there's nothing\": it's where the product\n * explains what will happen and offers the next step — empty space turned into\n * real use or education.\n *\n * Three things it can't do:\n * - Promise what doesn't exist.\n * - Blame the person (\"No has pedido…\" accuses; \"Todavía no tienes…\" describes).\n * - Leave no way out: if there is a sensible action, it goes here.\n *\n * An empty state is NOT an error state. If the list is empty because the request\n * failed, show an error with a retry that says something failed — mixing them makes\n * people think they lost their data.\n *\n * Its text is centred, the only time this system centres text: a short, isolated\n * block with nothing around to align with.\n */\nexport function EmptyState({\n title,\n description,\n icon,\n action,\n size = \"default\",\n headingLevel,\n className,\n ...props\n}: EmptyStateProps): React.JSX.Element {\n const Title = headingLevel ? (`h${headingLevel}` as const) : \"p\";\n\n return (\n <div\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants({ size }), className)}\n {...props}\n >\n {icon && (\n <IconContainer\n aria-hidden\n icon={icon}\n tone=\"brand\"\n size={size === \"compact\" ? 40 : 48}\n />\n )}\n <Title\n className={cn(\n \"max-w-sm text-text-primary\",\n size === \"compact\" ? \"text-body-default-strong\" : \"text-heading-h4\",\n )}\n >\n {title}\n </Title>\n {description && (\n <p className=\"max-w-sm text-body-small text-text-secondary\">\n {description}\n </p>\n )}\n {action && <div className=\"flex justify-center\">{action}</div>}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAMO,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IACX;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAqDO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,QAAM,QAAQ,eAAgB,IAAI,YAAY,KAAe;AAE7D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,mBAAmB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MACpD,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAW;AAAA,YACX;AAAA,YACA,MAAK;AAAA,YACL,MAAM,SAAS,YAAY,KAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAGpC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,YAAY,6BAA6B;AAAA,YAAA;AAAA,YAGnD,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,eACC,oBAAC,KAAA,EAAE,WAAU,gDACV,UAAA,aACH;AAAA,QAED,UAAU,oBAAC,OAAA,EAAI,WAAU,uBAAuB,UAAA,OAAA,CAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG9D;"}
@@ -11,9 +11,9 @@ const factCardVariants = cva(
11
11
  variants: {
12
12
  tone: {
13
13
  neutral: "border-border-subtle bg-background",
14
- success: "border-success bg-success-subtle",
15
- warning: "border-warning bg-warning-subtle",
16
- error: "border-error bg-error-subtle"
14
+ success: "border-transparent bg-success-subtle",
15
+ warning: "border-transparent bg-warning-subtle",
16
+ error: "border-transparent bg-error-subtle"
17
17
  }
18
18
  },
19
19
  defaultVariants: {
@@ -1 +1 @@
1
- {"version":3,"file":"fact-card.js","sources":["../../../src/components/fact-card/fact-card.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport {\n AlertCircleIcon,\n AlertTriangleIcon,\n CheckCircleIcon,\n InfoCircleIcon,\n} from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\n\nexport const factCardVariants = cva(\n \"flex min-w-0 items-start gap-3 rounded-xl border p-4\",\n {\n variants: {\n tone: {\n neutral: \"border-border-subtle bg-background\",\n success: \"border-success bg-success-subtle\",\n warning: \"border-warning bg-warning-subtle\",\n error: \"border-error bg-error-subtle\",\n },\n },\n defaultVariants: {\n tone: \"neutral\",\n },\n },\n);\n\nconst iconClassName: Record<FactCardTone, string> = {\n neutral: \"text-text-secondary\",\n success: \"text-text-success\",\n warning: \"text-text-warning\",\n error: \"text-text-error\",\n};\n\nconst defaultIcon: Record<FactCardTone, ReactNode> = {\n neutral: <InfoCircleIcon />,\n success: <CheckCircleIcon />,\n warning: <AlertTriangleIcon />,\n error: <AlertCircleIcon />,\n};\n\nexport type FactCardTone = \"neutral\" | \"success\" | \"warning\" | \"error\";\n\nexport interface FactCardProps\n extends Omit<ComponentProps<\"div\">, \"title\">,\n VariantProps<typeof factCardVariants> {\n /**\n * The fact, in a few words: \"Datos de facturación completos\", \"Falta el NIF\".\n * On `error`, say what it blocks: \"Falta el NIF: no se puede cerrar el mes\".\n */\n title: ReactNode;\n /** The detail: \"Laura Gómez Ruiz · NIF 12345678Z · IBAN ···· 1332\". */\n description?: ReactNode;\n /** One way to act on it, a `Link` or a small `Button`. */\n action?: ReactNode;\n /** Replaces the tone's icon. */\n icon?: ReactNode;\n /**\n * `success` when the fact is settled, `warning` when it limits something,\n * `error` when it blocks the main action, `neutral` for plain information.\n */\n tone?: FactCardTone;\n}\n\n/**\n * A fact about the record that decides what can be done with it, next to its figures\n * in a `DetailHeader`: the invoicing data before a close, a missing consent, a pending\n * document. It stays as long as the fact does.\n *\n * Unlike `Banner`, it is a card among cards, not a notice across the page, and it is\n * never dismissible. Text in text-primary on every tone; the icon carries the tone,\n * so the meaning is in the words. `error` is announced with `role=\"status\"`.\n */\nexport function FactCard({\n title,\n description,\n action,\n icon,\n tone = \"neutral\",\n className,\n ...props\n}: FactCardProps): React.JSX.Element {\n return (\n <div\n data-slot=\"fact-card\"\n role={tone === \"error\" ? \"status\" : undefined}\n className={cn(factCardVariants({ tone }), className)}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n \"mt-0.5 flex shrink-0 [&_svg]:size-icon-md\",\n iconClassName[tone],\n )}\n >\n {icon ?? defaultIcon[tone]}\n </span>\n <div className=\"flex min-w-0 flex-col gap-1\">\n <p className=\"text-label-strong text-text-primary\">{title}</p>\n {description && (\n <p\n className={cn(\n \"text-body-small\",\n tone === \"neutral\" ? \"text-text-secondary\" : \"text-text-primary\",\n )}\n >\n {description}\n </p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAWO,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAEA,MAAM,gBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AAEA,MAAM,cAA+C;AAAA,EACnD,6BAAU,gBAAA,EAAe;AAAA,EACzB,6BAAU,iBAAA,EAAgB;AAAA,EAC1B,6BAAU,mBAAA,EAAkB;AAAA,EAC5B,2BAAQ,iBAAA,CAAA,CAAgB;AAC1B;AAkCO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAqC;AACnC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAM,SAAS,UAAU,WAAW;AAAA,MACpC,WAAW,GAAG,iBAAiB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MAClD,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,YAAA;AAAA,YAGnB,UAAA,QAAQ,YAAY,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,QAE3B,qBAAC,OAAA,EAAI,WAAU,+BACb,UAAA;AAAA,UAAA,oBAAC,KAAA,EAAE,WAAU,uCAAuC,UAAA,OAAM;AAAA,UACzD,eACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,SAAS,YAAY,wBAAwB;AAAA,cAAA;AAAA,cAG9C,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,UAAU,oBAAC,OAAA,EAAI,WAAU,QAAQ,UAAA,OAAA,CAAO;AAAA,QAAA,EAAA,CAC3C;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"fact-card.js","sources":["../../../src/components/fact-card/fact-card.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport {\n AlertCircleIcon,\n AlertTriangleIcon,\n CheckCircleIcon,\n InfoCircleIcon,\n} from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\n\nexport const factCardVariants = cva(\n \"flex min-w-0 items-start gap-3 rounded-xl border p-4\",\n {\n variants: {\n tone: {\n neutral: \"border-border-subtle bg-background\",\n success: \"border-transparent bg-success-subtle\",\n warning: \"border-transparent bg-warning-subtle\",\n error: \"border-transparent bg-error-subtle\",\n },\n },\n defaultVariants: {\n tone: \"neutral\",\n },\n },\n);\n\nconst iconClassName: Record<FactCardTone, string> = {\n neutral: \"text-text-secondary\",\n success: \"text-text-success\",\n warning: \"text-text-warning\",\n error: \"text-text-error\",\n};\n\nconst defaultIcon: Record<FactCardTone, ReactNode> = {\n neutral: <InfoCircleIcon />,\n success: <CheckCircleIcon />,\n warning: <AlertTriangleIcon />,\n error: <AlertCircleIcon />,\n};\n\nexport type FactCardTone = \"neutral\" | \"success\" | \"warning\" | \"error\";\n\nexport interface FactCardProps\n extends Omit<ComponentProps<\"div\">, \"title\">,\n VariantProps<typeof factCardVariants> {\n /**\n * The fact, in a few words: \"Datos de facturación completos\", \"Falta el NIF\".\n * On `error`, say what it blocks: \"Falta el NIF: no se puede cerrar el mes\".\n */\n title: ReactNode;\n /** The detail: \"Laura Gómez Ruiz · NIF 12345678Z · IBAN ···· 1332\". */\n description?: ReactNode;\n /** One way to act on it, a `Link` or a small `Button`. */\n action?: ReactNode;\n /** Replaces the tone's icon. */\n icon?: ReactNode;\n /**\n * `success` when the fact is settled, `warning` when it limits something,\n * `error` when it blocks the main action, `neutral` for plain information.\n */\n tone?: FactCardTone;\n}\n\n/**\n * A fact about the record that decides what can be done with it, next to its figures\n * in a `DetailHeader`: the invoicing data before a close, a missing consent, a pending\n * document. It stays as long as the fact does.\n *\n * Unlike `Banner`, it is a card among cards, not a notice across the page, and it is\n * never dismissible. Text in text-primary on every tone; the icon carries the tone,\n * so the meaning is in the words. `error` is announced with `role=\"status\"`.\n */\nexport function FactCard({\n title,\n description,\n action,\n icon,\n tone = \"neutral\",\n className,\n ...props\n}: FactCardProps): React.JSX.Element {\n return (\n <div\n data-slot=\"fact-card\"\n role={tone === \"error\" ? \"status\" : undefined}\n className={cn(factCardVariants({ tone }), className)}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n \"mt-0.5 flex shrink-0 [&_svg]:size-icon-md\",\n iconClassName[tone],\n )}\n >\n {icon ?? defaultIcon[tone]}\n </span>\n <div className=\"flex min-w-0 flex-col gap-1\">\n <p className=\"text-label-strong text-text-primary\">{title}</p>\n {description && (\n <p\n className={cn(\n \"text-body-small\",\n tone === \"neutral\" ? \"text-text-secondary\" : \"text-text-primary\",\n )}\n >\n {description}\n </p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAWO,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAEA,MAAM,gBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AAEA,MAAM,cAA+C;AAAA,EACnD,6BAAU,gBAAA,EAAe;AAAA,EACzB,6BAAU,iBAAA,EAAgB;AAAA,EAC1B,6BAAU,mBAAA,EAAkB;AAAA,EAC5B,2BAAQ,iBAAA,CAAA,CAAgB;AAC1B;AAkCO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAqC;AACnC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAM,SAAS,UAAU,WAAW;AAAA,MACpC,WAAW,GAAG,iBAAiB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MAClD,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA,cACA,cAAc,IAAI;AAAA,YAAA;AAAA,YAGnB,UAAA,QAAQ,YAAY,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,QAE3B,qBAAC,OAAA,EAAI,WAAU,+BACb,UAAA;AAAA,UAAA,oBAAC,KAAA,EAAE,WAAU,uCAAuC,UAAA,OAAM;AAAA,UACzD,eACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,SAAS,YAAY,wBAAwB;AAAA,cAAA;AAAA,cAG9C,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,UAAU,oBAAC,OAAA,EAAI,WAAU,QAAQ,UAAA,OAAA,CAAO;AAAA,QAAA,EAAA,CAC3C;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -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-input bg-background text-text-primary 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 transition-[border-color,box-shadow]";
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. */
@@ -1,4 +1,4 @@
1
- const controlBox = "w-full min-w-0 rounded-md border border-border-input bg-background text-text-primary 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 transition-[border-color,box-shadow]";
2
2
  const controlRow = "flex items-center gap-2 px-3 text-label-default";
3
3
  const controlHeight = {
4
4
  default: "h-control-md",
@@ -7,17 +7,17 @@ const controlHeight = {
7
7
  const wrappedControlStates = [
8
8
  "has-[[aria-invalid=true]]:border-error has-[[aria-invalid=true]]:ring-error",
9
9
  "has-[:focus:not(button)]:border-border-focus has-[:focus:not(button)]:ring-border-focus",
10
- "has-[:is(input,select):disabled]:cursor-not-allowed has-[:is(input,select):disabled]:border-border has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled"
10
+ "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"
11
11
  ].join(" ");
12
12
  const selfControlStates = [
13
13
  "outline-none focus:border-border-focus focus:ring-border-focus",
14
14
  "aria-invalid:not-focus:border-error aria-invalid:not-focus:ring-error",
15
- "disabled:cursor-not-allowed disabled:border-border disabled:bg-surface disabled:text-text-disabled"
15
+ "disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface disabled:text-text-disabled disabled:shadow-none"
16
16
  ].join(" ");
17
17
  const triggerControlStates = [
18
18
  "outline-none focus:border-border-focus focus:ring-border-focus data-popup-open:border-border-focus data-popup-open:ring-border-focus",
19
19
  "aria-invalid:not-focus:not-data-popup-open:border-error aria-invalid:not-focus:not-data-popup-open:ring-error",
20
- "data-disabled:cursor-not-allowed data-disabled:border-border data-disabled:bg-surface data-disabled:text-text-disabled"
20
+ "data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none"
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";
@@ -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-input bg-background text-text-primary 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 has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled\",\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 disabled:bg-surface disabled:text-text-disabled\",\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 data-disabled:bg-surface data-disabled:text-text-disabled\",\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 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;"}
@@ -19,7 +19,7 @@ const linkVariants = cva(
19
19
  },
20
20
  disabled: {
21
21
  true: "cursor-not-allowed text-text-disabled",
22
- false: "cursor-pointer text-text-link hover:decoration-2"
22
+ false: "cursor-pointer text-text-brand decoration-border hover:decoration-current"
23
23
  },
24
24
  withIcon: {
25
25
  true: "inline-flex items-center gap-1",
@@ -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: \"cursor-pointer text-text-link hover:decoration-2\",\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,OAAO;AAAA,MAAA;AAAA,MAET,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\";\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;"}
@@ -55,7 +55,7 @@ export interface MenuContentProps {
55
55
  /** Sheet only: accessible name of the sheet. Defaults to the `moreOptions` label. */
56
56
  label?: string;
57
57
  }
58
- /** The list of options. 240px popover with `shadow-md` and a border, or a bottom sheet. */
58
+ /** The list of options. 240px popover with `shadow-lg` and a border, or a bottom sheet. */
59
59
  export declare function MenuContent({ children, className, side, align, label, }: MenuContentProps): React.JSX.Element;
60
60
  export interface MenuItemProps {
61
61
  /** The option's label. */
@@ -117,7 +117,7 @@ function MenuContent({
117
117
  className: cn(
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
- "flex w-60 flex-col rounded-lg border border-border-subtle bg-background p-1 shadow-md outline-hidden",
120
+ "flex w-60 flex-col rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-hidden",
121
121
  "origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-out",
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",
@@ -131,7 +131,7 @@ function MenuContent({
131
131
  }
132
132
  const menuItemVariants = cva(
133
133
  [
134
- "flex w-full shrink-0 cursor-pointer select-none items-center gap-3 rounded-md px-3 text-left text-label-default",
134
+ "flex w-full shrink-0 cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-left text-label-default",
135
135
  "outline-hidden focus-visible:outline-2 focus-visible:outline-border-focus focus-visible:outline-solid focus-visible:-outline-offset-2",
136
136
  "[&_svg]:size-icon-md [&_svg]:shrink-0",
137
137
  "data-disabled:cursor-not-allowed data-disabled:text-text-disabled disabled:cursor-not-allowed disabled:text-text-disabled"
@@ -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-md` 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-lg border border-border-subtle bg-background p-1 shadow-md 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-md 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\",\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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"}
@@ -93,7 +93,7 @@ function ModalContent({
93
93
  initialFocus: initialFocus ?? popupRef,
94
94
  className: cn(
95
95
  "flex max-h-[90dvh] w-full flex-col gap-4 overflow-y-auto overscroll-contain",
96
- "rounded-t-xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden",
96
+ "rounded-t-2xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden",
97
97
  "[transform:translateY(var(--drawer-swipe-movement-y,0px))] transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]",
98
98
  "data-starting-style:[transform:translateY(100%)] data-ending-style:[transform:translateY(100%)]",
99
99
  "data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] data-swiping:select-none",
@@ -134,7 +134,7 @@ function ModalContent({
134
134
  initialFocus: initialFocus ?? popupRef,
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
- "rounded-xl bg-background p-6 text-text-primary shadow-xl outline-hidden",
137
+ "rounded-2xl bg-background p-6 text-text-primary shadow-xl outline-hidden",
138
138
  "transition-[opacity,scale] duration-200 ease-out",
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",
@@ -1 +1 @@
1
- {"version":3,"file":"modal.js","sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["import { Dialog } from \"@base-ui/react/dialog\";\nimport { Drawer } from \"@base-ui/react/drawer\";\nimport {\n type ComponentProps,\n type CSSProperties,\n createContext,\n type ReactNode,\n type Ref,\n type RefObject,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { IconButton } from \"../icon-button\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"./use-media-query\";\n\nexport type ModalLayout = \"center\" | \"sheet\";\n\nexport type ModalChangeEventDetails =\n | Dialog.Root.ChangeEventDetails\n | Drawer.Root.ChangeEventDetails;\n\nexport interface ModalActions {\n /** Closes the modal as if the person had dismissed it. */\n close: () => void;\n /** Unmounts the popup after an externally controlled closing animation. */\n unmount: () => void;\n}\n\ninterface ModalContextValue {\n layout: ModalLayout;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\nfunction useModalContext(part: string): ModalContextValue {\n const context = useContext(ModalContext);\n if (context === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Modal>.`);\n }\n return context;\n}\n\nexport interface ModalProps {\n /**\n * `center` is the 480px dialog, `sheet` the bottom sheet. Leave it unset and it\n * follows the screen: sheet below `md`, centered dialog from `md` up.\n */\n layout?: ModalLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: ModalChangeEventDetails) => void;\n /** Called after the open/close animation finishes. */\n onOpenChangeComplete?: (open: boolean) => void;\n /** Keeps the modal open when the person clicks or taps outside it. */\n disablePointerDismissal?: boolean;\n /** Imperative `close()` / `unmount()`. */\n actionsRef?: RefObject<ModalActions | null>;\n children?: ReactNode;\n}\n\n/**\n * Confirmation and interruption dialog. One component, two layouts with the same\n * content: a centered dialog on desktop and a bottom sheet on mobile.\n *\n * - `center`: close X in the header, actions in a row aligned right.\n * - `sheet`: drag handle and no X (it closes by swiping down or tapping outside),\n * actions stacked full width with the primary on top.\n *\n * Focus moves into the dialog when it opens, Tab stays inside, Escape closes it\n * and focus returns to the element that opened it.\n *\n * There is no destructive Modal: what is destructive is the action, so pass a\n * `destructive` Button as the primary action (or use `ConfirmModal`).\n */\nexport function Modal({\n layout,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: ModalProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: ModalLayout =\n layout ?? (isDesktop ? \"center\" : \"sheet\");\n\n // The open state lives here (not in Base UI) so the modal stays open when the\n // screen crosses the breakpoint and the Dialog root is swapped for the Drawer one.\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const open = openProp ?? uncontrolledOpen;\n const isControlled = openProp !== undefined;\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean, eventDetails: ModalChangeEventDetails) => {\n onOpenChange?.(nextOpen, eventDetails);\n if (!isControlled && !eventDetails.isCanceled) {\n setUncontrolledOpen(nextOpen);\n }\n },\n [isControlled, onOpenChange],\n );\n\n const context = useMemo(() => ({ layout: resolvedLayout }), [resolvedLayout]);\n\n return (\n <ModalContext.Provider value={context}>\n {resolvedLayout === \"sheet\" ? (\n <Drawer.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Drawer.Root>\n ) : (\n <Dialog.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Dialog.Root>\n )}\n </ModalContext.Provider>\n );\n}\n\nexport interface ModalTriggerProps extends Dialog.Trigger.Props {}\n\n/**\n * The element that opens the modal. Unstyled: pass the look with\n * `render={<Button variant=\"…\" />}`.\n */\nexport function ModalTrigger(props: ModalTriggerProps): React.JSX.Element {\n // Drawer.Trigger is Dialog.Trigger, so the same part works for both layouts.\n return <Dialog.Trigger data-slot=\"modal-trigger\" {...props} />;\n}\n\nexport interface ModalCloseProps extends Dialog.Close.Props {}\n\n/** Closes the modal. Use it for the \"Cancelar\" action: `render={<Button variant=\"secondary\" />}`. */\nexport function ModalClose(props: ModalCloseProps): React.JSX.Element {\n return <Dialog.Close data-slot=\"modal-close\" {...props} />;\n}\n\nconst backdropBase =\n \"fixed inset-0 z-50 min-h-dvh bg-scrim-image transition-opacity duration-200 data-starting-style:opacity-0 data-ending-style:opacity-0 motion-reduce:transition-none supports-[-webkit-touch-callout:none]:absolute\";\n\nexport interface ModalContentProps\n extends Omit<Dialog.Popup.Props, \"className\" | \"style\" | \"render\" | \"ref\"> {\n className?: string;\n style?: CSSProperties;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The dialog itself, with the scrim behind it. Children go in a column with 16px\n * between them: `ModalHeader`, then `ModalBody` (or your own content, sized to\n * the popup width) and `ModalFooter`.\n *\n * On open, focus goes to the dialog itself so screen readers announce its title and\n * message, and a destructive primary action is never focused by default. Pass\n * `initialFocus` to focus a field instead (e.g. in a form).\n */\nexport function ModalContent({\n className,\n children,\n initialFocus,\n ref,\n ...props\n}: ModalContentProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalContent\");\n const popupRef = useRef<HTMLDivElement | null>(null);\n\n const setPopupRef = useCallback(\n (node: HTMLDivElement | null) => {\n popupRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n if (layout === \"sheet\") {\n return (\n <Drawer.Portal>\n <Drawer.Backdrop\n data-slot=\"modal-backdrop\"\n className={cn(\n backdropBase,\n \"opacity-[calc(1-var(--drawer-swipe-progress,0))] data-swiping:duration-0\",\n )}\n />\n <Drawer.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-end justify-center\"\n >\n <Drawer.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"sheet\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-[90dvh] w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-t-xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden\",\n \"[transform:translateY(var(--drawer-swipe-movement-y,0px))] transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]\",\n \"data-starting-style:[transform:translateY(100%)] data-ending-style:[transform:translateY(100%)]\",\n \"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] data-swiping:select-none\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n <div\n aria-hidden=\"true\"\n data-slot=\"modal-handle\"\n className=\"mx-auto h-1 w-9 shrink-0 rounded-full bg-border\"\n />\n <Drawer.Content className=\"flex flex-col gap-4\">\n {children}\n </Drawer.Content>\n </Drawer.Popup>\n </Drawer.Viewport>\n </Drawer.Portal>\n );\n }\n\n return (\n <Dialog.Portal>\n <Dialog.Backdrop data-slot=\"modal-backdrop\" className={backdropBase} />\n <Dialog.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-center justify-center p-6\"\n >\n <Dialog.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"center\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-full w-120 max-w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-xl bg-background p-6 text-text-primary shadow-xl outline-hidden\",\n \"transition-[opacity,scale] duration-200 ease-out\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n );\n}\n\nexport interface ModalHeaderProps extends ComponentProps<\"div\"> {\n /**\n * Shows the close X (default). Only in the `center` layout: the sheet never has one,\n * it closes by swiping or tapping outside.\n */\n closeButton?: boolean;\n}\n\n/** Title row. Put a `ModalTitle` inside; the close X is added for you. */\nexport function ModalHeader({\n closeButton = true,\n className,\n children,\n ...props\n}: ModalHeaderProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalHeader\");\n const labels = useMediCoreLabels();\n\n return (\n <div\n data-slot=\"modal-header\"\n className={cn(\"flex items-start gap-3\", className)}\n {...props}\n >\n {children}\n {closeButton && layout === \"center\" && (\n <Dialog.Close\n render={\n <IconButton\n variant=\"ghost\"\n icon={<XCloseIcon />}\n aria-label={labels.close}\n />\n }\n />\n )}\n </div>\n );\n}\n\nexport interface ModalTitleProps extends Omit<Dialog.Title.Props, \"className\"> {\n className?: string;\n}\n\n/** Names the dialog (it becomes its accessible name). Renders an `<h2>`. */\nexport function ModalTitle({\n className,\n ...props\n}: ModalTitleProps): React.JSX.Element {\n return (\n <Dialog.Title\n data-slot=\"modal-title\"\n className={cn(\n \"min-w-0 flex-1 text-heading-h3 text-text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface ModalBodyProps\n extends Omit<Dialog.Description.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * The default content: the dialog's message, announced as its description.\n * For your own content (a form, a list) put it directly inside `ModalContent`\n * instead of `ModalBody`.\n */\nexport function ModalBody({\n className,\n ...props\n}: ModalBodyProps): React.JSX.Element {\n return (\n <Dialog.Description\n data-slot=\"modal-body\"\n className={cn(\"text-body-default text-text-secondary\", className)}\n {...props}\n />\n );\n}\n\nexport interface ModalFooterProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /** The main action, usually a `Button` (`variant=\"destructive\"` for a destructive confirmation). */\n primaryAction: ReactNode;\n /** Usually `<ModalClose render={<Button variant=\"secondary\" />}>Cancelar</ModalClose>`. */\n secondaryAction?: ReactNode;\n}\n\n/**\n * The actions. `center`: in a row aligned right, secondary first. `sheet`: stacked\n * full width with the primary on top, away from the resting thumb.\n */\nexport function ModalFooter({\n primaryAction,\n secondaryAction,\n className,\n ...props\n}: ModalFooterProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalFooter\");\n const isSheet = layout === \"sheet\";\n\n return (\n <div\n data-slot=\"modal-footer\"\n className={cn(\n \"flex gap-2\",\n isSheet ? \"flex-col *:w-full\" : \"flex-row flex-wrap justify-end\",\n className,\n )}\n {...props}\n >\n {isSheet ? primaryAction : secondaryAction}\n {isSheet ? secondaryAction : primaryAction}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"modal.js","sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["import { Dialog } from \"@base-ui/react/dialog\";\nimport { Drawer } from \"@base-ui/react/drawer\";\nimport {\n type ComponentProps,\n type CSSProperties,\n createContext,\n type ReactNode,\n type Ref,\n type RefObject,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { IconButton } from \"../icon-button\";\nimport { DESKTOP_MEDIA_QUERY, useMediaQuery } from \"./use-media-query\";\n\nexport type ModalLayout = \"center\" | \"sheet\";\n\nexport type ModalChangeEventDetails =\n | Dialog.Root.ChangeEventDetails\n | Drawer.Root.ChangeEventDetails;\n\nexport interface ModalActions {\n /** Closes the modal as if the person had dismissed it. */\n close: () => void;\n /** Unmounts the popup after an externally controlled closing animation. */\n unmount: () => void;\n}\n\ninterface ModalContextValue {\n layout: ModalLayout;\n}\n\nconst ModalContext = createContext<ModalContextValue | null>(null);\n\nfunction useModalContext(part: string): ModalContextValue {\n const context = useContext(ModalContext);\n if (context === null) {\n throw new Error(`MediCore: <${part}> must be used inside <Modal>.`);\n }\n return context;\n}\n\nexport interface ModalProps {\n /**\n * `center` is the 480px dialog, `sheet` the bottom sheet. Leave it unset and it\n * follows the screen: sheet below `md`, centered dialog from `md` up.\n */\n layout?: ModalLayout;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, eventDetails: ModalChangeEventDetails) => void;\n /** Called after the open/close animation finishes. */\n onOpenChangeComplete?: (open: boolean) => void;\n /** Keeps the modal open when the person clicks or taps outside it. */\n disablePointerDismissal?: boolean;\n /** Imperative `close()` / `unmount()`. */\n actionsRef?: RefObject<ModalActions | null>;\n children?: ReactNode;\n}\n\n/**\n * Confirmation and interruption dialog. One component, two layouts with the same\n * content: a centered dialog on desktop and a bottom sheet on mobile.\n *\n * - `center`: close X in the header, actions in a row aligned right.\n * - `sheet`: drag handle and no X (it closes by swiping down or tapping outside),\n * actions stacked full width with the primary on top.\n *\n * Focus moves into the dialog when it opens, Tab stays inside, Escape closes it\n * and focus returns to the element that opened it.\n *\n * There is no destructive Modal: what is destructive is the action, so pass a\n * `destructive` Button as the primary action (or use `ConfirmModal`).\n */\nexport function Modal({\n layout,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: ModalProps): React.JSX.Element {\n const isDesktop = useMediaQuery(DESKTOP_MEDIA_QUERY);\n const resolvedLayout: ModalLayout =\n layout ?? (isDesktop ? \"center\" : \"sheet\");\n\n // The open state lives here (not in Base UI) so the modal stays open when the\n // screen crosses the breakpoint and the Dialog root is swapped for the Drawer one.\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const open = openProp ?? uncontrolledOpen;\n const isControlled = openProp !== undefined;\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean, eventDetails: ModalChangeEventDetails) => {\n onOpenChange?.(nextOpen, eventDetails);\n if (!isControlled && !eventDetails.isCanceled) {\n setUncontrolledOpen(nextOpen);\n }\n },\n [isControlled, onOpenChange],\n );\n\n const context = useMemo(() => ({ layout: resolvedLayout }), [resolvedLayout]);\n\n return (\n <ModalContext.Provider value={context}>\n {resolvedLayout === \"sheet\" ? (\n <Drawer.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Drawer.Root>\n ) : (\n <Dialog.Root open={open} onOpenChange={handleOpenChange} {...props}>\n {children}\n </Dialog.Root>\n )}\n </ModalContext.Provider>\n );\n}\n\nexport interface ModalTriggerProps extends Dialog.Trigger.Props {}\n\n/**\n * The element that opens the modal. Unstyled: pass the look with\n * `render={<Button variant=\"…\" />}`.\n */\nexport function ModalTrigger(props: ModalTriggerProps): React.JSX.Element {\n // Drawer.Trigger is Dialog.Trigger, so the same part works for both layouts.\n return <Dialog.Trigger data-slot=\"modal-trigger\" {...props} />;\n}\n\nexport interface ModalCloseProps extends Dialog.Close.Props {}\n\n/** Closes the modal. Use it for the \"Cancelar\" action: `render={<Button variant=\"secondary\" />}`. */\nexport function ModalClose(props: ModalCloseProps): React.JSX.Element {\n return <Dialog.Close data-slot=\"modal-close\" {...props} />;\n}\n\nconst backdropBase =\n \"fixed inset-0 z-50 min-h-dvh bg-scrim-image transition-opacity duration-200 data-starting-style:opacity-0 data-ending-style:opacity-0 motion-reduce:transition-none supports-[-webkit-touch-callout:none]:absolute\";\n\nexport interface ModalContentProps\n extends Omit<Dialog.Popup.Props, \"className\" | \"style\" | \"render\" | \"ref\"> {\n className?: string;\n style?: CSSProperties;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The dialog itself, with the scrim behind it. Children go in a column with 16px\n * between them: `ModalHeader`, then `ModalBody` (or your own content, sized to\n * the popup width) and `ModalFooter`.\n *\n * On open, focus goes to the dialog itself so screen readers announce its title and\n * message, and a destructive primary action is never focused by default. Pass\n * `initialFocus` to focus a field instead (e.g. in a form).\n */\nexport function ModalContent({\n className,\n children,\n initialFocus,\n ref,\n ...props\n}: ModalContentProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalContent\");\n const popupRef = useRef<HTMLDivElement | null>(null);\n\n const setPopupRef = useCallback(\n (node: HTMLDivElement | null) => {\n popupRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n if (layout === \"sheet\") {\n return (\n <Drawer.Portal>\n <Drawer.Backdrop\n data-slot=\"modal-backdrop\"\n className={cn(\n backdropBase,\n \"opacity-[calc(1-var(--drawer-swipe-progress,0))] data-swiping:duration-0\",\n )}\n />\n <Drawer.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-end justify-center\"\n >\n <Drawer.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"sheet\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-[90dvh] w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-t-2xl bg-background px-6 pt-3 pb-8 text-text-primary shadow-xl outline-hidden\",\n \"[transform:translateY(var(--drawer-swipe-movement-y,0px))] transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]\",\n \"data-starting-style:[transform:translateY(100%)] data-ending-style:[transform:translateY(100%)]\",\n \"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] data-swiping:select-none\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n <div\n aria-hidden=\"true\"\n data-slot=\"modal-handle\"\n className=\"mx-auto h-1 w-9 shrink-0 rounded-full bg-border\"\n />\n <Drawer.Content className=\"flex flex-col gap-4\">\n {children}\n </Drawer.Content>\n </Drawer.Popup>\n </Drawer.Viewport>\n </Drawer.Portal>\n );\n }\n\n return (\n <Dialog.Portal>\n <Dialog.Backdrop data-slot=\"modal-backdrop\" className={backdropBase} />\n <Dialog.Viewport\n data-slot=\"modal-viewport\"\n className=\"fixed inset-0 z-50 flex items-center justify-center p-6\"\n >\n <Dialog.Popup\n ref={setPopupRef}\n data-slot=\"modal-content\"\n data-layout=\"center\"\n initialFocus={initialFocus ?? popupRef}\n className={cn(\n \"flex max-h-full w-120 max-w-full flex-col gap-4 overflow-y-auto overscroll-contain\",\n \"rounded-2xl bg-background p-6 text-text-primary shadow-xl outline-hidden\",\n \"transition-[opacity,scale] duration-200 ease-out\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0\",\n \"motion-reduce:transition-none\",\n className,\n )}\n {...props}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n );\n}\n\nexport interface ModalHeaderProps extends ComponentProps<\"div\"> {\n /**\n * Shows the close X (default). Only in the `center` layout: the sheet never has one,\n * it closes by swiping or tapping outside.\n */\n closeButton?: boolean;\n}\n\n/** Title row. Put a `ModalTitle` inside; the close X is added for you. */\nexport function ModalHeader({\n closeButton = true,\n className,\n children,\n ...props\n}: ModalHeaderProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalHeader\");\n const labels = useMediCoreLabels();\n\n return (\n <div\n data-slot=\"modal-header\"\n className={cn(\"flex items-start gap-3\", className)}\n {...props}\n >\n {children}\n {closeButton && layout === \"center\" && (\n <Dialog.Close\n render={\n <IconButton\n variant=\"ghost\"\n icon={<XCloseIcon />}\n aria-label={labels.close}\n />\n }\n />\n )}\n </div>\n );\n}\n\nexport interface ModalTitleProps extends Omit<Dialog.Title.Props, \"className\"> {\n className?: string;\n}\n\n/** Names the dialog (it becomes its accessible name). Renders an `<h2>`. */\nexport function ModalTitle({\n className,\n ...props\n}: ModalTitleProps): React.JSX.Element {\n return (\n <Dialog.Title\n data-slot=\"modal-title\"\n className={cn(\n \"min-w-0 flex-1 text-heading-h3 text-text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface ModalBodyProps\n extends Omit<Dialog.Description.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * The default content: the dialog's message, announced as its description.\n * For your own content (a form, a list) put it directly inside `ModalContent`\n * instead of `ModalBody`.\n */\nexport function ModalBody({\n className,\n ...props\n}: ModalBodyProps): React.JSX.Element {\n return (\n <Dialog.Description\n data-slot=\"modal-body\"\n className={cn(\"text-body-default text-text-secondary\", className)}\n {...props}\n />\n );\n}\n\nexport interface ModalFooterProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /** The main action, usually a `Button` (`variant=\"destructive\"` for a destructive confirmation). */\n primaryAction: ReactNode;\n /** Usually `<ModalClose render={<Button variant=\"secondary\" />}>Cancelar</ModalClose>`. */\n secondaryAction?: ReactNode;\n}\n\n/**\n * The actions. `center`: in a row aligned right, secondary first. `sheet`: stacked\n * full width with the primary on top, away from the resting thumb.\n */\nexport function ModalFooter({\n primaryAction,\n secondaryAction,\n className,\n ...props\n}: ModalFooterProps): React.JSX.Element {\n const { layout } = useModalContext(\"ModalFooter\");\n const isSheet = layout === \"sheet\";\n\n return (\n <div\n data-slot=\"modal-footer\"\n className={cn(\n \"flex gap-2\",\n isSheet ? \"flex-col *:w-full\" : \"flex-row flex-wrap justify-end\",\n className,\n )}\n {...props}\n >\n {isSheet ? primaryAction : secondaryAction}\n {isSheet ? secondaryAction : primaryAction}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -142,11 +142,11 @@ function PhoneInput({
142
142
  {
143
143
  "data-slot": "phone-input-prefix",
144
144
  className: cn(
145
- "group/control relative w-24 shrink-0",
146
145
  controlBox,
147
146
  controlRow,
148
147
  controlHeight.default,
149
148
  wrappedControlStates,
149
+ "group/control relative w-24 shrink-0",
150
150
  // The number carries aria-invalid (it is the Field control): the prefix follows it.
151
151
  "group-has-[[aria-invalid=true]]/phone:border-error group-has-[[aria-invalid=true]]/phone:ring-error"
152
152
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"phone-input.js","sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\n\nimport { ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n controlBox,\n controlHeight,\n controlRow,\n wrappedControlStates,\n} from \"../input/control-styles\";\nimport { Input, type InputProps } from \"../input/input\";\n\nexport interface PhonePrefix {\n /** Dial code, with the plus: \"+34\". It is what the box shows and what is submitted. */\n value: string;\n /** Country name for the list: \"España\". The option reads \"España (+34)\". */\n label: string;\n}\n\ntype InputChangeEvent = Parameters<NonNullable<InputProps[\"onChange\"]>>[0];\n\n/** E.164 allows at most 15 digits. */\nconst MAX_DIGITS = 15;\n\nconst defaultRegions: ReadonlyArray<readonly [region: string, dial: string]> = [\n [\"ES\", \"+34\"],\n [\"PT\", \"+351\"],\n [\"FR\", \"+33\"],\n [\"IT\", \"+39\"],\n [\"DE\", \"+49\"],\n [\"GB\", \"+44\"],\n [\"MX\", \"+52\"],\n [\"CO\", \"+57\"],\n [\"AR\", \"+54\"],\n [\"CL\", \"+56\"],\n];\n\n/**\n * A short default list (Spain, neighbours and the main Latin American markets). The\n * country names come from `Intl.DisplayNames` in the page language (`<html lang>`),\n * so nothing is hardcoded. The real list comes from the app.\n */\nexport function getDefaultPhonePrefixes(): PhonePrefix[] {\n const lang =\n typeof document === \"undefined\"\n ? undefined\n : document.documentElement.lang || undefined;\n let names: Intl.DisplayNames | undefined;\n try {\n names = new Intl.DisplayNames(lang ? [lang] : undefined, {\n type: \"region\",\n });\n } catch {\n names = undefined;\n }\n return defaultRegions.map(([region, value]) => ({\n value,\n label: names?.of(region) ?? region,\n }));\n}\n\n/** 612345678 → \"612 345 678\": groups of three, as the person reads it aloud. */\nfunction formatDigits(digits: string): string {\n return digits.replace(/(\\d{3})(?=\\d)/g, \"$1 \");\n}\n\n/** Where the caret goes in `formatted` so that `digits` digits are before it. */\nfunction caretAfterDigits(formatted: string, digits: number): number {\n if (digits <= 0) {\n return 0;\n }\n let seen = 0;\n for (let index = 0; index < formatted.length; index++) {\n if (/\\d/.test(formatted.charAt(index))) {\n seen++;\n if (seen === digits) {\n return index + 1;\n }\n }\n }\n return formatted.length;\n}\n\nexport interface PhoneInputProps\n extends Omit<\n InputProps,\n | \"type\"\n | \"value\"\n | \"defaultValue\"\n | \"onValueChange\"\n | \"size\"\n | \"leadingIcon\"\n | \"trailingIcon\"\n | \"clearable\"\n | \"name\"\n > {\n /** Number without spaces (digits only). Use when controlled. */\n value?: string;\n /** Number without spaces. Use when uncontrolled. */\n defaultValue?: string;\n /** Called with the digits only, never with the grouping spaces. */\n onValueChange?: (value: string) => void;\n /** Countries in the prefix list. Defaults to `getDefaultPhonePrefixes()`. */\n prefixes?: readonly PhonePrefix[];\n /** Chosen dial code (\"+34\"). Use when controlled. */\n prefix?: string;\n /** Initial dial code, the country chosen when entering. Default \"+34\". */\n defaultPrefix?: string;\n onPrefixChange?: (prefix: string) => void;\n /** Form name of the number. It is submitted without spaces. */\n name?: string;\n /** Form name of the dial code. */\n prefixName?: string;\n}\n\n/**\n * Mobile number with country prefix: two controls in a row, a native prefix select\n * (+34 by default, `autocomplete=\"tel-country-code\"`) and the number\n * (`type=\"tel\" inputmode=\"tel\" autocomplete=\"tel-national\"`).\n *\n * The number is shown in groups of three (612 345 678) but `onValueChange` and the\n * form get it without spaces. Put it inside `Field` for the label and the message,\n * and make the error say what happens (\"Este número no parece un móvil español: tiene\n * que empezar por 6 o 7 y tener 9 cifras\"), not just \"Número no válido\".\n *\n * The prefix list opens the operating system's picker (a real `<select>` under the\n * \"+34\"), so it is searchable and accessible on every device.\n */\nexport function PhoneInput({\n value,\n defaultValue,\n onValueChange,\n prefixes: prefixesProp,\n prefix: prefixProp,\n defaultPrefix = \"+34\",\n onPrefixChange,\n name,\n prefixName,\n disabled: disabledProp,\n invalid,\n className,\n onChange,\n ref,\n \"aria-label\": ariaLabel,\n ...props\n}: PhoneInputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const field = useFieldContext();\n const disabled = Boolean(disabledProp || field?.disabled);\n\n const prefixes = useMemo(\n () => prefixesProp ?? getDefaultPhonePrefixes(),\n [prefixesProp],\n );\n const [uncontrolledPrefix, setUncontrolledPrefix] = useState(defaultPrefix);\n const prefix = prefixProp ?? uncontrolledPrefix;\n\n const [uncontrolledDigits, setUncontrolledDigits] = useState(() =>\n (defaultValue ?? \"\").replace(/\\D/g, \"\").slice(0, MAX_DIGITS),\n );\n const digits =\n value === undefined\n ? uncontrolledDigits\n : value.replace(/\\D/g, \"\").slice(0, MAX_DIGITS);\n const formatted = formatDigits(digits);\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const pendingCaret = useRef<number | null>(null);\n\n const setRefs = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n // Reformatting moves the caret to the end; put it back after the same digit.\n // biome-ignore lint/correctness/useExhaustiveDependencies: runs when the formatted text changes.\n useLayoutEffect(() => {\n const input = inputRef.current;\n const digitsBefore = pendingCaret.current;\n pendingCaret.current = null;\n if (digitsBefore === null || !input || document.activeElement !== input) {\n return;\n }\n const position = caretAfterDigits(input.value, digitsBefore);\n input.setSelectionRange(position, position);\n }, [formatted]);\n\n function handleChange(event: InputChangeEvent): void {\n const input = event.currentTarget;\n const raw = input.value;\n const caret = input.selectionStart ?? raw.length;\n let next = raw.replace(/\\D/g, \"\");\n let digitsBefore = raw.slice(0, caret).replace(/\\D/g, \"\").length;\n\n const compact = raw.replace(/[^\\d+]/g, \"\");\n if (compact.startsWith(\"+\")) {\n // A full number pasted or autofilled with its prefix: keep the national part.\n next = compact.startsWith(prefix)\n ? compact.slice(prefix.length)\n : compact.slice(1);\n digitsBefore = next.length;\n } else if (\n (event.nativeEvent as InputEvent).inputType === \"deleteContentBackward\" &&\n next === digits &&\n digitsBefore > 0\n ) {\n // Backspace on a grouping space: delete the digit before it instead.\n next = next.slice(0, digitsBefore - 1) + next.slice(digitsBefore);\n digitsBefore -= 1;\n }\n\n next = next.slice(0, MAX_DIGITS);\n pendingCaret.current = Math.min(digitsBefore, next.length);\n if (value === undefined) {\n setUncontrolledDigits(next);\n }\n if (next !== digits) {\n onValueChange?.(next);\n }\n onChange?.(event);\n }\n\n return (\n <div\n data-slot=\"phone-input\"\n className={cn(\"group/phone flex w-full min-w-0 gap-2\", className)}\n >\n {/* A <label> around the select: its \"+34\" is then the select's own text, which\n also keeps it out of contrast checks when the control is disabled. */}\n <label\n data-slot=\"phone-input-prefix\"\n className={cn(\n \"group/control relative w-24 shrink-0\",\n controlBox,\n controlRow,\n controlHeight.default,\n wrappedControlStates,\n // The number carries aria-invalid (it is the Field control): the prefix follows it.\n \"group-has-[[aria-invalid=true]]/phone:border-error group-has-[[aria-invalid=true]]/phone:ring-error\",\n )}\n >\n <span aria-hidden className=\"min-w-0 flex-1 truncate\">\n {prefix}\n </span>\n <ChevronDownIcon\n size=\"sm\"\n className=\"text-text-primary group-has-[select:disabled]/control:text-text-disabled\"\n />\n <select\n aria-label={labels.phonePrefix}\n autoComplete=\"tel-country-code\"\n name={prefixName}\n value={prefix}\n disabled={disabled}\n onChange={(event) => {\n const next = event.currentTarget.value;\n if (prefixProp === undefined) {\n setUncontrolledPrefix(next);\n }\n onPrefixChange?.(next);\n }}\n className=\"absolute inset-0 size-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed\"\n >\n {prefixes.some((option) => option.value === prefix) ? null : (\n <option value={prefix}>{prefix}</option>\n )}\n {prefixes.map((option) => (\n <option\n key={`${option.value}-${option.label}`}\n value={option.value}\n >\n {`${option.label} (${option.value})`}\n </option>\n ))}\n </select>\n </label>\n <Input\n ref={setRefs}\n type=\"tel\"\n inputMode=\"tel\"\n autoComplete=\"tel-national\"\n aria-label={ariaLabel ?? labels.phoneNumber}\n value={formatted}\n disabled={disabled}\n invalid={invalid}\n onChange={handleChange}\n className=\"flex-1\"\n {...props}\n />\n {name && (\n <input type=\"hidden\" name={name} value={digits} disabled={disabled} />\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"phone-input.js","sources":["../../../src/components/phone-input/phone-input.tsx"],"sourcesContent":["import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\n\nimport { ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n controlBox,\n controlHeight,\n controlRow,\n wrappedControlStates,\n} from \"../input/control-styles\";\nimport { Input, type InputProps } from \"../input/input\";\n\nexport interface PhonePrefix {\n /** Dial code, with the plus: \"+34\". It is what the box shows and what is submitted. */\n value: string;\n /** Country name for the list: \"España\". The option reads \"España (+34)\". */\n label: string;\n}\n\ntype InputChangeEvent = Parameters<NonNullable<InputProps[\"onChange\"]>>[0];\n\n/** E.164 allows at most 15 digits. */\nconst MAX_DIGITS = 15;\n\nconst defaultRegions: ReadonlyArray<readonly [region: string, dial: string]> = [\n [\"ES\", \"+34\"],\n [\"PT\", \"+351\"],\n [\"FR\", \"+33\"],\n [\"IT\", \"+39\"],\n [\"DE\", \"+49\"],\n [\"GB\", \"+44\"],\n [\"MX\", \"+52\"],\n [\"CO\", \"+57\"],\n [\"AR\", \"+54\"],\n [\"CL\", \"+56\"],\n];\n\n/**\n * A short default list (Spain, neighbours and the main Latin American markets). The\n * country names come from `Intl.DisplayNames` in the page language (`<html lang>`),\n * so nothing is hardcoded. The real list comes from the app.\n */\nexport function getDefaultPhonePrefixes(): PhonePrefix[] {\n const lang =\n typeof document === \"undefined\"\n ? undefined\n : document.documentElement.lang || undefined;\n let names: Intl.DisplayNames | undefined;\n try {\n names = new Intl.DisplayNames(lang ? [lang] : undefined, {\n type: \"region\",\n });\n } catch {\n names = undefined;\n }\n return defaultRegions.map(([region, value]) => ({\n value,\n label: names?.of(region) ?? region,\n }));\n}\n\n/** 612345678 → \"612 345 678\": groups of three, as the person reads it aloud. */\nfunction formatDigits(digits: string): string {\n return digits.replace(/(\\d{3})(?=\\d)/g, \"$1 \");\n}\n\n/** Where the caret goes in `formatted` so that `digits` digits are before it. */\nfunction caretAfterDigits(formatted: string, digits: number): number {\n if (digits <= 0) {\n return 0;\n }\n let seen = 0;\n for (let index = 0; index < formatted.length; index++) {\n if (/\\d/.test(formatted.charAt(index))) {\n seen++;\n if (seen === digits) {\n return index + 1;\n }\n }\n }\n return formatted.length;\n}\n\nexport interface PhoneInputProps\n extends Omit<\n InputProps,\n | \"type\"\n | \"value\"\n | \"defaultValue\"\n | \"onValueChange\"\n | \"size\"\n | \"leadingIcon\"\n | \"trailingIcon\"\n | \"clearable\"\n | \"name\"\n > {\n /** Number without spaces (digits only). Use when controlled. */\n value?: string;\n /** Number without spaces. Use when uncontrolled. */\n defaultValue?: string;\n /** Called with the digits only, never with the grouping spaces. */\n onValueChange?: (value: string) => void;\n /** Countries in the prefix list. Defaults to `getDefaultPhonePrefixes()`. */\n prefixes?: readonly PhonePrefix[];\n /** Chosen dial code (\"+34\"). Use when controlled. */\n prefix?: string;\n /** Initial dial code, the country chosen when entering. Default \"+34\". */\n defaultPrefix?: string;\n onPrefixChange?: (prefix: string) => void;\n /** Form name of the number. It is submitted without spaces. */\n name?: string;\n /** Form name of the dial code. */\n prefixName?: string;\n}\n\n/**\n * Mobile number with country prefix: two controls in a row, a native prefix select\n * (+34 by default, `autocomplete=\"tel-country-code\"`) and the number\n * (`type=\"tel\" inputmode=\"tel\" autocomplete=\"tel-national\"`).\n *\n * The number is shown in groups of three (612 345 678) but `onValueChange` and the\n * form get it without spaces. Put it inside `Field` for the label and the message,\n * and make the error say what happens (\"Este número no parece un móvil español: tiene\n * que empezar por 6 o 7 y tener 9 cifras\"), not just \"Número no válido\".\n *\n * The prefix list opens the operating system's picker (a real `<select>` under the\n * \"+34\"), so it is searchable and accessible on every device.\n */\nexport function PhoneInput({\n value,\n defaultValue,\n onValueChange,\n prefixes: prefixesProp,\n prefix: prefixProp,\n defaultPrefix = \"+34\",\n onPrefixChange,\n name,\n prefixName,\n disabled: disabledProp,\n invalid,\n className,\n onChange,\n ref,\n \"aria-label\": ariaLabel,\n ...props\n}: PhoneInputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const field = useFieldContext();\n const disabled = Boolean(disabledProp || field?.disabled);\n\n const prefixes = useMemo(\n () => prefixesProp ?? getDefaultPhonePrefixes(),\n [prefixesProp],\n );\n const [uncontrolledPrefix, setUncontrolledPrefix] = useState(defaultPrefix);\n const prefix = prefixProp ?? uncontrolledPrefix;\n\n const [uncontrolledDigits, setUncontrolledDigits] = useState(() =>\n (defaultValue ?? \"\").replace(/\\D/g, \"\").slice(0, MAX_DIGITS),\n );\n const digits =\n value === undefined\n ? uncontrolledDigits\n : value.replace(/\\D/g, \"\").slice(0, MAX_DIGITS);\n const formatted = formatDigits(digits);\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const pendingCaret = useRef<number | null>(null);\n\n const setRefs = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n // Reformatting moves the caret to the end; put it back after the same digit.\n // biome-ignore lint/correctness/useExhaustiveDependencies: runs when the formatted text changes.\n useLayoutEffect(() => {\n const input = inputRef.current;\n const digitsBefore = pendingCaret.current;\n pendingCaret.current = null;\n if (digitsBefore === null || !input || document.activeElement !== input) {\n return;\n }\n const position = caretAfterDigits(input.value, digitsBefore);\n input.setSelectionRange(position, position);\n }, [formatted]);\n\n function handleChange(event: InputChangeEvent): void {\n const input = event.currentTarget;\n const raw = input.value;\n const caret = input.selectionStart ?? raw.length;\n let next = raw.replace(/\\D/g, \"\");\n let digitsBefore = raw.slice(0, caret).replace(/\\D/g, \"\").length;\n\n const compact = raw.replace(/[^\\d+]/g, \"\");\n if (compact.startsWith(\"+\")) {\n // A full number pasted or autofilled with its prefix: keep the national part.\n next = compact.startsWith(prefix)\n ? compact.slice(prefix.length)\n : compact.slice(1);\n digitsBefore = next.length;\n } else if (\n (event.nativeEvent as InputEvent).inputType === \"deleteContentBackward\" &&\n next === digits &&\n digitsBefore > 0\n ) {\n // Backspace on a grouping space: delete the digit before it instead.\n next = next.slice(0, digitsBefore - 1) + next.slice(digitsBefore);\n digitsBefore -= 1;\n }\n\n next = next.slice(0, MAX_DIGITS);\n pendingCaret.current = Math.min(digitsBefore, next.length);\n if (value === undefined) {\n setUncontrolledDigits(next);\n }\n if (next !== digits) {\n onValueChange?.(next);\n }\n onChange?.(event);\n }\n\n return (\n <div\n data-slot=\"phone-input\"\n className={cn(\"group/phone flex w-full min-w-0 gap-2\", className)}\n >\n {/* A <label> around the select: its \"+34\" is then the select's own text, which\n also keeps it out of contrast checks when the control is disabled. */}\n <label\n data-slot=\"phone-input-prefix\"\n className={cn(\n controlBox,\n controlRow,\n controlHeight.default,\n wrappedControlStates,\n \"group/control relative w-24 shrink-0\",\n // The number carries aria-invalid (it is the Field control): the prefix follows it.\n \"group-has-[[aria-invalid=true]]/phone:border-error group-has-[[aria-invalid=true]]/phone:ring-error\",\n )}\n >\n <span aria-hidden className=\"min-w-0 flex-1 truncate\">\n {prefix}\n </span>\n <ChevronDownIcon\n size=\"sm\"\n className=\"text-text-primary group-has-[select:disabled]/control:text-text-disabled\"\n />\n <select\n aria-label={labels.phonePrefix}\n autoComplete=\"tel-country-code\"\n name={prefixName}\n value={prefix}\n disabled={disabled}\n onChange={(event) => {\n const next = event.currentTarget.value;\n if (prefixProp === undefined) {\n setUncontrolledPrefix(next);\n }\n onPrefixChange?.(next);\n }}\n className=\"absolute inset-0 size-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed\"\n >\n {prefixes.some((option) => option.value === prefix) ? null : (\n <option value={prefix}>{prefix}</option>\n )}\n {prefixes.map((option) => (\n <option\n key={`${option.value}-${option.label}`}\n value={option.value}\n >\n {`${option.label} (${option.value})`}\n </option>\n ))}\n </select>\n </label>\n <Input\n ref={setRefs}\n type=\"tel\"\n inputMode=\"tel\"\n autoComplete=\"tel-national\"\n aria-label={ariaLabel ?? labels.phoneNumber}\n value={formatted}\n disabled={disabled}\n invalid={invalid}\n onChange={handleChange}\n className=\"flex-1\"\n {...props}\n />\n {name && (\n <input type=\"hidden\" name={name} value={digits} disabled={disabled} />\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -76,7 +76,7 @@ function Select({
76
76
  // Same surface as Menu: shadow-md to lift it and a border for when the
77
77
  // shadow doesn't show (dark backgrounds, poor screens).
78
78
  "max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto",
79
- "rounded-lg border border-border-subtle bg-background p-1 shadow-md outline-none",
79
+ "rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-none",
80
80
  "transition-[opacity,scale] duration-150 motion-reduce:transition-none",
81
81
  "data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
82
82
  ),
@@ -105,7 +105,7 @@ function SelectItem({
105
105
  {
106
106
  "data-slot": "select-item",
107
107
  className: cn(
108
- "flex h-control-md cursor-pointer select-none items-center gap-3 rounded-md px-3 text-label-default text-text-primary outline-none",
108
+ "flex h-control-md cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-label-default text-text-primary outline-none",
109
109
  "data-highlighted:bg-surface focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset",
110
110
  "data-disabled:cursor-not-allowed data-disabled:text-text-disabled",
111
111
  className
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sources":["../../../src/components/select/select.tsx"],"sourcesContent":["import { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { type ReactNode, type Ref, useLayoutEffect } from \"react\";\n\nimport { CheckIcon, ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n type ControlSize,\n controlBox,\n controlHeight,\n controlIcon,\n controlRow,\n triggerControlStates,\n} from \"../input/control-styles\";\n\nexport interface SelectOption<Value> {\n value: Value;\n label: ReactNode;\n disabled?: boolean;\n}\n\nexport interface SelectProps<Value>\n extends Omit<\n BaseSelect.Root.Props<Value, false>,\n \"multiple\" | \"items\" | \"children\"\n > {\n /** The options. Also lets the trigger show the label of the chosen value. */\n items?: readonly SelectOption<Value>[];\n /** Custom options (`SelectItem`) instead of `items`. */\n children?: ReactNode;\n /** Shown in text-secondary while nothing is chosen: \"Elige una opción\". */\n placeholder?: ReactNode;\n /** `default` 40px, `small` 36px: the same heights as Input and Button. */\n size?: ControlSize;\n /** Red 2px border and `aria-invalid`. The message goes under it, in `Field`. */\n invalid?: boolean;\n /** Content icon before the value. The chevron is structure, not a prop. */\n leadingIcon?: ReactNode;\n /** Applied to the trigger. */\n className?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * Dropdown with a styled list (Base UI Select). The trigger has exactly the geometry\n * of Input, so a field and a select in the same row only differ by the chevron.\n *\n * The open list is not in Figma on purpose. Prefer `NativeSelect` on mobile and\n * whenever the options are plain text: it opens the operating system's picker and\n * is accessible out of the box. Use this one on desktop screens (the clinics admin)\n * where the list has to look like MediCore. Options that an `<option>` can't draw\n * (a doctor with photo and licence number) are a `Menu`, not this.\n *\n * Name it with `Field`'s label, or with `aria-label` when there is no visible label.\n */\nexport function Select<Value>({\n items,\n children,\n placeholder,\n size = \"default\",\n invalid = false,\n leadingIcon,\n className,\n disabled,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ref,\n ...props\n}: SelectProps<Value>): React.JSX.Element {\n const setNativeLabel = useFieldContext()?.setNativeLabel;\n\n // A native <label> around a button would open the list on click: Field renders a\n // non-native label while a Select is inside.\n useLayoutEffect(() => {\n if (!setNativeLabel) {\n return undefined;\n }\n setNativeLabel(false);\n return () => setNativeLabel(true);\n }, [setNativeLabel]);\n\n return (\n <BaseSelect.Root items={items} disabled={disabled} {...props}>\n <BaseSelect.Trigger\n ref={ref}\n data-slot=\"select\"\n // Only when given: an explicit undefined would override Field's aria-labelledby.\n {...(ariaLabel !== undefined && { \"aria-label\": ariaLabel })}\n {...(ariaLabelledBy !== undefined && {\n \"aria-labelledby\": ariaLabelledBy,\n })}\n aria-describedby={ariaDescribedBy}\n aria-invalid={invalid && !disabled ? true : undefined}\n className={cn(\n \"group/control cursor-pointer text-start\",\n controlBox,\n controlRow,\n controlHeight[size],\n triggerControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseSelect.Value\n placeholder={placeholder}\n className=\"min-w-0 flex-1 truncate data-placeholder:text-text-secondary group-data-disabled/control:text-text-disabled\"\n />\n <BaseSelect.Icon className=\"flex shrink-0 text-text-primary group-data-disabled/control:text-text-disabled\">\n <ChevronDownIcon size=\"sm\" />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n alignItemWithTrigger={false}\n sideOffset={4}\n className=\"z-50 outline-none\"\n >\n <BaseSelect.Popup\n data-slot=\"select-popup\"\n className={cn(\n // Same surface as Menu: shadow-md to lift it and a border for when the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto\",\n \"rounded-lg border border-border-subtle bg-background p-1 shadow-md outline-none\",\n \"transition-[opacity,scale] duration-150 motion-reduce:transition-none\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0\",\n )}\n >\n <BaseSelect.List>\n {children ??\n items?.map((item) => (\n <SelectItem\n key={String(item.value)}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </SelectItem>\n ))}\n </BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n </BaseSelect.Root>\n );\n}\n\nexport interface SelectItemProps\n extends Omit<BaseSelect.Item.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * One option of `Select`: 40px rows like MenuItem (a list is used with a finger on\n * it), surface on hover / arrow keys, a check on the chosen one.\n */\nexport function SelectItem({\n className,\n children,\n ...props\n}: SelectItemProps): React.JSX.Element {\n return (\n <BaseSelect.Item\n data-slot=\"select-item\"\n className={cn(\n \"flex h-control-md cursor-pointer select-none items-center gap-3 rounded-md px-3 text-label-default text-text-primary outline-none\",\n \"data-highlighted:bg-surface focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n className,\n )}\n {...props}\n >\n <BaseSelect.ItemText className=\"min-w-0 flex-1 truncate\">\n {children}\n </BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className=\"flex shrink-0 text-text-brand\">\n <CheckIcon size=\"sm\" />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n"],"names":["BaseSelect"],"mappings":";;;;;;;;AA0DO,SAAS,OAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,QAAM,iBAAiB,mBAAmB;AAI1C,kBAAgB,MAAM;AACpB,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,IACT;AACA,mBAAe,KAAK;AACpB,WAAO,MAAM,eAAe,IAAI;AAAA,EAClC,GAAG,CAAC,cAAc,CAAC;AAEnB,8BACGA,SAAW,MAAX,EAAgB,OAAc,UAAqB,GAAG,OACrD,UAAA;AAAA,IAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QAET,GAAI,cAAc,UAAa,EAAE,cAAc,UAAA;AAAA,QAC/C,GAAI,mBAAmB,UAAa;AAAA,UACnC,mBAAmB;AAAA,QAAA;AAAA,QAErB,oBAAkB;AAAA,QAClB,gBAAc,WAAW,CAAC,WAAW,OAAO;AAAA,QAC5C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc,IAAI;AAAA,UAClB;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,eAAe,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,aAAY;AAAA,UAC3D;AAAA,YAACA,SAAW;AAAA,YAAX;AAAA,cACC;AAAA,cACA,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAEZ,oBAACA,SAAW,MAAX,EAAgB,WAAU,kFACzB,UAAA,oBAAC,iBAAA,EAAgB,MAAK,KAAA,CAAK,EAAA,CAC7B;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,oBAACA,SAAW,QAAX,EACC,UAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC,sBAAsB;AAAA,QACtB,YAAY;AAAA,QACZ,WAAU;AAAA,QAEV,UAAA;AAAA,UAACA,SAAW;AAAA,UAAX;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,oBAACA,SAAW,MAAX,EACE,sBACC,OAAO,IAAI,CAAC,SACV;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,OAAO,KAAK;AAAA,gBACZ,UAAU,KAAK;AAAA,gBAEd,UAAA,KAAK;AAAA,cAAA;AAAA,cAJD,OAAO,KAAK,KAAK;AAAA,YAAA,CAMzB,EAAA,CACL;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GACF;AAEJ;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAACA,SAAW,UAAX,EAAoB,WAAU,2BAC5B,UACH;AAAA,QACA,oBAACA,SAAW,eAAX,EAAyB,WAAU,iCAClC,UAAA,oBAAC,WAAA,EAAU,MAAK,KAAA,CAAK,EAAA,CACvB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"select.js","sources":["../../../src/components/select/select.tsx"],"sourcesContent":["import { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { type ReactNode, type Ref, useLayoutEffect } from \"react\";\n\nimport { CheckIcon, ChevronDownIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useFieldContext } from \"../field/field-context\";\nimport {\n type ControlSize,\n controlBox,\n controlHeight,\n controlIcon,\n controlRow,\n triggerControlStates,\n} from \"../input/control-styles\";\n\nexport interface SelectOption<Value> {\n value: Value;\n label: ReactNode;\n disabled?: boolean;\n}\n\nexport interface SelectProps<Value>\n extends Omit<\n BaseSelect.Root.Props<Value, false>,\n \"multiple\" | \"items\" | \"children\"\n > {\n /** The options. Also lets the trigger show the label of the chosen value. */\n items?: readonly SelectOption<Value>[];\n /** Custom options (`SelectItem`) instead of `items`. */\n children?: ReactNode;\n /** Shown in text-secondary while nothing is chosen: \"Elige una opción\". */\n placeholder?: ReactNode;\n /** `default` 40px, `small` 36px: the same heights as Input and Button. */\n size?: ControlSize;\n /** Red 2px border and `aria-invalid`. The message goes under it, in `Field`. */\n invalid?: boolean;\n /** Content icon before the value. The chevron is structure, not a prop. */\n leadingIcon?: ReactNode;\n /** Applied to the trigger. */\n className?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * Dropdown with a styled list (Base UI Select). The trigger has exactly the geometry\n * of Input, so a field and a select in the same row only differ by the chevron.\n *\n * The open list is not in Figma on purpose. Prefer `NativeSelect` on mobile and\n * whenever the options are plain text: it opens the operating system's picker and\n * is accessible out of the box. Use this one on desktop screens (the clinics admin)\n * where the list has to look like MediCore. Options that an `<option>` can't draw\n * (a doctor with photo and licence number) are a `Menu`, not this.\n *\n * Name it with `Field`'s label, or with `aria-label` when there is no visible label.\n */\nexport function Select<Value>({\n items,\n children,\n placeholder,\n size = \"default\",\n invalid = false,\n leadingIcon,\n className,\n disabled,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ref,\n ...props\n}: SelectProps<Value>): React.JSX.Element {\n const setNativeLabel = useFieldContext()?.setNativeLabel;\n\n // A native <label> around a button would open the list on click: Field renders a\n // non-native label while a Select is inside.\n useLayoutEffect(() => {\n if (!setNativeLabel) {\n return undefined;\n }\n setNativeLabel(false);\n return () => setNativeLabel(true);\n }, [setNativeLabel]);\n\n return (\n <BaseSelect.Root items={items} disabled={disabled} {...props}>\n <BaseSelect.Trigger\n ref={ref}\n data-slot=\"select\"\n // Only when given: an explicit undefined would override Field's aria-labelledby.\n {...(ariaLabel !== undefined && { \"aria-label\": ariaLabel })}\n {...(ariaLabelledBy !== undefined && {\n \"aria-labelledby\": ariaLabelledBy,\n })}\n aria-describedby={ariaDescribedBy}\n aria-invalid={invalid && !disabled ? true : undefined}\n className={cn(\n \"group/control cursor-pointer text-start\",\n controlBox,\n controlRow,\n controlHeight[size],\n triggerControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseSelect.Value\n placeholder={placeholder}\n className=\"min-w-0 flex-1 truncate data-placeholder:text-text-secondary group-data-disabled/control:text-text-disabled\"\n />\n <BaseSelect.Icon className=\"flex shrink-0 text-text-primary group-data-disabled/control:text-text-disabled\">\n <ChevronDownIcon size=\"sm\" />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n alignItemWithTrigger={false}\n sideOffset={4}\n className=\"z-50 outline-none\"\n >\n <BaseSelect.Popup\n data-slot=\"select-popup\"\n className={cn(\n // Same surface as Menu: shadow-md to lift it and a border for when the\n // shadow doesn't show (dark backgrounds, poor screens).\n \"max-h-[min(20rem,var(--available-height))] min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto\",\n \"rounded-xl border border-border-subtle bg-background p-1.5 shadow-lg outline-none\",\n \"transition-[opacity,scale] duration-150 motion-reduce:transition-none\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0\",\n )}\n >\n <BaseSelect.List>\n {children ??\n items?.map((item) => (\n <SelectItem\n key={String(item.value)}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </SelectItem>\n ))}\n </BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n </BaseSelect.Root>\n );\n}\n\nexport interface SelectItemProps\n extends Omit<BaseSelect.Item.Props, \"className\"> {\n className?: string;\n}\n\n/**\n * One option of `Select`: 40px rows like MenuItem (a list is used with a finger on\n * it), surface on hover / arrow keys, a check on the chosen one.\n */\nexport function SelectItem({\n className,\n children,\n ...props\n}: SelectItemProps): React.JSX.Element {\n return (\n <BaseSelect.Item\n data-slot=\"select-item\"\n className={cn(\n \"flex h-control-md cursor-pointer select-none items-center gap-3 rounded-lg px-3 text-label-default text-text-primary outline-none\",\n \"data-highlighted:bg-surface focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset\",\n \"data-disabled:cursor-not-allowed data-disabled:text-text-disabled\",\n className,\n )}\n {...props}\n >\n <BaseSelect.ItemText className=\"min-w-0 flex-1 truncate\">\n {children}\n </BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className=\"flex shrink-0 text-text-brand\">\n <CheckIcon size=\"sm\" />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n"],"names":["BaseSelect"],"mappings":";;;;;;;;AA0DO,SAAS,OAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB;AAAA,EACA,GAAG;AACL,GAA0C;AACxC,QAAM,iBAAiB,mBAAmB;AAI1C,kBAAgB,MAAM;AACpB,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,IACT;AACA,mBAAe,KAAK;AACpB,WAAO,MAAM,eAAe,IAAI;AAAA,EAClC,GAAG,CAAC,cAAc,CAAC;AAEnB,8BACGA,SAAW,MAAX,EAAgB,OAAc,UAAqB,GAAG,OACrD,UAAA;AAAA,IAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QAET,GAAI,cAAc,UAAa,EAAE,cAAc,UAAA;AAAA,QAC/C,GAAI,mBAAmB,UAAa;AAAA,UACnC,mBAAmB;AAAA,QAAA;AAAA,QAErB,oBAAkB;AAAA,QAClB,gBAAc,WAAW,CAAC,WAAW,OAAO;AAAA,QAC5C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc,IAAI;AAAA,UAClB;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,eAAe,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,aAAY;AAAA,UAC3D;AAAA,YAACA,SAAW;AAAA,YAAX;AAAA,cACC;AAAA,cACA,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAEZ,oBAACA,SAAW,MAAX,EAAgB,WAAU,kFACzB,UAAA,oBAAC,iBAAA,EAAgB,MAAK,KAAA,CAAK,EAAA,CAC7B;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,oBAACA,SAAW,QAAX,EACC,UAAA;AAAA,MAACA,SAAW;AAAA,MAAX;AAAA,QACC,sBAAsB;AAAA,QACtB,YAAY;AAAA,QACZ,WAAU;AAAA,QAEV,UAAA;AAAA,UAACA,SAAW;AAAA,UAAX;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,oBAACA,SAAW,MAAX,EACE,sBACC,OAAO,IAAI,CAAC,SACV;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,OAAO,KAAK;AAAA,gBACZ,UAAU,KAAK;AAAA,gBAEd,UAAA,KAAK;AAAA,cAAA;AAAA,cAJD,OAAO,KAAK,KAAK;AAAA,YAAA,CAMzB,EAAA,CACL;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GACF;AAEJ;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuC;AACrC,SACE;AAAA,IAACA,SAAW;AAAA,IAAX;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAACA,SAAW,UAAX,EAAoB,WAAU,2BAC5B,UACH;AAAA,QACA,oBAACA,SAAW,eAAX,EAAyB,WAAU,iCAClC,UAAA,oBAAC,WAAA,EAAU,MAAK,KAAA,CAAK,EAAA,CACvB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mediquo/medicore",
3
3
  "description": "MediCore — design system de las plataformas web de mediQuo",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "sideEffects": [