@mediquo/medicore 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/card-program/card-program.js +38 -11
- package/dist/components/card-program/card-program.js.map +1 -1
- package/dist/components/input/control-styles.d.ts +6 -0
- package/dist/components/input/control-styles.js +5 -0
- package/dist/components/input/control-styles.js.map +1 -1
- package/dist/components/input/input.js +3 -2
- package/dist/components/input/input.js.map +1 -1
- package/dist/styles/utilities.css +10 -2
- package/package.json +1 -1
|
@@ -63,7 +63,7 @@ function CardProgram({
|
|
|
63
63
|
"data-slot": "card-program",
|
|
64
64
|
"data-layout": "featured",
|
|
65
65
|
className: cn(
|
|
66
|
-
"@container relative min-w-0 rounded-xl bg-background shadow-s interactive hover-lift hover-zoom-media",
|
|
66
|
+
"@container relative min-w-0 rounded-xl bg-background shadow-s interactive hover-lift hover-scale hover-zoom-media",
|
|
67
67
|
className
|
|
68
68
|
),
|
|
69
69
|
...props,
|
|
@@ -97,7 +97,7 @@ function CardProgram({
|
|
|
97
97
|
"data-slot": "card-program",
|
|
98
98
|
"data-layout": "grid",
|
|
99
99
|
className: cn(
|
|
100
|
-
"@container relative flex min-w-0 flex-col rounded-xl bg-background shadow-s interactive hover-lift hover-zoom-media",
|
|
100
|
+
"@container relative flex min-w-0 flex-col rounded-xl bg-background shadow-s interactive hover-lift hover-scale hover-zoom-media",
|
|
101
101
|
className
|
|
102
102
|
),
|
|
103
103
|
...props,
|
|
@@ -122,18 +122,45 @@ function CardProgram({
|
|
|
122
122
|
description && /* @__PURE__ */ jsx("p", { className: "line-clamp-3 hidden text-body-small text-text-secondary @min-[240px]:block", children: description }),
|
|
123
123
|
/* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center gap-2", children: [
|
|
124
124
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: priceRow }),
|
|
125
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ jsxs(
|
|
126
126
|
"span",
|
|
127
127
|
{
|
|
128
128
|
"aria-hidden": true,
|
|
129
|
-
className:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
className: cn(
|
|
130
|
+
"relative hidden size-7 shrink-0 items-center justify-center @min-[240px]:flex",
|
|
131
|
+
"text-text-inverse interactive hover-triggered:text-text-primary"
|
|
132
|
+
),
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"svg",
|
|
136
|
+
{
|
|
137
|
+
"aria-hidden": "true",
|
|
138
|
+
viewBox: "0 0 28 28",
|
|
139
|
+
className: "absolute inset-0 size-full overflow-visible",
|
|
140
|
+
children: /* @__PURE__ */ jsx(
|
|
141
|
+
"circle",
|
|
142
|
+
{
|
|
143
|
+
cx: "14",
|
|
144
|
+
cy: "14",
|
|
145
|
+
r: "13.5",
|
|
146
|
+
strokeWidth: "1",
|
|
147
|
+
className: cn(
|
|
148
|
+
"fill-brand stroke-transparent",
|
|
149
|
+
"transition-[fill,stroke] duration-(--duration-interactive) ease-out-soft",
|
|
150
|
+
"hover-triggered:fill-transparent hover-triggered:stroke-text-primary"
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
/* @__PURE__ */ jsx(
|
|
157
|
+
ArrowNarrowRightIcon,
|
|
158
|
+
{
|
|
159
|
+
size: "sm",
|
|
160
|
+
className: "relative interactive motion-safe:hover-triggered:translate-x-0.5"
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
]
|
|
137
164
|
}
|
|
138
165
|
)
|
|
139
166
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card-program.js","sources":["../../../src/components/card-program/card-program.tsx"],"sourcesContent":["import type { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { ArrowNarrowRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { Badge } from \"../badge\";\nimport { type CardHeadingLevel, CardLink } from \"../card\";\n\nexport type CardProgramLayout = \"grid\" | \"featured\";\n\nexport interface CardProgramProps\n extends Omit<ComponentProps<\"article\">, \"title\"> {\n /** \"Pérdida de peso\". The card's heading and the link's name. */\n title: ReactNode;\n /** One or two lines. Hidden in the compact (Mobile) card. */\n description?: ReactNode;\n /**\n * The live price of THIS clinic's page, es-ES format with a space: \"Desde 30 €\",\n * \"29,99 €\". Never another clinic's price.\n */\n price: ReactNode;\n /** \"Videoconsulta\". Shown after the price with a \"·\". Hidden in the compact card. */\n channel?: ReactNode;\n /** A real photo from mediQuo's image line: `<img src=\"…\" alt=\"\" />` if it adds nothing. */\n image?: ReactNode;\n /** \"Tratamiento destacado\": a brand Badge (on the image in Grid, above the title in Featured). */\n badge?: ReactNode;\n /** Featured only: a metric Badge next to `badge` (\"4,6/5 valoración\"). */\n metric?: ReactNode;\n /** The programme's page. The whole card is this link. */\n href?: string;\n /** Router link: `render={<Link to=\"…\" />}`. */\n render?: useRender.RenderProp;\n /** Featured only: the one action, a primary `Button` \"Reservar consulta\" (never \"Empezar tratamiento\"). */\n action?: ReactNode;\n /**\n * `grid` (default): the three-column grid; below 240px wide it becomes the compact\n * Mobile card (two-column grid on a phone) by itself. `featured`: the highlighted\n * programme, horizontal from 640px wide.\n */\n layout?: CardProgramLayout;\n /** Level of the title's heading. Default 3. */\n headingLevel?: CardHeadingLevel;\n}\n\nfunction ImageSlot({\n image,\n className,\n children,\n}: {\n image?: ReactNode;\n className?: string;\n children?: ReactNode;\n}): React.JSX.Element {\n return (\n <div\n className={cn(\n \"relative shrink-0 overflow-hidden bg-brand-subtle [&>img]:size-full [&>img]:object-cover\",\n className,\n )}\n >\n {image}\n {children}\n </div>\n );\n}\n\n/**\n * A specialised programme (Clínic). The whole card is the link to the programme's\n * page (a `CardLink` in the title); the arrow is decorative. One action.\n *\n * Figma `Layout=Mobile` is not a value: the `grid` card turns compact (no\n * description, no channel, no arrow) when its column is narrower than 240px.\n */\nexport function CardProgram({\n title,\n description,\n price,\n channel,\n image,\n badge,\n metric,\n href,\n render,\n action,\n layout = \"grid\",\n headingLevel = 3,\n className,\n ...props\n}: CardProgramProps): React.JSX.Element {\n const Heading = `h${headingLevel}` as const;\n const heading =\n href || render ? (\n <CardLink href={href} render={render}>\n {title}\n </CardLink>\n ) : (\n title\n );\n\n const priceRow = (\n <p className=\"flex min-w-0 flex-wrap items-baseline gap-1\">\n <span className=\"text-label-strong text-text-primary\">{price}</span>\n {channel && (\n <span\n className={cn(\n \"text-body-small text-text-secondary\",\n layout === \"grid\" && \"hidden @min-[240px]:inline\",\n )}\n >\n · {channel}\n </span>\n )}\n </p>\n );\n\n if (layout === \"featured\") {\n return (\n <article\n data-slot=\"card-program\"\n data-layout=\"featured\"\n className={cn(\n \"@container relative min-w-0 rounded-xl bg-background shadow-s interactive hover-lift hover-zoom-media\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-col @min-[640px]:flex-row\">\n <ImageSlot\n image={image}\n className=\"h-40 rounded-t-xl @min-[640px]:h-auto @min-[640px]:min-h-60 @min-[640px]:w-1/2 @min-[640px]:rounded-tr-none @min-[640px]:rounded-l-xl\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col justify-center gap-4 p-6 @min-[640px]:p-8\">\n {(badge || metric) && (\n <div className=\"flex flex-wrap gap-2\">\n {badge && <Badge variant=\"brand\">{badge}</Badge>}\n {metric && <Badge>{metric}</Badge>}\n </div>\n )}\n <Heading className=\"text-heading-h2 text-text-primary\">\n {heading}\n </Heading>\n {description && (\n <p className=\"text-body-small text-text-secondary\">\n {description}\n </p>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-4\">\n {priceRow}\n {action && <div className=\"relative z-10 flex\">{action}</div>}\n </div>\n </div>\n </div>\n </article>\n );\n }\n\n return (\n <article\n data-slot=\"card-program\"\n data-layout=\"grid\"\n className={cn(\n \"@container relative flex min-w-0 flex-col rounded-xl bg-background shadow-s interactive hover-lift hover-zoom-media\",\n className,\n )}\n {...props}\n >\n <ImageSlot\n image={image}\n className=\"h-27.5 rounded-t-xl @min-[240px]:h-40\"\n >\n {badge && (\n <Badge\n variant=\"brand\"\n className=\"absolute top-3 left-3 hidden @min-[240px]:inline-flex\"\n >\n {badge}\n </Badge>\n )}\n </ImageSlot>\n <div className=\"flex flex-1 flex-col gap-2 p-3 @min-[240px]:p-4\">\n <Heading className=\"text-label-strong text-text-primary @min-[240px]:text-heading-h4\">\n {heading}\n </Heading>\n {description && (\n <p className=\"line-clamp-3 hidden text-body-small text-text-secondary @min-[240px]:block\">\n {description}\n </p>\n )}\n <div className=\"mt-auto flex items-center gap-2\">\n <div className=\"min-w-0 flex-1\">{priceRow}</div>\n <span\n aria-hidden\n className=\"hidden size-7 shrink-0 items-center justify-center rounded-full bg-brand text-text-inverse @min-[240px]:flex\"\n >\n <ArrowNarrowRightIcon\n size=\"sm\"\n className=\"interactive motion-safe:hover-triggered:translate-x-0.5\"\n />\n </span>\n </div>\n </div>\n </article>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"card-program.js","sources":["../../../src/components/card-program/card-program.tsx"],"sourcesContent":["import type { useRender } from \"@base-ui/react/use-render\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { ArrowNarrowRightIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { Badge } from \"../badge\";\nimport { type CardHeadingLevel, CardLink } from \"../card\";\n\nexport type CardProgramLayout = \"grid\" | \"featured\";\n\nexport interface CardProgramProps\n extends Omit<ComponentProps<\"article\">, \"title\"> {\n /** \"Pérdida de peso\". The card's heading and the link's name. */\n title: ReactNode;\n /** One or two lines. Hidden in the compact (Mobile) card. */\n description?: ReactNode;\n /**\n * The live price of THIS clinic's page, es-ES format with a space: \"Desde 30 €\",\n * \"29,99 €\". Never another clinic's price.\n */\n price: ReactNode;\n /** \"Videoconsulta\". Shown after the price with a \"·\". Hidden in the compact card. */\n channel?: ReactNode;\n /** A real photo from mediQuo's image line: `<img src=\"…\" alt=\"\" />` if it adds nothing. */\n image?: ReactNode;\n /** \"Tratamiento destacado\": a brand Badge (on the image in Grid, above the title in Featured). */\n badge?: ReactNode;\n /** Featured only: a metric Badge next to `badge` (\"4,6/5 valoración\"). */\n metric?: ReactNode;\n /** The programme's page. The whole card is this link. */\n href?: string;\n /** Router link: `render={<Link to=\"…\" />}`. */\n render?: useRender.RenderProp;\n /** Featured only: the one action, a primary `Button` \"Reservar consulta\" (never \"Empezar tratamiento\"). */\n action?: ReactNode;\n /**\n * `grid` (default): the three-column grid; below 240px wide it becomes the compact\n * Mobile card (two-column grid on a phone) by itself. `featured`: the highlighted\n * programme, horizontal from 640px wide.\n */\n layout?: CardProgramLayout;\n /** Level of the title's heading. Default 3. */\n headingLevel?: CardHeadingLevel;\n}\n\nfunction ImageSlot({\n image,\n className,\n children,\n}: {\n image?: ReactNode;\n className?: string;\n children?: ReactNode;\n}): React.JSX.Element {\n return (\n <div\n className={cn(\n \"relative shrink-0 overflow-hidden bg-brand-subtle [&>img]:size-full [&>img]:object-cover\",\n className,\n )}\n >\n {image}\n {children}\n </div>\n );\n}\n\n/**\n * A specialised programme (Clínic). The whole card is the link to the programme's\n * page (a `CardLink` in the title); the arrow is decorative. One action.\n *\n * Figma `Layout=Mobile` is not a value: the `grid` card turns compact (no\n * description, no channel, no arrow) when its column is narrower than 240px.\n */\nexport function CardProgram({\n title,\n description,\n price,\n channel,\n image,\n badge,\n metric,\n href,\n render,\n action,\n layout = \"grid\",\n headingLevel = 3,\n className,\n ...props\n}: CardProgramProps): React.JSX.Element {\n const Heading = `h${headingLevel}` as const;\n const heading =\n href || render ? (\n <CardLink href={href} render={render}>\n {title}\n </CardLink>\n ) : (\n title\n );\n\n const priceRow = (\n <p className=\"flex min-w-0 flex-wrap items-baseline gap-1\">\n <span className=\"text-label-strong text-text-primary\">{price}</span>\n {channel && (\n <span\n className={cn(\n \"text-body-small text-text-secondary\",\n layout === \"grid\" && \"hidden @min-[240px]:inline\",\n )}\n >\n · {channel}\n </span>\n )}\n </p>\n );\n\n if (layout === \"featured\") {\n return (\n <article\n data-slot=\"card-program\"\n data-layout=\"featured\"\n className={cn(\n \"@container relative min-w-0 rounded-xl bg-background shadow-s interactive hover-lift hover-scale hover-zoom-media\",\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-col @min-[640px]:flex-row\">\n <ImageSlot\n image={image}\n className=\"h-40 rounded-t-xl @min-[640px]:h-auto @min-[640px]:min-h-60 @min-[640px]:w-1/2 @min-[640px]:rounded-tr-none @min-[640px]:rounded-l-xl\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col justify-center gap-4 p-6 @min-[640px]:p-8\">\n {(badge || metric) && (\n <div className=\"flex flex-wrap gap-2\">\n {badge && <Badge variant=\"brand\">{badge}</Badge>}\n {metric && <Badge>{metric}</Badge>}\n </div>\n )}\n <Heading className=\"text-heading-h2 text-text-primary\">\n {heading}\n </Heading>\n {description && (\n <p className=\"text-body-small text-text-secondary\">\n {description}\n </p>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-4\">\n {priceRow}\n {action && <div className=\"relative z-10 flex\">{action}</div>}\n </div>\n </div>\n </div>\n </article>\n );\n }\n\n return (\n <article\n data-slot=\"card-program\"\n data-layout=\"grid\"\n className={cn(\n \"@container relative flex min-w-0 flex-col rounded-xl bg-background shadow-s interactive hover-lift hover-scale hover-zoom-media\",\n className,\n )}\n {...props}\n >\n <ImageSlot\n image={image}\n className=\"h-27.5 rounded-t-xl @min-[240px]:h-40\"\n >\n {badge && (\n <Badge\n variant=\"brand\"\n className=\"absolute top-3 left-3 hidden @min-[240px]:inline-flex\"\n >\n {badge}\n </Badge>\n )}\n </ImageSlot>\n <div className=\"flex flex-1 flex-col gap-2 p-3 @min-[240px]:p-4\">\n <Heading className=\"text-label-strong text-text-primary @min-[240px]:text-heading-h4\">\n {heading}\n </Heading>\n {description && (\n <p className=\"line-clamp-3 hidden text-body-small text-text-secondary @min-[240px]:block\">\n {description}\n </p>\n )}\n <div className=\"mt-auto flex items-center gap-2\">\n <div className=\"min-w-0 flex-1\">{priceRow}</div>\n <span\n aria-hidden\n className={cn(\n \"relative hidden size-7 shrink-0 items-center justify-center @min-[240px]:flex\",\n \"text-text-inverse interactive hover-triggered:text-text-primary\",\n )}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 28 28\"\n className=\"absolute inset-0 size-full overflow-visible\"\n >\n <circle\n cx=\"14\"\n cy=\"14\"\n r=\"13.5\"\n strokeWidth=\"1\"\n className={cn(\n \"fill-brand stroke-transparent\",\n \"transition-[fill,stroke] duration-(--duration-interactive) ease-out-soft\",\n \"hover-triggered:fill-transparent hover-triggered:stroke-text-primary\",\n )}\n />\n </svg>\n <ArrowNarrowRightIcon\n size=\"sm\"\n className=\"relative interactive motion-safe:hover-triggered:translate-x-0.5\"\n />\n </span>\n </div>\n </div>\n </article>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -32,3 +32,9 @@ export declare const controlPlaceholder = "placeholder:text-text-secondary disab
|
|
|
32
32
|
export declare const controlIcon = "flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled";
|
|
33
33
|
/** Small icon button inside the box (reveal password, clear). 44px hit area. */
|
|
34
34
|
export declare const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
|
|
35
|
+
/**
|
|
36
|
+
* Browser autofill paints its own yellow or blue fill over the control. An inset
|
|
37
|
+
* shadow in the box's colour covers it and the text keeps the field's colour, so a
|
|
38
|
+
* saved email and password look like typed ones.
|
|
39
|
+
*/
|
|
40
|
+
export declare const controlAutofill: string;
|
|
@@ -22,8 +22,13 @@ const triggerControlStates = [
|
|
|
22
22
|
const controlPlaceholder = "placeholder:text-text-secondary disabled:placeholder:text-text-disabled";
|
|
23
23
|
const controlIcon = "flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled";
|
|
24
24
|
const controlAction = "focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm";
|
|
25
|
+
const controlAutofill = [
|
|
26
|
+
"autofill:shadow-[inset_0_0_0_100vmax_var(--color-background)] autofill:[-webkit-text-fill-color:var(--color-text-primary)] autofill:caret-text-primary",
|
|
27
|
+
"autofill:disabled:shadow-[inset_0_0_0_100vmax_var(--color-surface)] autofill:disabled:[-webkit-text-fill-color:var(--color-text-disabled)]"
|
|
28
|
+
].join(" ");
|
|
25
29
|
export {
|
|
26
30
|
controlAction,
|
|
31
|
+
controlAutofill,
|
|
27
32
|
controlBox,
|
|
28
33
|
controlHeight,
|
|
29
34
|
controlIcon,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-styles.js","sources":["../../../src/components/input/control-styles.ts"],"sourcesContent":["/*\n * The box shared by Input, Textarea, Select, NativeSelect and PhoneInput. Figma draws\n * them as the same object on purpose: same radius, padding, border and states, so a\n * field and a dropdown in the same row only differ by the chevron.\n *\n * Focus and Error are a 2px border (border-thick), not just a colour change. The\n * second pixel is an inset ring, so the content never moves when the state changes.\n * Focus wins over Error: the message under the field keeps saying what is wrong.\n */\n\n/** Border, radius, fill and text colour of the box. */\nexport const controlBox =\n \"w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset interactive hover:border-border-strong\";\n\n/** One-line content: 12px side padding, 8px between icon and text, Label/Default (14). */\nexport const controlRow = \"flex items-center gap-2 px-3 text-label-default\";\n\n/** control-md (40) and control-sm (36): the same tokens as Button, so they line up. */\nexport const controlHeight = {\n default: \"h-control-md\",\n small: \"h-control-sm\",\n} as const;\n\nexport type ControlSize = keyof typeof controlHeight;\n\n/**\n * States for a box that WRAPS the real control (Input, NativeSelect, the phone\n * prefix). Read from the control inside: `aria-invalid` (set by `invalid` or by\n * Field), `:focus` and `:disabled` (of the input/select only: a native select's\n * placeholder `<option disabled>` must not grey the box out). Buttons inside the box\n * (reveal password, clear) keep their own focus ring and don't light the box up.\n */\nexport const wrappedControlStates = [\n \"has-[[aria-invalid=true]]:border-error has-[[aria-invalid=true]]:ring-error\",\n \"has-[:focus:not(button)]:border-border-focus has-[:focus:not(button)]:ring-border-focus\",\n \"has-[:is(input,select):disabled]:cursor-not-allowed has-[:is(input,select):disabled]:border-border-subtle has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled has-[:is(input,select):disabled]:shadow-none\",\n].join(\" \");\n\n/** States for a box that IS the control (Textarea). */\nexport const selfControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus\",\n \"aria-invalid:not-focus:border-error aria-invalid:not-focus:ring-error\",\n \"disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface disabled:text-text-disabled disabled:shadow-none\",\n].join(\" \");\n\n/**\n * States for a button box (Select trigger). It keeps the focus border while its popup\n * is open, because focus is inside the list then.\n */\nexport const triggerControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus data-popup-open:border-border-focus data-popup-open:ring-border-focus\",\n \"aria-invalid:not-focus:not-data-popup-open:border-error aria-invalid:not-focus:not-data-popup-open:ring-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n].join(\" \");\n\n/** Placeholder: the same Value node in text-secondary, never a variant. */\nexport const controlPlaceholder =\n \"placeholder:text-text-secondary disabled:placeholder:text-text-disabled\";\n\n/**\n * Leading / trailing icon slot: 16px in text-secondary (5.07:1), disabled with the\n * box. The box carries `group/control`.\n */\nexport const controlIcon =\n \"flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled\";\n\n/** Small icon button inside the box (reveal password, clear). 44px hit area. */\nexport const controlAction =\n \"focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm\";\n"],"names":[],"mappings":"AAWO,MAAM,aACX;AAGK,MAAM,aAAa;AAGnB,MAAM,gBAAgB;AAAA,EAC3B,SAAS;AAAA,EACT,OAAO;AACT;AAWO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAMH,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,qBACX;AAMK,MAAM,cACX;AAGK,MAAM,gBACX;"}
|
|
1
|
+
{"version":3,"file":"control-styles.js","sources":["../../../src/components/input/control-styles.ts"],"sourcesContent":["/*\n * The box shared by Input, Textarea, Select, NativeSelect and PhoneInput. Figma draws\n * them as the same object on purpose: same radius, padding, border and states, so a\n * field and a dropdown in the same row only differ by the chevron.\n *\n * Focus and Error are a 2px border (border-thick), not just a colour change. The\n * second pixel is an inset ring, so the content never moves when the state changes.\n * Focus wins over Error: the message under the field keeps saying what is wrong.\n */\n\n/** Border, radius, fill and text colour of the box. */\nexport const controlBox =\n \"w-full min-w-0 rounded-md border border-border bg-background text-text-primary shadow-xs ring-1 ring-transparent ring-inset interactive hover:border-border-strong\";\n\n/** One-line content: 12px side padding, 8px between icon and text, Label/Default (14). */\nexport const controlRow = \"flex items-center gap-2 px-3 text-label-default\";\n\n/** control-md (40) and control-sm (36): the same tokens as Button, so they line up. */\nexport const controlHeight = {\n default: \"h-control-md\",\n small: \"h-control-sm\",\n} as const;\n\nexport type ControlSize = keyof typeof controlHeight;\n\n/**\n * States for a box that WRAPS the real control (Input, NativeSelect, the phone\n * prefix). Read from the control inside: `aria-invalid` (set by `invalid` or by\n * Field), `:focus` and `:disabled` (of the input/select only: a native select's\n * placeholder `<option disabled>` must not grey the box out). Buttons inside the box\n * (reveal password, clear) keep their own focus ring and don't light the box up.\n */\nexport const wrappedControlStates = [\n \"has-[[aria-invalid=true]]:border-error has-[[aria-invalid=true]]:ring-error\",\n \"has-[:focus:not(button)]:border-border-focus has-[:focus:not(button)]:ring-border-focus\",\n \"has-[:is(input,select):disabled]:cursor-not-allowed has-[:is(input,select):disabled]:border-border-subtle has-[:is(input,select):disabled]:bg-surface has-[:is(input,select):disabled]:text-text-disabled has-[:is(input,select):disabled]:shadow-none\",\n].join(\" \");\n\n/** States for a box that IS the control (Textarea). */\nexport const selfControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus\",\n \"aria-invalid:not-focus:border-error aria-invalid:not-focus:ring-error\",\n \"disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface disabled:text-text-disabled disabled:shadow-none\",\n].join(\" \");\n\n/**\n * States for a button box (Select trigger). It keeps the focus border while its popup\n * is open, because focus is inside the list then.\n */\nexport const triggerControlStates = [\n \"outline-none focus:border-border-focus focus:ring-border-focus data-popup-open:border-border-focus data-popup-open:ring-border-focus\",\n \"aria-invalid:not-focus:not-data-popup-open:border-error aria-invalid:not-focus:not-data-popup-open:ring-error\",\n \"data-disabled:cursor-not-allowed data-disabled:border-border-subtle data-disabled:bg-surface data-disabled:text-text-disabled data-disabled:shadow-none\",\n].join(\" \");\n\n/** Placeholder: the same Value node in text-secondary, never a variant. */\nexport const controlPlaceholder =\n \"placeholder:text-text-secondary disabled:placeholder:text-text-disabled\";\n\n/**\n * Leading / trailing icon slot: 16px in text-secondary (5.07:1), disabled with the\n * box. The box carries `group/control`.\n */\nexport const controlIcon =\n \"flex shrink-0 text-text-secondary [&_svg]:size-icon-sm group-has-[:is(input,select):disabled]/control:text-text-disabled group-data-disabled/control:text-text-disabled\";\n\n/** Small icon button inside the box (reveal password, clear). 44px hit area. */\nexport const controlAction =\n \"focus-ring touch-target -me-1 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-text-secondary interactive hover:text-text-primary disabled:cursor-not-allowed disabled:text-text-disabled [&_svg]:size-icon-sm\";\n\n/**\n * Browser autofill paints its own yellow or blue fill over the control. An inset\n * shadow in the box's colour covers it and the text keeps the field's colour, so a\n * saved email and password look like typed ones.\n */\nexport const controlAutofill = [\n \"autofill:shadow-[inset_0_0_0_100vmax_var(--color-background)] autofill:[-webkit-text-fill-color:var(--color-text-primary)] autofill:caret-text-primary\",\n \"autofill:disabled:shadow-[inset_0_0_0_100vmax_var(--color-surface)] autofill:disabled:[-webkit-text-fill-color:var(--color-text-disabled)]\",\n].join(\" \");\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;AAOK,MAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF,EAAE,KAAK,GAAG;"}
|
|
@@ -5,7 +5,7 @@ import { EyeIcon } from "../../icons/generated/eye.js";
|
|
|
5
5
|
import { EyeOffIcon } from "../../icons/generated/eye-off.js";
|
|
6
6
|
import { XCloseIcon } from "../../icons/generated/x-close.js";
|
|
7
7
|
import { cn } from "../../lib/cn.js";
|
|
8
|
-
import { controlIcon, controlPlaceholder, controlAction, wrappedControlStates, controlHeight, controlRow, controlBox } from "./control-styles.js";
|
|
8
|
+
import { controlIcon, controlAutofill, controlPlaceholder, controlAction, wrappedControlStates, controlHeight, controlRow, controlBox } from "./control-styles.js";
|
|
9
9
|
import { useMediCoreLabels } from "../../provider/medicore-provider.js";
|
|
10
10
|
function Input({
|
|
11
11
|
size = "default",
|
|
@@ -86,7 +86,8 @@ function Input({
|
|
|
86
86
|
},
|
|
87
87
|
className: cn(
|
|
88
88
|
"h-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed",
|
|
89
|
-
controlPlaceholder
|
|
89
|
+
controlPlaceholder,
|
|
90
|
+
controlAutofill
|
|
90
91
|
),
|
|
91
92
|
...props
|
|
92
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sources":["../../../src/components/input/input.tsx"],"sourcesContent":["import { Input as BaseInput } from \"@base-ui/react/input\";\nimport { type ReactNode, type Ref, useCallback, useRef, useState } from \"react\";\n\nimport { EyeIcon, EyeOffIcon, XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport {\n type ControlSize,\n controlAction,\n controlBox,\n controlHeight,\n controlIcon,\n controlPlaceholder,\n controlRow,\n wrappedControlStates,\n} from \"./control-styles\";\n\nexport interface InputProps\n extends Omit<BaseInput.Props, \"className\" | \"size\" | \"ref\"> {\n ref?: Ref<HTMLInputElement>;\n /** `default` 40px (control-md), `small` 36px (control-sm): the same heights as Button. */\n size?: ControlSize;\n /**\n * Red 2px border and `aria-invalid`. Never on its own: the message that says what\n * is wrong goes under the field (use `Field` with `error`, which sets this for you).\n */\n invalid?: boolean;\n /** Decorative 16px icon before the text: `SearchMdIcon`, `CalendarIcon`, `User01Icon`. */\n leadingIcon?: ReactNode;\n /** Decorative 16px icon after the text: `CalendarIcon`, `AlertCircleIcon`. */\n trailingIcon?: ReactNode;\n /** Shows an × button (\"Borrar\") while there is text. It fires `onChange` with \"\". */\n clearable?: boolean;\n /** Applied to the box, not to the `<input>`: use it for the width. */\n className?: string;\n}\n\n/**\n * One-line text field. Only the box: the label, the help text and the error go\n * OUTSIDE, in `Field`, so the message can grow to two lines.\n *\n * - It fills its container (Figma draws it at 280 as a reference). Constrain it with\n * `className=\"max-w-xs\"` or with the layout.\n * - Placeholder in text-secondary, value in text-primary. The placeholder is an\n * example, never the label.\n * - `type=\"password\"` adds the reveal button (eye / eye-off) with its accessible name.\n * - Ask the browser for help: `type=\"email\"`, `inputMode=\"numeric\"`,\n * `autoComplete=\"given-name\"`… The keyboard and autofill depend on it.\n * - Inside `Field` the label, description and `aria-invalid` wire themselves.\n */\nexport function Input({\n size = \"default\",\n invalid = false,\n leadingIcon,\n trailingIcon,\n clearable = false,\n className,\n type = \"text\",\n disabled,\n readOnly,\n value,\n defaultValue,\n onChange,\n ref,\n ...props\n}: InputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [revealed, setRevealed] = useState(false);\n const [uncontrolledFilled, setUncontrolledFilled] = useState(\n () => defaultValue != null && String(defaultValue) !== \"\",\n );\n const filled =\n value === undefined\n ? uncontrolledFilled\n : value !== null && String(value) !== \"\";\n const isPassword = type === \"password\";\n\n const setRefs = useCallback(\n (node: HTMLElement | null) => {\n const input = node as HTMLInputElement | null;\n inputRef.current = input;\n if (typeof ref === \"function\") {\n ref(input);\n } else if (ref) {\n ref.current = input;\n }\n },\n [ref],\n );\n\n function clear(): void {\n const input = inputRef.current;\n if (!input) {\n return;\n }\n // Through the native setter and a real input event, so onChange / onValueChange\n // fire exactly as if the person had deleted the text (controlled or not).\n Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n \"value\",\n )?.set?.call(input, \"\");\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.focus();\n }\n\n return (\n <div\n data-slot=\"input\"\n className={cn(\n \"group/control\",\n controlBox,\n controlRow,\n controlHeight[size],\n wrappedControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseInput\n ref={setRefs}\n data-slot=\"input-control\"\n type={isPassword && revealed ? \"text\" : type}\n disabled={disabled}\n readOnly={readOnly}\n value={value}\n defaultValue={defaultValue}\n aria-invalid={invalid && !disabled ? true : undefined}\n onChange={(event) => {\n setUncontrolledFilled(event.currentTarget.value !== \"\");\n onChange?.(event);\n }}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed\",\n controlPlaceholder,\n )}\n {...props}\n />\n {clearable && filled && !disabled && !readOnly && (\n <button\n type=\"button\"\n aria-label={labels.clear}\n className={controlAction}\n onClick={clear}\n >\n <XCloseIcon size=\"sm\" />\n </button>\n )}\n {trailingIcon && <span className={controlIcon}>{trailingIcon}</span>}\n {isPassword && (\n <button\n type=\"button\"\n aria-label={revealed ? labels.hidePassword : labels.showPassword}\n disabled={disabled}\n className={controlAction}\n onClick={() => setRevealed((current) => !current)}\n >\n {revealed ? <EyeOffIcon size=\"sm\" /> : <EyeIcon size=\"sm\" />}\n </button>\n )}\n </div>\n );\n}\n"],"names":["BaseInput"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"input.js","sources":["../../../src/components/input/input.tsx"],"sourcesContent":["import { Input as BaseInput } from \"@base-ui/react/input\";\nimport { type ReactNode, type Ref, useCallback, useRef, useState } from \"react\";\n\nimport { EyeIcon, EyeOffIcon, XCloseIcon } from \"../../icons\";\nimport { cn } from \"../../lib/cn\";\nimport { useMediCoreLabels } from \"../../provider\";\nimport {\n type ControlSize,\n controlAction,\n controlAutofill,\n controlBox,\n controlHeight,\n controlIcon,\n controlPlaceholder,\n controlRow,\n wrappedControlStates,\n} from \"./control-styles\";\n\nexport interface InputProps\n extends Omit<BaseInput.Props, \"className\" | \"size\" | \"ref\"> {\n ref?: Ref<HTMLInputElement>;\n /** `default` 40px (control-md), `small` 36px (control-sm): the same heights as Button. */\n size?: ControlSize;\n /**\n * Red 2px border and `aria-invalid`. Never on its own: the message that says what\n * is wrong goes under the field (use `Field` with `error`, which sets this for you).\n */\n invalid?: boolean;\n /** Decorative 16px icon before the text: `SearchMdIcon`, `CalendarIcon`, `User01Icon`. */\n leadingIcon?: ReactNode;\n /** Decorative 16px icon after the text: `CalendarIcon`, `AlertCircleIcon`. */\n trailingIcon?: ReactNode;\n /** Shows an × button (\"Borrar\") while there is text. It fires `onChange` with \"\". */\n clearable?: boolean;\n /** Applied to the box, not to the `<input>`: use it for the width. */\n className?: string;\n}\n\n/**\n * One-line text field. Only the box: the label, the help text and the error go\n * OUTSIDE, in `Field`, so the message can grow to two lines.\n *\n * - It fills its container (Figma draws it at 280 as a reference). Constrain it with\n * `className=\"max-w-xs\"` or with the layout.\n * - Placeholder in text-secondary, value in text-primary. The placeholder is an\n * example, never the label.\n * - `type=\"password\"` adds the reveal button (eye / eye-off) with its accessible name.\n * - Ask the browser for help: `type=\"email\"`, `inputMode=\"numeric\"`,\n * `autoComplete=\"given-name\"`… The keyboard and autofill depend on it.\n * - Inside `Field` the label, description and `aria-invalid` wire themselves.\n */\nexport function Input({\n size = \"default\",\n invalid = false,\n leadingIcon,\n trailingIcon,\n clearable = false,\n className,\n type = \"text\",\n disabled,\n readOnly,\n value,\n defaultValue,\n onChange,\n ref,\n ...props\n}: InputProps): React.JSX.Element {\n const labels = useMediCoreLabels();\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [revealed, setRevealed] = useState(false);\n const [uncontrolledFilled, setUncontrolledFilled] = useState(\n () => defaultValue != null && String(defaultValue) !== \"\",\n );\n const filled =\n value === undefined\n ? uncontrolledFilled\n : value !== null && String(value) !== \"\";\n const isPassword = type === \"password\";\n\n const setRefs = useCallback(\n (node: HTMLElement | null) => {\n const input = node as HTMLInputElement | null;\n inputRef.current = input;\n if (typeof ref === \"function\") {\n ref(input);\n } else if (ref) {\n ref.current = input;\n }\n },\n [ref],\n );\n\n function clear(): void {\n const input = inputRef.current;\n if (!input) {\n return;\n }\n // Through the native setter and a real input event, so onChange / onValueChange\n // fire exactly as if the person had deleted the text (controlled or not).\n Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n \"value\",\n )?.set?.call(input, \"\");\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.focus();\n }\n\n return (\n <div\n data-slot=\"input\"\n className={cn(\n \"group/control\",\n controlBox,\n controlRow,\n controlHeight[size],\n wrappedControlStates,\n className,\n )}\n >\n {leadingIcon && <span className={controlIcon}>{leadingIcon}</span>}\n <BaseInput\n ref={setRefs}\n data-slot=\"input-control\"\n type={isPassword && revealed ? \"text\" : type}\n disabled={disabled}\n readOnly={readOnly}\n value={value}\n defaultValue={defaultValue}\n aria-invalid={invalid && !disabled ? true : undefined}\n onChange={(event) => {\n setUncontrolledFilled(event.currentTarget.value !== \"\");\n onChange?.(event);\n }}\n className={cn(\n \"h-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed\",\n controlPlaceholder,\n controlAutofill,\n )}\n {...props}\n />\n {clearable && filled && !disabled && !readOnly && (\n <button\n type=\"button\"\n aria-label={labels.clear}\n className={controlAction}\n onClick={clear}\n >\n <XCloseIcon size=\"sm\" />\n </button>\n )}\n {trailingIcon && <span className={controlIcon}>{trailingIcon}</span>}\n {isPassword && (\n <button\n type=\"button\"\n aria-label={revealed ? labels.hidePassword : labels.showPassword}\n disabled={disabled}\n className={controlAction}\n onClick={() => setRevealed((current) => !current)}\n >\n {revealed ? <EyeOffIcon size=\"sm\" /> : <EyeIcon size=\"sm\" />}\n </button>\n )}\n </div>\n );\n}\n"],"names":["BaseInput"],"mappings":";;;;;;;;;AAmDO,SAAS,MAAM;AAAA,EACpB,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkC;AAChC,QAAM,SAAS,kBAAA;AACf,QAAM,WAAW,OAAgC,IAAI;AACrD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,oBAAoB,qBAAqB,IAAI;AAAA,IAClD,MAAM,gBAAgB,QAAQ,OAAO,YAAY,MAAM;AAAA,EAAA;AAEzD,QAAM,SACJ,UAAU,SACN,qBACA,UAAU,QAAQ,OAAO,KAAK,MAAM;AAC1C,QAAM,aAAa,SAAS;AAE5B,QAAM,UAAU;AAAA,IACd,CAAC,SAA6B;AAC5B,YAAM,QAAQ;AACd,eAAS,UAAU;AACnB,UAAI,OAAO,QAAQ,YAAY;AAC7B,YAAI,KAAK;AAAA,MACX,WAAW,KAAK;AACd,YAAI,UAAU;AAAA,MAChB;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAGN,WAAS,QAAc;AACrB,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAGA,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB;AAAA,IAAA,GACC,KAAK,KAAK,OAAO,EAAE;AACtB,UAAM,cAAc,IAAI,MAAM,SAAS,EAAE,SAAS,KAAA,CAAM,CAAC;AACzD,UAAM,MAAA;AAAA,EACR;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA,eAAe,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,aAAY;AAAA,QAC3D;AAAA,UAACA;AAAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,aAAU;AAAA,YACV,MAAM,cAAc,WAAW,SAAS;AAAA,YACxC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,gBAAc,WAAW,CAAC,WAAW,OAAO;AAAA,YAC5C,UAAU,CAAC,UAAU;AACnB,oCAAsB,MAAM,cAAc,UAAU,EAAE;AACtD,yBAAW,KAAK;AAAA,YAClB;AAAA,YACA,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAED,GAAG;AAAA,UAAA;AAAA,QAAA;AAAA,QAEL,aAAa,UAAU,CAAC,YAAY,CAAC,YACpC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAY,OAAO;AAAA,YACnB,WAAW;AAAA,YACX,SAAS;AAAA,YAET,UAAA,oBAAC,YAAA,EAAW,MAAK,KAAA,CAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAGzB,gBAAgB,oBAAC,QAAA,EAAK,WAAW,aAAc,UAAA,cAAa;AAAA,QAC5D,cACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAY,WAAW,OAAO,eAAe,OAAO;AAAA,YACpD;AAAA,YACA,WAAW;AAAA,YACX,SAAS,MAAM,YAAY,CAAC,YAAY,CAAC,OAAO;AAAA,YAE/C,UAAA,+BAAY,YAAA,EAAW,MAAK,MAAK,IAAK,oBAAC,SAAA,EAAQ,MAAK,KAAA,CAAK;AAAA,UAAA;AAAA,QAAA;AAAA,MAC5D;AAAA,IAAA;AAAA,EAAA;AAIR;"}
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
|
|
75
75
|
@utility hover-zoom-media {
|
|
76
76
|
& img {
|
|
77
|
-
transition: scale
|
|
77
|
+
transition: scale 500ms var(--ease-out-soft);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
|
|
81
81
|
&:has([data-slot="card-link"]):hover img {
|
|
82
|
-
scale: 1.
|
|
82
|
+
scale: 1.05;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -156,3 +156,11 @@
|
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
@utility hover-scale {
|
|
161
|
+
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
|
|
162
|
+
&:has([data-slot="card-link"]):hover {
|
|
163
|
+
scale: 1.02;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|