@alpic-ai/ui 1.168.1 → 1.169.0

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.
@@ -19,7 +19,6 @@ const SECONDARY_BADGE_VARIANT = {
19
19
  };
20
20
  const PLACEHOLDER_HEIGHT = 168;
21
21
  const RAMP_CEILING = .8;
22
- const SEMANTIC_FLOOR = .62;
23
22
  const IN_FILL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.28)";
24
23
  const ROW_INTERACTIVE_CLASS = "rounded-md text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card";
25
24
  function BarList({ data, index, dataKey = "value", secondaryKey, secondaryVariantKey, titleKey, maxItems, palette, semantic, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
@@ -39,7 +38,7 @@ function BarList({ data, index, dataKey = "value", secondaryKey, secondaryVarian
39
38
  const capped = maxItems ? mapped.slice(0, maxItems) : mapped;
40
39
  return capped.map((row, rank) => {
41
40
  const rankFraction = capped.length > 1 ? rank / (capped.length - 1) : 0;
42
- const accentWeight = Math.round((1 - rankFraction * (1 - SEMANTIC_FLOOR)) * 100);
41
+ const accentWeight = Math.round((1 - rankFraction * .38) * 100);
43
42
  return {
44
43
  ...row,
45
44
  color: accent ? `color-mix(in oklab, ${accent} ${accentWeight}%, white)` : rampColor(paletteName, rankFraction * RAMP_CEILING)
@@ -29,7 +29,7 @@ function BreadcrumbItem({ className, ...props }) {
29
29
  function BreadcrumbLink({ asChild, className, ...props }) {
30
30
  return /* @__PURE__ */ jsx(asChild ? Slot : "a", {
31
31
  "data-slot": "breadcrumb-link",
32
- className: cn("type-text-sm font-medium text-subtle-foreground transition-colors [@media(hover:hover)]:hover:text-muted-foreground", className),
32
+ className: cn("type-text-sm font-medium text-subtle-foreground transition-colors [@media(hover:hover)]:hover:text-muted-foreground", "inline-flex items-center max-md:min-h-11", className),
33
33
  ...props
34
34
  });
35
35
  }
@@ -8,6 +8,7 @@ import { Slot, Slottable } from "@radix-ui/react-slot";
8
8
  const buttonVariants = cva([
9
9
  "inline-flex items-center justify-center gap-1 whitespace-nowrap",
10
10
  "h-8 px-2 rounded-md",
11
+ "max-md:min-h-11",
11
12
  "font-medium",
12
13
  "transition-colors",
13
14
  "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
@@ -24,13 +25,13 @@ const buttonVariants = cva([
24
25
  "focus-visible:bg-background"
25
26
  ].join(" "),
26
27
  link: [
27
- "h-auto px-0 rounded-xs underline-offset-4",
28
+ "h-auto max-md:min-h-0 px-0 rounded-xs underline-offset-4",
28
29
  "text-link",
29
30
  "[@media(hover:hover)]:hover:underline",
30
31
  "focus-visible:bg-background"
31
32
  ].join(" "),
32
33
  "link-muted": [
33
- "h-auto px-0 rounded-xs underline-offset-4",
34
+ "h-auto max-md:min-h-0 px-0 rounded-xs underline-offset-4",
34
35
  "text-link-muted",
35
36
  "[@media(hover:hover)]:hover:underline",
36
37
  "focus-visible:bg-background"
@@ -46,8 +47,8 @@ const buttonVariants = cva([
46
47
  },
47
48
  size: {
48
49
  default: "type-text-sm",
49
- icon: "size-8 p-0 type-text-sm",
50
- "icon-rounded": "size-8 p-0 rounded-full type-text-sm",
50
+ icon: "size-8 max-md:min-w-11 p-0 type-text-sm",
51
+ "icon-rounded": "size-8 max-md:min-w-11 p-0 rounded-full type-text-sm",
51
52
  pill: "h-7 px-3 rounded-full type-text-xs"
52
53
  }
53
54
  },
@@ -52,7 +52,7 @@ function DialogContent({ className, children, size, showCloseButton = true, ...p
52
52
  ...props,
53
53
  children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
54
54
  "data-slot": "dialog-close",
55
- className: cn("absolute top-3 right-4 flex size-8 items-center justify-center rounded-md", "text-subtle-foreground transition-colors", "[@media(hover:hover)]:hover:text-foreground", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", "disabled:pointer-events-none"),
55
+ className: cn("absolute top-3 right-4 flex size-8 items-center justify-center rounded-md", "max-md:before:absolute max-md:before:top-1/2 max-md:before:left-1/2 max-md:before:size-11 max-md:before:-translate-x-1/2 max-md:before:-translate-y-1/2 max-md:before:content-['']", "text-subtle-foreground transition-colors", "[@media(hover:hover)]:hover:text-foreground", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", "disabled:pointer-events-none"),
56
56
  children: [/* @__PURE__ */ jsx(XIcon, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
57
57
  className: "sr-only",
58
58
  children: "Close"
@@ -39,6 +39,7 @@ function DropdownMenuGroup({ ...props }) {
39
39
  }
40
40
  const dropdownMenuItemVariants = cva([
41
41
  "relative flex cursor-default items-center gap-2 rounded-sm px-2.5 py-2 outline-hidden select-none",
42
+ "max-md:min-h-11",
42
43
  "type-text-sm font-semibold",
43
44
  "mx-1.5 my-px",
44
45
  "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
@@ -218,7 +218,7 @@ function RadioField({ control, name, rules, required, label, description, toolti
218
218
  disabled: option.disabled
219
219
  }), /* @__PURE__ */ jsx(Label, {
220
220
  htmlFor: itemId,
221
- className: "type-text-sm font-normal",
221
+ className: "type-text-sm font-normal max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:pl-2",
222
222
  children: option.label
223
223
  })]
224
224
  }, option.value);
@@ -251,16 +251,17 @@ function ChecklistField({ control, name, rules, required, label, description, to
251
251
  onBlur: field.onBlur,
252
252
  children: options.map((option) => {
253
253
  const itemId = `${field.name}-${option.value}`;
254
+ const checked = selected.includes(option.value);
254
255
  return /* @__PURE__ */ jsxs("div", {
255
256
  className: "flex items-start gap-2",
256
257
  children: [/* @__PURE__ */ jsx(Checkbox, {
257
258
  id: itemId,
258
- checked: selected.includes(option.value),
259
+ checked,
259
260
  onCheckedChange: (next) => toggle(option.value, Boolean(next)),
260
261
  disabled: option.disabled
261
262
  }), /* @__PURE__ */ jsx(Label, {
262
263
  htmlFor: itemId,
263
- className: "type-text-sm font-normal leading-tight",
264
+ className: "type-text-sm font-normal leading-tight max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:items-start max-md:pl-2",
264
265
  children: option.label
265
266
  })]
266
267
  }, option.value);
@@ -287,7 +288,7 @@ function CheckboxField({ control, name, rules, label, description, tooltip }) {
287
288
  onBlur: field.onBlur
288
289
  }) }), label && /* @__PURE__ */ jsx(FormLabel, {
289
290
  tooltip,
290
- className: "font-normal",
291
+ className: "font-normal max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:items-start max-md:pl-2",
291
292
  children: label
292
293
  })]
293
294
  }),
@@ -85,7 +85,7 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
85
85
  if (colorScale === "rank") {
86
86
  const sorted = [...new Set(cells.map((cell) => cell.value).filter((value) => value > 0))].sort((lower, upper) => lower - upper);
87
87
  const RANK_FLOOR = .18;
88
- const fractionByValue = new Map(sorted.map((value, index) => [value, sorted.length > 1 ? RANK_FLOOR + (1 - RANK_FLOOR) * (index / (sorted.length - 1)) : 1]));
88
+ const fractionByValue = new Map(sorted.map((value, index) => [value, sorted.length > 1 ? RANK_FLOOR + .8200000000000001 * (index / (sorted.length - 1)) : 1]));
89
89
  return (value) => value > 0 ? fractionByValue.get(value) ?? 0 : 0;
90
90
  }
91
91
  return (value) => maxValue > 0 ? value / maxValue : 0;
@@ -45,6 +45,23 @@ function Input({ className, id, label, required, hint, error, tooltip, leadingTe
45
45
  "aria-describedby": fieldId && (hint || error) ? `${fieldId}-description` : void 0,
46
46
  ...props
47
47
  });
48
+ const wrappedInput = hasAddons ? /* @__PURE__ */ jsxs("div", {
49
+ className: cn("flex items-center w-full overflow-hidden", sizes.wrapper, "bg-background border border-border rounded-md", "transition-colors", "has-[input:focus-visible]:border-ring has-[input:focus-visible]:border-2", "has-[input:disabled]:bg-disabled has-[input:disabled]:text-disabled-foreground has-[input:disabled]:cursor-not-allowed", error && "border-border-error"),
50
+ children: [
51
+ leadingIcon && /* @__PURE__ */ jsx("span", {
52
+ className: cn("flex items-center text-subtle-foreground [&_svg]:shrink-0", sizes.leadingIcon),
53
+ children: leadingIcon
54
+ }),
55
+ leadingText && /* @__PURE__ */ jsx("span", {
56
+ className: cn("flex items-center text-subtle-foreground whitespace-nowrap border-r border-border self-stretch", sizes.leadingText),
57
+ children: leadingText
58
+ }),
59
+ /* @__PURE__ */ jsx("div", {
60
+ className: cn("flex flex-1 items-center min-w-0", sizes.inner),
61
+ children: inputElement
62
+ })
63
+ ]
64
+ }) : inputElement;
48
65
  return /* @__PURE__ */ jsx(Field, {
49
66
  fieldId,
50
67
  label,
@@ -52,23 +69,7 @@ function Input({ className, id, label, required, hint, error, tooltip, leadingTe
52
69
  hint,
53
70
  error,
54
71
  tooltip,
55
- children: hasAddons ? /* @__PURE__ */ jsxs("div", {
56
- className: cn("flex items-center w-full overflow-hidden", sizes.wrapper, "bg-background border border-border rounded-md", "transition-colors", "has-[input:focus-visible]:border-ring has-[input:focus-visible]:border-2", "has-[input:disabled]:bg-disabled has-[input:disabled]:text-disabled-foreground has-[input:disabled]:cursor-not-allowed", error && "border-border-error"),
57
- children: [
58
- leadingIcon && /* @__PURE__ */ jsx("span", {
59
- className: cn("flex items-center text-subtle-foreground [&_svg]:shrink-0", sizes.leadingIcon),
60
- children: leadingIcon
61
- }),
62
- leadingText && /* @__PURE__ */ jsx("span", {
63
- className: cn("flex items-center text-subtle-foreground whitespace-nowrap border-r border-border self-stretch", sizes.leadingText),
64
- children: leadingText
65
- }),
66
- /* @__PURE__ */ jsx("div", {
67
- className: cn("flex flex-1 items-center min-w-0", sizes.inner),
68
- children: inputElement
69
- })
70
- ]
71
- }) : inputElement
72
+ children: wrappedInput
72
73
  });
73
74
  }
74
75
  //#endregion
@@ -6,7 +6,7 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
6
6
  function ScrollArea({ className, children, ...props }) {
7
7
  return /* @__PURE__ */ jsxs(ScrollAreaPrimitive.Root, {
8
8
  "data-slot": "scroll-area",
9
- className: cn("relative", className),
9
+ className: cn("relative min-w-0", className),
10
10
  ...props,
11
11
  children: [
12
12
  /* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, {
@@ -56,7 +56,7 @@ function SelectLabel({ className, ...props }) {
56
56
  function SelectItem({ className, children, ...props }) {
57
57
  return /* @__PURE__ */ jsxs(SelectPrimitive.Item, {
58
58
  "data-slot": "select-item",
59
- className: cn("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none", "type-text-md font-medium text-foreground mx-1 my-px", "[@media(hover:hover)]:hover:bg-background-hover", "focus:bg-background-hover", "data-[disabled]:pointer-events-none data-[disabled]:text-disabled-foreground", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
59
+ className: cn("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none", "max-md:min-h-11", "type-text-md font-medium text-foreground mx-1 my-px", "[@media(hover:hover)]:hover:bg-background-hover", "focus:bg-background-hover", "data-[disabled]:pointer-events-none data-[disabled]:text-disabled-foreground", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
60
60
  ...props,
61
61
  children: [/* @__PURE__ */ jsx("span", {
62
62
  className: "absolute right-2 flex size-4 items-center justify-center",
@@ -12,7 +12,7 @@ import * as React$1 from "react";
12
12
  import { Slot } from "@radix-ui/react-slot";
13
13
  //#region src/components/sidebar.tsx
14
14
  const SIDEBAR_COOKIE_NAME = "sidebar_state";
15
- const SIDEBAR_COOKIE_MAX_AGE = 3600 * 24 * 7;
15
+ const SIDEBAR_COOKIE_MAX_AGE = 604800;
16
16
  const SIDEBAR_WIDTH = "15rem";
17
17
  const SIDEBAR_WIDTH_MOBILE = "16rem";
18
18
  const SIDEBAR_WIDTH_ICON = "3.5rem";
@@ -6,7 +6,7 @@ import * as SwitchPrimitive from "@radix-ui/react-switch";
6
6
  function Switch({ className, ...props }) {
7
7
  return /* @__PURE__ */ jsx(SwitchPrimitive.Root, {
8
8
  "data-slot": "switch",
9
- className: cn("peer inline-flex h-5 w-9 shrink-0 items-center rounded-full p-0.5", "bg-subtle data-[state=checked]:not-disabled:bg-primary", "transition-colors", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", "disabled:pointer-events-none disabled:opacity-50", className),
9
+ className: cn("peer inline-flex h-5 w-9 shrink-0 items-center rounded-full p-0.5", "max-md:relative max-md:before:absolute max-md:before:top-1/2 max-md:before:left-1/2 max-md:before:size-11 max-md:before:-translate-x-1/2 max-md:before:-translate-y-1/2 max-md:before:content-['']", "bg-subtle data-[state=checked]:not-disabled:bg-primary", "transition-colors", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", "disabled:pointer-events-none disabled:opacity-50", className),
10
10
  ...props,
11
11
  children: /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, {
12
12
  "data-slot": "switch-thumb",
@@ -43,14 +43,14 @@ function TableRow({ className, ...props }) {
43
43
  function TableHead({ className, ...props }) {
44
44
  return /* @__PURE__ */ jsx("th", {
45
45
  "data-slot": "table-head",
46
- className: cn("h-11 px-6 py-3 bg-muted text-left align-middle type-text-xs font-semibold text-placeholder dark:text-subtle-foreground whitespace-nowrap", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0", className),
46
+ className: cn("h-11 px-6 max-md:px-3 py-3 bg-muted text-left align-middle type-text-xs font-semibold text-placeholder dark:text-subtle-foreground whitespace-nowrap", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0", className),
47
47
  ...props
48
48
  });
49
49
  }
50
50
  function TableCell({ className, interactive = false, ...props }) {
51
51
  return /* @__PURE__ */ jsx("td", {
52
52
  "data-slot": "table-cell",
53
- className: cn("align-middle", interactive ? "h-px p-0" : "px-6 py-2", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0", className),
53
+ className: cn("align-middle", interactive ? "h-px p-0" : "px-6 max-md:px-3 py-2", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0", className),
54
54
  ...props
55
55
  });
56
56
  }
@@ -15,13 +15,13 @@ const tabsTriggerVariants = cva([
15
15
  variants: { variant: {
16
16
  default: ["rounded-md border border-transparent px-2 py-1", "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"],
17
17
  line: [
18
- "h-8 pb-3 px-1",
18
+ "h-8 max-md:min-h-11 pb-3 px-1",
19
19
  "text-quaternary-foreground",
20
20
  "[@media(hover:hover)]:hover:text-muted-foreground-hover",
21
21
  "data-[state=active]:border-b-2 data-[state=active]:border-foreground data-[state=active]:text-foreground"
22
22
  ],
23
23
  pill: [
24
- "rounded-md px-2 py-1.5",
24
+ "rounded-md max-md:min-h-11 px-2 py-1.5",
25
25
  "text-quaternary-foreground",
26
26
  "[@media(hover:hover)]:hover:bg-accent [@media(hover:hover)]:hover:text-muted-foreground",
27
27
  "data-[state=active]:bg-accent data-[state=active]:text-muted-foreground"
@@ -41,7 +41,7 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
41
41
  const TabsListVariantContext = createContext("default");
42
42
  const tabsListVariants = cva("inline-flex w-fit items-center justify-center text-muted-foreground group-data-[orientation=vertical]/tabs:flex-col", {
43
43
  variants: { variant: {
44
- default: "rounded-lg bg-muted p-[3px] group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:bg-transparent group-data-[orientation=vertical]/tabs:p-0 group-data-[orientation=vertical]/tabs:rounded-none",
44
+ default: "rounded-lg bg-muted p-[3px] group-data-[orientation=horizontal]/tabs:h-9 max-md:group-data-[orientation=horizontal]/tabs:h-11 group-data-[orientation=vertical]/tabs:bg-transparent group-data-[orientation=vertical]/tabs:p-0 group-data-[orientation=vertical]/tabs:rounded-none",
45
45
  line: "gap-3 rounded-none bg-transparent border-b border-subtle group-data-[orientation=horizontal]/tabs:flex-wrap"
46
46
  } },
47
47
  defaultVariants: { variant: "default" }
@@ -7,6 +7,15 @@ import * as React$1 from "react";
7
7
  function Textarea({ className, id, label, required, hint, error, tooltip, ...props }) {
8
8
  const generatedId = React$1.useId();
9
9
  const fieldId = id ?? generatedId;
10
+ const textarea = /* @__PURE__ */ jsx("textarea", {
11
+ id: fieldId,
12
+ "data-slot": "textarea",
13
+ className: cn("block w-full min-h-[120px] max-h-[480px] resize-y [field-sizing:content]", "px-3.5 py-3", "type-text-md text-foreground placeholder:text-placeholder", "bg-background border border-border rounded-md", "transition-colors", "outline-none focus-visible:border-ring focus-visible:border-2", "disabled:bg-disabled disabled:text-disabled-foreground disabled:cursor-not-allowed", "aria-invalid:border-border-error [@media(hover:hover)]:aria-invalid:hover:border-border-error", error && "border-border-error [@media(hover:hover)]:hover:border-border-error", className),
14
+ required,
15
+ "aria-invalid": error ? true : void 0,
16
+ "aria-describedby": fieldId && (hint || error) ? `${fieldId}-description` : void 0,
17
+ ...props
18
+ });
10
19
  return /* @__PURE__ */ jsx(Field, {
11
20
  fieldId,
12
21
  label,
@@ -14,15 +23,7 @@ function Textarea({ className, id, label, required, hint, error, tooltip, ...pro
14
23
  hint,
15
24
  error,
16
25
  tooltip,
17
- children: /* @__PURE__ */ jsx("textarea", {
18
- id: fieldId,
19
- "data-slot": "textarea",
20
- className: cn("block w-full min-h-[120px] max-h-[480px] resize-y [field-sizing:content]", "px-3.5 py-3", "type-text-md text-foreground placeholder:text-placeholder", "bg-background border border-border rounded-md", "transition-colors", "outline-none focus-visible:border-ring focus-visible:border-2", "disabled:bg-disabled disabled:text-disabled-foreground disabled:cursor-not-allowed", "aria-invalid:border-border-error [@media(hover:hover)]:aria-invalid:hover:border-border-error", error && "border-border-error [@media(hover:hover)]:hover:border-border-error", className),
21
- required,
22
- "aria-invalid": error ? true : void 0,
23
- "aria-describedby": fieldId && (hint || error) ? `${fieldId}-description` : void 0,
24
- ...props
25
- })
26
+ children: textarea
26
27
  });
27
28
  }
28
29
  //#endregion
@@ -27,8 +27,8 @@ const toggleGroupItemVariants = cva([
27
27
  ].join(" ")
28
28
  },
29
29
  size: {
30
- default: "h-8 px-2 min-w-8",
31
- sm: "h-7 px-1.5 min-w-7"
30
+ default: "h-8 max-md:min-h-11 px-2 min-w-8",
31
+ sm: "h-7 max-md:min-h-11 px-1.5 min-w-7"
32
32
  }
33
33
  },
34
34
  defaultVariants: {
@@ -1,10 +1,9 @@
1
1
  import * as React$1 from "react";
2
2
  //#region src/hooks/use-mobile.ts
3
- const MOBILE_BREAKPOINT = 768;
4
3
  function useIsMobile() {
5
4
  const [isMobile, setIsMobile] = React$1.useState(void 0);
6
5
  React$1.useEffect(() => {
7
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
6
+ const mql = window.matchMedia(`(max-width: 767px)`);
8
7
  const onChange = () => setIsMobile(mql.matches);
9
8
  mql.addEventListener("change", onChange);
10
9
  setIsMobile(mql.matches);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "1.168.1",
3
+ "version": "1.169.0",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,6 +31,7 @@ function BreadcrumbLink({ asChild, className, ...props }: React.ComponentProps<"
31
31
  data-slot="breadcrumb-link"
32
32
  className={cn(
33
33
  "type-text-sm font-medium text-subtle-foreground transition-colors [@media(hover:hover)]:hover:text-muted-foreground",
34
+ "inline-flex items-center max-md:min-h-11",
34
35
  className,
35
36
  )}
36
37
  {...props}
@@ -11,6 +11,7 @@ const buttonVariants = cva(
11
11
  [
12
12
  "inline-flex items-center justify-center gap-1 whitespace-nowrap",
13
13
  "h-8 px-2 rounded-md",
14
+ "max-md:min-h-11",
14
15
  "font-medium",
15
16
  "transition-colors",
16
17
  "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
@@ -31,13 +32,13 @@ const buttonVariants = cva(
31
32
  "focus-visible:bg-background",
32
33
  ].join(" "),
33
34
  link: [
34
- "h-auto px-0 rounded-xs underline-offset-4",
35
+ "h-auto max-md:min-h-0 px-0 rounded-xs underline-offset-4",
35
36
  "text-link",
36
37
  "[@media(hover:hover)]:hover:underline",
37
38
  "focus-visible:bg-background",
38
39
  ].join(" "),
39
40
  "link-muted": [
40
- "h-auto px-0 rounded-xs underline-offset-4",
41
+ "h-auto max-md:min-h-0 px-0 rounded-xs underline-offset-4",
41
42
  "text-link-muted",
42
43
  "[@media(hover:hover)]:hover:underline",
43
44
  "focus-visible:bg-background",
@@ -56,8 +57,8 @@ const buttonVariants = cva(
56
57
  },
57
58
  size: {
58
59
  default: "type-text-sm",
59
- icon: "size-8 p-0 type-text-sm",
60
- "icon-rounded": "size-8 p-0 rounded-full type-text-sm",
60
+ icon: "size-8 max-md:min-w-11 p-0 type-text-sm",
61
+ "icon-rounded": "size-8 max-md:min-w-11 p-0 rounded-full type-text-sm",
61
62
  pill: "h-7 px-3 rounded-full type-text-xs",
62
63
  },
63
64
  },
@@ -77,6 +77,7 @@ function DialogContent({ className, children, size, showCloseButton = true, ...p
77
77
  data-slot="dialog-close"
78
78
  className={cn(
79
79
  "absolute top-3 right-4 flex size-8 items-center justify-center rounded-md",
80
+ "max-md:before:absolute max-md:before:top-1/2 max-md:before:left-1/2 max-md:before:size-11 max-md:before:-translate-x-1/2 max-md:before:-translate-y-1/2 max-md:before:content-['']",
80
81
  "text-subtle-foreground transition-colors",
81
82
  "[@media(hover:hover)]:hover:text-foreground",
82
83
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
@@ -54,6 +54,7 @@ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMen
54
54
  const dropdownMenuItemVariants = cva(
55
55
  [
56
56
  "relative flex cursor-default items-center gap-2 rounded-sm px-2.5 py-2 outline-hidden select-none",
57
+ "max-md:min-h-11",
57
58
  "type-text-sm font-semibold",
58
59
  "mx-1.5 my-px",
59
60
  "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
@@ -362,7 +362,10 @@ function RadioField<TFieldValues extends FieldValues, TName extends FieldPath<TF
362
362
  return (
363
363
  <div key={option.value} className="flex items-center gap-2">
364
364
  <RadioGroupItem id={itemId} value={option.value} disabled={option.disabled} />
365
- <Label htmlFor={itemId} className="type-text-sm font-normal">
365
+ <Label
366
+ htmlFor={itemId}
367
+ className="type-text-sm font-normal max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:pl-2"
368
+ >
366
369
  {option.label}
367
370
  </Label>
368
371
  </div>
@@ -427,7 +430,10 @@ function ChecklistField<TFieldValues extends FieldValues, TName extends FieldPat
427
430
  onCheckedChange={(next) => toggle(option.value, Boolean(next))}
428
431
  disabled={option.disabled}
429
432
  />
430
- <Label htmlFor={itemId} className="type-text-sm font-normal leading-tight">
433
+ <Label
434
+ htmlFor={itemId}
435
+ className="type-text-sm font-normal leading-tight max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:items-start max-md:pl-2"
436
+ >
431
437
  {option.label}
432
438
  </Label>
433
439
  </div>
@@ -470,7 +476,10 @@ function CheckboxField<TFieldValues extends FieldValues, TName extends FieldPath
470
476
  />
471
477
  </FormControl>
472
478
  {label && (
473
- <FormLabel tooltip={tooltip} className="font-normal">
479
+ <FormLabel
480
+ tooltip={tooltip}
481
+ className="font-normal max-md:-ml-2 max-md:min-h-11 max-md:flex-1 max-md:items-start max-md:pl-2"
482
+ >
474
483
  {label}
475
484
  </FormLabel>
476
485
  )}
@@ -7,7 +7,7 @@ import { cn } from "../lib/cn";
7
7
 
8
8
  function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
9
9
  return (
10
- <ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
10
+ <ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative min-w-0", className)} {...props}>
11
11
  <ScrollAreaPrimitive.Viewport
12
12
  data-slot="scroll-area-viewport"
13
13
  className="size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:ring-2 focus-visible:ring-ring"
@@ -101,6 +101,7 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
101
101
  data-slot="select-item"
102
102
  className={cn(
103
103
  "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none",
104
+ "max-md:min-h-11",
104
105
  "type-text-md font-medium text-foreground mx-1 my-px",
105
106
  "[@media(hover:hover)]:hover:bg-background-hover",
106
107
  "focus:bg-background-hover",
@@ -11,6 +11,7 @@ function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimi
11
11
  data-slot="switch"
12
12
  className={cn(
13
13
  "peer inline-flex h-5 w-9 shrink-0 items-center rounded-full p-0.5",
14
+ "max-md:relative max-md:before:absolute max-md:before:top-1/2 max-md:before:left-1/2 max-md:before:size-11 max-md:before:-translate-x-1/2 max-md:before:-translate-y-1/2 max-md:before:content-['']",
14
15
  "bg-subtle data-[state=checked]:not-disabled:bg-primary",
15
16
  "transition-colors",
16
17
  "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
@@ -65,7 +65,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
65
65
  <th
66
66
  data-slot="table-head"
67
67
  className={cn(
68
- "h-11 px-6 py-3 bg-muted text-left align-middle type-text-xs font-semibold text-placeholder dark:text-subtle-foreground whitespace-nowrap",
68
+ "h-11 px-6 max-md:px-3 py-3 bg-muted text-left align-middle type-text-xs font-semibold text-placeholder dark:text-subtle-foreground whitespace-nowrap",
69
69
  "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0",
70
70
  className,
71
71
  )}
@@ -85,7 +85,7 @@ function TableCell({ className, interactive = false, ...props }: TableCellProps)
85
85
  data-slot="table-cell"
86
86
  className={cn(
87
87
  "align-middle",
88
- interactive ? "h-px p-0" : "px-6 py-2",
88
+ interactive ? "h-px p-0" : "px-6 max-md:px-3 py-2",
89
89
  "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:px-0",
90
90
  className,
91
91
  )}
@@ -25,13 +25,13 @@ const tabsTriggerVariants = cva(
25
25
  "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
26
26
  ],
27
27
  line: [
28
- "h-8 pb-3 px-1",
28
+ "h-8 max-md:min-h-11 pb-3 px-1",
29
29
  "text-quaternary-foreground",
30
30
  "[@media(hover:hover)]:hover:text-muted-foreground-hover",
31
31
  "data-[state=active]:border-b-2 data-[state=active]:border-foreground data-[state=active]:text-foreground",
32
32
  ],
33
33
  pill: [
34
- "rounded-md px-2 py-1.5",
34
+ "rounded-md max-md:min-h-11 px-2 py-1.5",
35
35
  "text-quaternary-foreground",
36
36
  "[@media(hover:hover)]:hover:bg-accent [@media(hover:hover)]:hover:text-muted-foreground",
37
37
  "data-[state=active]:bg-accent data-[state=active]:text-muted-foreground",
@@ -71,7 +71,7 @@ const tabsListVariants = cva(
71
71
  variants: {
72
72
  variant: {
73
73
  default:
74
- "rounded-lg bg-muted p-[3px] group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:bg-transparent group-data-[orientation=vertical]/tabs:p-0 group-data-[orientation=vertical]/tabs:rounded-none",
74
+ "rounded-lg bg-muted p-[3px] group-data-[orientation=horizontal]/tabs:h-9 max-md:group-data-[orientation=horizontal]/tabs:h-11 group-data-[orientation=vertical]/tabs:bg-transparent group-data-[orientation=vertical]/tabs:p-0 group-data-[orientation=vertical]/tabs:rounded-none",
75
75
  line: "gap-3 rounded-none bg-transparent border-b border-subtle group-data-[orientation=horizontal]/tabs:flex-wrap",
76
76
  },
77
77
  },
@@ -183,7 +183,6 @@ function TabsNavTrigger({
183
183
  className={cn(
184
184
  tabsTriggerVariants({ variant }),
185
185
  "flex transition-colors",
186
- // TODO(mobile): line h-8 (32px) is below 44px touch target minimum — desktop-primary nav per Figma spec
187
186
  variant === "line" && "cursor-pointer",
188
187
  className,
189
188
  )}
@@ -30,8 +30,8 @@ const toggleGroupItemVariants = cva(
30
30
  ].join(" "),
31
31
  },
32
32
  size: {
33
- default: "h-8 px-2 min-w-8",
34
- sm: "h-7 px-1.5 min-w-7",
33
+ default: "h-8 max-md:min-h-11 px-2 min-w-8",
34
+ sm: "h-7 max-md:min-h-11 px-1.5 min-w-7",
35
35
  },
36
36
  },
37
37
  defaultVariants: {