@tangle-network/ui 11.32.2 → 11.33.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.
@@ -153,7 +153,9 @@ describe("FileArtifactPane previews", () => {
153
153
  />,
154
154
  );
155
155
 
156
- expect(await screen.findByRole("link", { name: "Sibling" })).toHaveAttribute(
156
+ // The rich editor lazy-loads its editor packages; under a loaded host that takes
157
+ // longer than findBy's 1s default.
158
+ expect(await screen.findByRole("link", { name: "Sibling" }, { timeout: 10_000 })).toHaveAttribute(
157
159
  "href",
158
160
  "/app/ws-1/vault?file=docs%2Fsibling.md",
159
161
  );
@@ -252,7 +254,7 @@ describe("FileArtifactPane controlled drafts", () => {
252
254
  editor: { enabled: true, savedContent: "# Saved notes", onSave },
253
255
  };
254
256
  const view = render(<FileArtifactPane {...props} />);
255
- const save = await screen.findByRole("button", { name: "Save changes" });
257
+ const save = await screen.findByRole("button", { name: "Save changes" }, { timeout: 10_000 });
256
258
  expect(screen.getByRole("heading", { name: "Unsaved source draft" })).toBeVisible();
257
259
  expect(screen.getByText("Unsaved changes")).toBeVisible();
258
260
  expect(save).toBeEnabled();
@@ -1,4 +1,5 @@
1
1
  import { focusField } from "./focus";
2
+ import { ixField } from "./interaction";
2
3
  import { cn } from "./utils";
3
4
 
4
5
  // Time-valued tokens only: --transition-fast is a legacy duration + easing
@@ -66,16 +67,21 @@ export const controlSizes = {
66
67
  touch: "h-11 min-h-11 min-w-11 px-4 py-2 text-base",
67
68
  } as const;
68
69
 
69
- // --input is the shadcn border/track role, NOT a field background. --bg-input
70
- // resolves the recessed well inside every canonical/named light/dark scope.
70
+ // --input is the shadcn border/track role, NOT a field background. The fill is
71
+ // `ixField`'s `--well`, the brand's surface-context variable: the page well at
72
+ // the top level, re-declared by each card level so a field is never the colour
73
+ // of the plane it sits on. `ixField` also replaces focusField's resting
74
+ // `border-border` and neutral hover with `--field-border` and the green hover
75
+ // edge; focusField's `:focus` border and halo are kept exactly.
71
76
  // An autofill inset shadow coexists with the Tailwind focus ring; never hide
72
77
  // autofill with a seconds-long background transition or clear the focus halo.
73
78
  // --field-surface lets a context raise its fields: Toolbar sets it to the card
74
79
  // surface so filters on the page canvas read as controls, not as holes in it.
75
80
  export const fieldPresentation = cn(
76
81
  focusField,
82
+ ixField,
77
83
  controlMotion,
78
- "min-w-0 bg-[var(--field-surface,var(--bg-input))] text-foreground",
84
+ "min-w-0 text-foreground",
79
85
  "aria-invalid:border-[var(--surface-danger-border)] aria-invalid:hover:border-[var(--surface-danger-border)] aria-invalid:focus:border-[var(--focus-border-danger)] aria-invalid:focus:ring-[var(--focus-halo-danger)]",
80
- "autofill:shadow-[inset_0_0_0_1000px_var(--bg-input)] autofill:[-webkit-text-fill-color:var(--text-primary)] autofill:caret-[var(--text-primary)]",
86
+ "autofill:shadow-[inset_0_0_0_1000px_var(--field-surface,var(--well))] autofill:[-webkit-text-fill-color:var(--text-primary)] autofill:caret-[var(--text-primary)]",
81
87
  );
package/src/lib/focus.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { ixField } from "./interaction";
2
+
1
3
  /**
2
4
  * The focus treatment every control shares, in two shapes over one palette
3
5
  * (`--focus-border`, `--focus-halo` and `--border-strong` from
@@ -42,9 +44,13 @@ export const focusField =
42
44
  * compose your own classes instead of this helper. The field selector is
43
45
  * written out in full because Tailwind reads class names statically.
44
46
  * Base styles use its marker class to clear the inner editor fallback outline.
47
+ *
48
+ * The container is the field, so it takes `ixField` (lib/interaction.ts): its
49
+ * own well fill, the `--field-border` hairline and the green hover edge. The
50
+ * borderless field inside stays transparent.
45
51
  */
46
52
  export const focusFieldWithin =
47
- "focus-field-within border-border transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--border-strong)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]";
53
+ `focus-field-within transition-[border-color,box-shadow] duration-150 ease-out ${ixField} has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]`;
48
54
 
49
55
  /** Error state for a field: a danger hairline and the same focus shape in danger. Compose after `focusField`. */
50
56
  export const focusFieldInvalid =
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The interaction states every control shares: one palette in five shapes, all
3
+ * read from `@tangle-network/brand`'s `--ix-*` tokens (tokens.css), which derive
4
+ * from the focus ring hue `--hsl-ring`. So hover, press, after-click and
5
+ * selected are the same green in Hospitality and the same indigo on the spine
6
+ * as the keyboard focus ring.
7
+ *
8
+ * hover 1px edge at .45 plus a 4px halo at .16 (`--ix-hover`)
9
+ * press 1px edge at .95 plus a 4px halo at .30 (`--ix-press`)
10
+ * after a control focused by a mouse click keeps a soft ring (`--ix-after`)
11
+ * selected a tint plus a 1px inset ring at .85 (`--ix-sel-bg`, `--ix-sel-ring`)
12
+ *
13
+ * The keyboard focus-visible glow stays exactly as `focusRing` / `focusField`
14
+ * (lib/focus.ts) draw it. Tailwind composes `--tw-ring-shadow` with
15
+ * `--tw-shadow`, so these shadows coexist with that ring, and
16
+ * `focus-visible:shadow-none` drops the hover glow while the keyboard ring shows.
17
+ *
18
+ * Every class is written out in full because Tailwind reads class names
19
+ * statically.
20
+ */
21
+
22
+ /** Bordered or filled control: green edge on hover, deeper on press, a persistent soft ring after a mouse click. */
23
+ export const ixControl =
24
+ "hover:border-[var(--ix-hover-border)] hover:shadow-[var(--ix-hover)] " +
25
+ "focus:not-focus-visible:border-[var(--ix-hover-border)] focus:not-focus-visible:shadow-[var(--ix-after)] " +
26
+ "active:border-[hsl(var(--hsl-ring))] active:shadow-[var(--ix-press)] focus-visible:shadow-none";
27
+
28
+ /** The after-click part of `ixControl` alone, for a field that opens a menu (SelectTrigger). */
29
+ export const ixControlAfter =
30
+ "focus:not-focus-visible:border-[var(--ix-hover-border)] focus:not-focus-visible:shadow-[var(--ix-after)]";
31
+
32
+ /** Ghost, icon, link and bare controls: a wash, never a glow (too noisy in sidebars and toolbars). */
33
+ export const ixControlSoft = "hover:bg-[var(--ix-hover-bg)] active:bg-[var(--ix-sel-bg)]";
34
+
35
+ /**
36
+ * Field: its own well and a visible border; hover replaces `focusField`'s
37
+ * neutral `--border-strong`. The well is the `--well` context variable, which a
38
+ * card re-declares for its descendants, so a field never takes its parent's
39
+ * colour. `--field-surface` still lets a context such as Toolbar pick the fill.
40
+ */
41
+ export const ixField =
42
+ "bg-[var(--field-surface,var(--well))] border-[var(--field-border)] shadow-[var(--shadow-well)] " +
43
+ "hover:border-[var(--ix-hover-border)] hover:shadow-[var(--shadow-well),var(--ix-hover)]";
44
+
45
+ /** Selected / on / current: tint plus a 1px inset green ring. */
46
+ export const ixSelected =
47
+ "aria-pressed:bg-[var(--ix-sel-bg)] aria-pressed:text-[var(--accent-text)] aria-pressed:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] " +
48
+ "aria-checked:bg-[var(--ix-sel-bg)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] " +
49
+ "aria-[current=page]:bg-[var(--ix-sel-bg)] aria-[current=page]:text-[var(--accent-text)] aria-[current=page]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] " +
50
+ "data-[state=on]:bg-[var(--ix-sel-bg)] data-[state=on]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] " +
51
+ "data-[state=active]:bg-[var(--ix-sel-bg)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)]";
52
+
53
+ /** Underline tab: 2px green underline plus a faint tint, never a box; a faint underline on hover. */
54
+ export const ixTabUnderline =
55
+ "hover:shadow-[inset_0_-2px_0_hsl(var(--hsl-ring)/0.4)] " +
56
+ "data-[state=active]:bg-[hsl(var(--hsl-ring)/0.07)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_-2px_0_var(--ix-sel-ring)] " +
57
+ "aria-checked:bg-[hsl(var(--hsl-ring)/0.07)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_-2px_0_var(--ix-sel-ring)]";
58
+
59
+ /** List row / menu item: wash plus inset edge on hover or keyboard highlight; checked = selected. */
60
+ export const ixRow =
61
+ "hover:bg-[var(--ix-hover-bg)] hover:shadow-[inset_0_0_0_1px_var(--ix-hover-border)] " +
62
+ "data-[highlighted]:bg-[var(--ix-sel-bg)] data-[highlighted]:shadow-[inset_0_0_0_1px_var(--ix-hover-border)] " +
63
+ "focus:bg-[var(--ix-sel-bg)] data-[state=checked]:bg-[var(--ix-sel-bg)] data-[state=checked]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] " +
64
+ "active:shadow-[inset_0_0_0_1px_hsl(var(--hsl-ring))]";
@@ -3,29 +3,35 @@ import { cva } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
  import { controlHeight, controlMotion, controlRadius, controlSizes, controlSquare, controlText } from "../lib/control-presentation";
5
5
  import { focusRing } from "../lib/focus";
6
+ import { ixControl, ixControlSoft, ixSelected } from "../lib/interaction";
6
7
  import { cn } from "../lib/utils";
7
8
 
8
9
  // Sizes follow the shared control scale (control-presentation.ts): sm, md and
9
10
  // lg match Input, Textarea and SelectTrigger at the same size. xl is a display
10
11
  // call to action outside that scale.
12
+ //
13
+ // Interaction (lib/interaction.ts): a variant with a border or a fill takes
14
+ // `ixControl` (green edge and glow on hover, press and after a click); ghost,
15
+ // link and bare take `ixControlSoft`, a wash that never glows. A toggle button
16
+ // (any `aria-pressed`) adds `ixControl` + `ixSelected` whatever its variant.
11
17
  const buttonVariants = cva(
12
18
  `inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-[color,background-color,border-color,box-shadow,transform,scale] ${controlMotion} ${focusRing} disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`,
13
19
  {
14
20
  variants: {
15
21
  variant: {
16
22
  default:
17
- "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.97]",
23
+ `bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.97] ${ixControl}`,
18
24
  destructive:
19
- "bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.97]",
25
+ `bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.97] ${ixControl}`,
20
26
  outline:
21
- "border border-border bg-card hover:bg-muted active:scale-[0.97] text-foreground",
27
+ `border border-border bg-card hover:bg-muted active:scale-[0.97] text-foreground ${ixControl}`,
22
28
  secondary:
23
- "bg-muted border border-border text-foreground hover:bg-muted/80 active:scale-[0.97]",
29
+ `bg-muted border border-border text-foreground hover:bg-muted/80 active:scale-[0.97] ${ixControl}`,
24
30
  ghost:
25
- "hover:bg-muted hover:text-foreground text-muted-foreground border border-transparent",
26
- link: "text-[var(--accent-text)] underline-offset-4 hover:underline",
31
+ `hover:text-foreground text-muted-foreground border border-transparent ${ixControlSoft}`,
32
+ link: `text-[var(--accent-text)] underline-offset-4 hover:underline ${ixControlSoft}`,
27
33
  sandbox:
28
- "bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border border-[var(--border-accent)] hover:bg-[var(--btn-primary-hover)] active:scale-[0.97]",
34
+ `bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border border-[var(--border-accent)] hover:bg-[var(--btn-primary-hover)] active:scale-[0.97] ${ixControl}`,
29
35
  },
30
36
  size: {
31
37
  default: `${controlHeight.md} ${controlText.md} ${controlRadius.md} px-4 py-2`,
@@ -50,7 +56,20 @@ const buttonVariants = cva(
50
56
  // every surface, size and type class. It is for pressable rows, tiles and pills
51
57
  // that draw their own layout; anything that reads as a button uses a styled
52
58
  // variant and a size.
53
- const bareButton = `${focusRing} disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50`;
59
+ const bareButton = `${focusRing} ${ixControlSoft} disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50`;
60
+
61
+ /** One Button render's classes: variant and size, the toggle states, then the caller's. */
62
+ function buttonClassName(
63
+ variant: ButtonProps["variant"],
64
+ size: ButtonProps["size"],
65
+ pressed: React.AriaAttributes["aria-pressed"],
66
+ className: string | undefined,
67
+ ) {
68
+ const toggle = pressed === undefined ? undefined : `${ixControl} ${ixSelected}`;
69
+ return variant === "bare"
70
+ ? cn(bareButton, toggle, className)
71
+ : cn(buttonVariants({ variant, size }), toggle, className);
72
+ }
54
73
 
55
74
  export interface ButtonProps
56
75
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -115,7 +134,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
115
134
  // Keep slotted content intact: its action name must survive loading too.
116
135
  return (
117
136
  <Slot
118
- className={variant === "bare" ? cn(bareButton, className) : cn(buttonVariants({ variant, size, className }))}
137
+ className={buttonClassName(variant, size, props["aria-pressed"], className)}
119
138
  ref={ref}
120
139
  {...props}
121
140
  {...(isDisabled ? disabledHandlers(props) : {})}
@@ -140,7 +159,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
140
159
 
141
160
  return (
142
161
  <button
143
- className={variant === "bare" ? cn(bareButton, className) : cn(buttonVariants({ variant, size, className }))}
162
+ className={buttonClassName(variant, size, props["aria-pressed"], className)}
144
163
  ref={ref}
145
164
  {...props}
146
165
  disabled={disabled || loading}
@@ -1,17 +1,31 @@
1
1
  import * as React from "react";
2
+ import { ixControl } from "../lib/interaction";
2
3
  import { cn } from "../lib/utils";
3
4
  import { Heading } from "./heading";
4
5
 
6
+ /**
7
+ * A card is one level of the brand surface ladder. It carries
8
+ * `data-slot="card"`, and the brand's surface-context rule (tokens.css)
9
+ * re-declares `--card` and `--well` for everything inside it: a Card or a
10
+ * `bg-card` box inside a card paints the raised level, one inside that the
11
+ * raised-2 level, and a field in any of them takes the well that is the
12
+ * opposite step from its parent. Nesting needs no prop and no app selector.
13
+ */
5
14
  const Card = React.forwardRef<
6
15
  HTMLDivElement,
7
16
  React.HTMLAttributes<HTMLDivElement> & {
8
17
  variant?: "default" | "glass" | "sandbox" | "elevated";
9
- /** Decorative border feedback only. Put actions in a native button or link. */
18
+ /** Decorative hover feedback only (the shared green edge). Put actions in a native button or link. */
10
19
  hover?: boolean;
20
+ /**
21
+ * The card is itself the control, such as a card wrapped in a link: it takes
22
+ * the full control states (hover glow, press, the ring after a click).
23
+ */
24
+ interactive?: boolean;
11
25
  }
12
- >(({ className, variant = "default", hover = false, ...props }, ref) => {
26
+ >(({ className, variant = "default", hover = false, interactive = false, ...props }, ref) => {
13
27
  const variants = {
14
- default: "bg-card border-border",
28
+ default: "bg-card border-border shadow-[var(--edge-highlight),var(--shadow-card)]",
15
29
  elevated: "bg-muted/50 border-border shadow-[var(--shadow-card)]",
16
30
  glass: "bg-card/80 backdrop-blur-xl border-border shadow-[var(--shadow-card)]",
17
31
  sandbox: "bg-muted/50 border-primary/20 shadow-[var(--shadow-accent)]",
@@ -21,12 +35,13 @@ const Card = React.forwardRef<
21
35
  <div
22
36
  ref={ref}
23
37
  className={cn(
24
- "rounded-[var(--radius-lg)] border text-card-foreground transition-[border-color,box-shadow]",
38
+ "rounded-[var(--radius-lg)] border text-card-foreground transition-[border-color,box-shadow,background-color]",
25
39
  "duration-[var(--transition-default)]",
26
40
  variants[variant],
27
- hover && "hover:border-[var(--border-strong)]",
41
+ (hover || interactive) && ixControl,
28
42
  className,
29
43
  )}
44
+ data-slot="card"
30
45
  {...props}
31
46
  />
32
47
  );
@@ -69,7 +84,9 @@ const CardDescription = React.forwardRef<
69
84
  >(({ className, ...props }, ref) => (
70
85
  <p
71
86
  ref={ref}
72
- className={cn("text-muted-foreground text-sm", className)}
87
+ // Two lines at most: a guard, not a copy budget. Long copy belongs in help
88
+ // text or a disclosure.
89
+ className={cn("text-muted-foreground text-sm line-clamp-2", className)}
73
90
  {...props}
74
91
  />
75
92
  ));
@@ -12,10 +12,13 @@ afterEach(cleanup);
12
12
 
13
13
  describe("shared control presentation", () => {
14
14
  it("uses the field well, not the border/track alias, with scoped foreground and time-only motion", () => {
15
- expect(fieldPresentation).toContain("bg-[var(--field-surface,var(--bg-input))]");
15
+ // The well is the brand's --well context variable; Toolbar can still pick the fill.
16
+ expect(fieldPresentation).toContain("bg-[var(--field-surface,var(--well))]");
16
17
  expect(fieldPresentation.split(" ")).not.toContain("bg-input");
17
18
  expect(fieldPresentation.split(" ")).not.toContain("bg-card");
18
- expect(fieldPresentation).toContain("border-border");
19
+ // ixField's --field-border replaces focusField's resting border-border.
20
+ expect(fieldPresentation.split(" ")).toContain("border-[var(--field-border)]");
21
+ expect(fieldPresentation.split(" ")).not.toContain("border-border");
19
22
  expect(fieldPresentation).toContain("text-foreground");
20
23
  expect(fieldPresentation).not.toContain("duration-150");
21
24
  expect(controlMotion).toContain("duration-[var(--duration-fast)]");
@@ -100,7 +103,7 @@ describe("shared control presentation", () => {
100
103
  expect(input.current).toHaveAttribute("aria-invalid", "true");
101
104
  expect(input.current!.className).toContain("autofill:shadow-");
102
105
  expect(input.current!.className.split(" ")).toContain("bg-card");
103
- expect(input.current!.className).not.toContain("bg-[var(--field-surface,var(--bg-input))]");
106
+ expect(input.current!.className).not.toContain("bg-[var(--field-surface,var(--well))]");
104
107
  expect(textarea.current).toBeDisabled();
105
108
  expect(trigger.current).toBe(screen.getByLabelText("Choice"));
106
109
  expect(trigger.current).toBeDisabled();
@@ -128,7 +128,7 @@ describe("raised fields in a Toolbar", () => {
128
128
  );
129
129
  expect(screen.getByTestId("toolbar").className).toContain("[--field-surface:hsl(var(--card))]");
130
130
  for (const name of ["Format", "Elsewhere"]) {
131
- expect(screen.getByRole("combobox", { name }).className).toContain("bg-[var(--field-surface,var(--bg-input))]");
131
+ expect(screen.getByRole("combobox", { name }).className).toContain("bg-[var(--field-surface,var(--well))]");
132
132
  }
133
133
  });
134
134
  });
@@ -1,6 +1,7 @@
1
1
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
2
  import { Check, ChevronRight, Circle } from "lucide-react";
3
3
  import * as React from "react";
4
+ import { ixRow } from "../lib/interaction";
4
5
  import { cn } from "../lib/utils";
5
6
 
6
7
  const DropdownMenu = DropdownMenuPrimitive.Root;
@@ -24,8 +25,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
24
25
  <DropdownMenuPrimitive.SubTrigger
25
26
  ref={ref}
26
27
  className={cn(
27
- "flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none",
28
- "focus:bg-accent data-[state=open]:bg-muted/50",
28
+ "flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-[color,background-color,box-shadow]",
29
+ "data-[state=open]:bg-[var(--ix-sel-bg)]",
30
+ ixRow,
29
31
  inset && "pl-8",
30
32
  className,
31
33
  )}
@@ -45,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
45
47
  <DropdownMenuPrimitive.SubContent
46
48
  ref={ref}
47
49
  className={cn(
48
- "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
50
+ "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-popover p-1 text-popover-foreground shadow-[var(--edge-highlight),var(--shadow-card)]",
49
51
  "data-[state=closed]:animate-out data-[state=open]:animate-in",
50
52
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
51
53
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -68,7 +70,7 @@ const DropdownMenuContent = React.forwardRef<
68
70
  ref={ref}
69
71
  sideOffset={sideOffset}
70
72
  className={cn(
71
- "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card p-1 text-foreground shadow-[var(--shadow-card)]",
73
+ "z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-popover p-1 text-popover-foreground shadow-[var(--edge-highlight),var(--shadow-card)]",
72
74
  "data-[state=closed]:animate-out data-[state=open]:animate-in",
73
75
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
74
76
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -91,8 +93,9 @@ const DropdownMenuItem = React.forwardRef<
91
93
  <DropdownMenuPrimitive.Item
92
94
  ref={ref}
93
95
  className={cn(
94
- "relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors",
95
- "focus:bg-muted/50 focus:text-foreground",
96
+ "relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-[color,background-color,box-shadow]",
97
+ "focus:text-foreground",
98
+ ixRow,
96
99
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
97
100
  inset && "pl-8",
98
101
  className,
@@ -109,8 +112,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
109
112
  <DropdownMenuPrimitive.CheckboxItem
110
113
  ref={ref}
111
114
  className={cn(
112
- "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
113
- "focus:bg-muted/50 focus:text-foreground",
115
+ "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-[color,background-color,box-shadow]",
116
+ "focus:text-foreground",
117
+ ixRow,
114
118
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
115
119
  className,
116
120
  )}
@@ -135,8 +139,9 @@ const DropdownMenuRadioItem = React.forwardRef<
135
139
  <DropdownMenuPrimitive.RadioItem
136
140
  ref={ref}
137
141
  className={cn(
138
- "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors",
139
- "focus:bg-muted/50 focus:text-foreground",
142
+ "relative flex cursor-pointer select-none items-center rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-[color,background-color,box-shadow]",
143
+ "focus:text-foreground",
144
+ ixRow,
140
145
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
141
146
  className,
142
147
  )}