torch-glare 2.5.2 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/apps/lib/components/ActionButton.tsx +39 -6
  2. package/apps/lib/components/Badge.tsx +5 -2
  3. package/apps/lib/components/BadgeField.tsx +1 -1
  4. package/apps/lib/components/Button.tsx +4 -4
  5. package/apps/lib/components/Calendar.tsx +7 -17
  6. package/apps/lib/components/ColorPicker.tsx +1 -1
  7. package/apps/lib/components/DataViews/data-views.tsx +1 -1
  8. package/apps/lib/components/DataViews/filters/filters.tsx +1 -1
  9. package/apps/lib/components/DataViews/header.tsx +5 -2
  10. package/apps/lib/components/DataViews/panel/section.tsx +3 -1
  11. package/apps/lib/components/DataViews/views/board-view.tsx +3 -1
  12. package/apps/lib/components/DataViews/views/inbox-view.tsx +4 -1
  13. package/apps/lib/components/DataViews/views/pane-views.tsx +5 -1
  14. package/apps/lib/components/DataViews/views/table-view.tsx +67 -8
  15. package/apps/lib/components/DataViews/views/tree-view.tsx +5 -3
  16. package/apps/lib/components/Drawer.tsx +18 -1
  17. package/apps/lib/components/FormBuilder/context.ts +9 -1
  18. package/apps/lib/components/FormBuilder/submit.tsx +11 -2
  19. package/apps/lib/components/FormBuilder/types.ts +5 -1
  20. package/apps/lib/components/FormRenderer/form-renderer.tsx +15 -3
  21. package/apps/lib/components/FormRenderer/stepper.tsx +35 -14
  22. package/apps/lib/components/FormRenderer/types.ts +1 -1
  23. package/apps/lib/components/Input.tsx +19 -4
  24. package/apps/lib/components/Popover.tsx +93 -55
  25. package/apps/lib/components/SearchableSelect.tsx +12 -11
  26. package/apps/lib/components/SearchableTree.tsx +10 -11
  27. package/apps/lib/components/SearchableTreeDialog.tsx +10 -11
  28. package/apps/lib/components/SectionBlock.tsx +14 -2
  29. package/apps/lib/components/Select.tsx +35 -11
  30. package/apps/lib/components/SimpleSelect.tsx +2 -2
  31. package/apps/lib/components/SlideDatePicker.tsx +17 -4
  32. package/apps/lib/components/Stepper.tsx +329 -180
  33. package/apps/lib/components/Switch.tsx +2 -2
  34. package/apps/lib/components/Table.tsx +55 -30
  35. package/apps/lib/components/Textarea.tsx +29 -4
  36. package/apps/lib/components/TreeDropDown.tsx +18 -6
  37. package/apps/lib/components/TreeFolder/TreeFolder.tsx +61 -61
  38. package/apps/lib/registry.json +7 -16
  39. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  40. package/docs/components/action-button.md +3 -1
  41. package/docs/components/data-views/index.md +7 -0
  42. package/docs/components/form-builder.md +4 -2
  43. package/docs/components/form-renderer.md +1 -1
  44. package/docs/components/stepper.md +119 -24
  45. package/docs/components/table.md +33 -0
  46. package/docs/how-to/forms-with-form-builder.md +3 -1
  47. package/docs/reference/tailwind-plugins.md +42 -0
  48. package/docs/tutorials/getting-started.md +32 -11
  49. package/package.json +1 -1
  50. package/apps/lib/components/FormStepper.tsx +0 -272
  51. package/docs/components/form-stepper.md +0 -250
@@ -16,7 +16,7 @@ import type {
16
16
  */
17
17
 
18
18
  export type FormRendererDisplay = "page" | "drawer";
19
- export type FieldDirection = "horizontal" | "vertical";
19
+ export type FieldDirection = "horizontal" | "vertical" | "flexible";
20
20
 
21
21
  export interface FormRendererProps<T extends FieldValues = FieldValues> {
22
22
  /** The form body — `FormRenderer.Section` / field / `FormRenderer.Stepper` JSX. */
@@ -62,7 +62,16 @@ interface TrillingProps extends HTMLAttributes<HTMLDivElement> {
62
62
  }
63
63
  export const Trilling = ({ children, className, ...props }: TrillingProps) => {
64
64
  return (
65
- <div {...props} className={cn("flex items-center justify-center h-full gap-1 py-1", className)}>
65
+ // `ps-1` mirrors the inner side of Figma's `EndField-Container` (`p-[4px]`), keeping the field's
66
+ // text clear of the end button instead of running straight into it.
67
+ //
68
+ // Logical, not physical: this slot flips to the left edge in RTL, so a `pl-1` would put the
69
+ // padding on the outside — measured as 0px of clearance and the button 8px from the edge
70
+ // instead of 4px. `padding-inline-start` follows the slot.
71
+ <div
72
+ {...props}
73
+ className={cn("flex items-center justify-center h-full gap-1 py-1 ps-1", className)}
74
+ >
66
75
  {children}
67
76
  </div>
68
77
  );
@@ -135,7 +144,10 @@ input:-webkit-autofill:active {
135
144
 
136
145
  export const GroupStyles = cva(
137
146
  [
138
- "flex w-full min-w-0 px-1 justify-center items-center",
147
+ // 3px, not 4: Figma's `EndField-Container` is `p-[4px]` and its stroke sits *inside* that
148
+ // padding, so the end button lands 4px from the field's outer edge. CSS `border-box` adds the
149
+ // 1px border on top of the padding, so 4px here would render 5px. 1 + 3 = 4.
150
+ "flex w-full min-w-0 px-[3px] justify-center items-center",
139
151
  "typography-body-small-regular",
140
152
  "border",
141
153
  "transition-all duration-200 ease-in-out",
@@ -180,14 +192,17 @@ export const GroupStyles = cva(
180
192
  "[&_input]:text-white",
181
193
  ],
182
194
  },
195
+ // Radius per Figma's `radius/*` collection: S is `radius/lg` (8px) and M is `radius/xl`
196
+ // (12px) — a deliberate jump, not consecutive steps. This `Group` is the shared field root,
197
+ // so it is also what gives `InputField` and `InnerLabelField` their corners.
183
198
  size: {
184
199
  S: [
185
200
  "h-[30px] min-h-[30px]",
186
- "rounded-[6px] [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]",
201
+ "rounded-radius-lg [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]",
187
202
  ],
188
203
  M: [
189
204
  "h-[40px] min-h-[40px]",
190
- "rounded-[8px] [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]",
205
+ "rounded-radius-xl [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]",
191
206
  ],
192
207
  },
193
208
  error: {
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { cva, VariantProps } from "class-variance-authority";
3
3
  import { cn } from "../utils/cn";
4
- import React, { useEffect, useRef } from "react";
4
+ import React, { cloneElement, isValidElement, useEffect, useRef } from "react";
5
5
  import * as PopoverPrimitive from "@radix-ui/react-popover";
6
6
  import { Slot } from "@radix-ui/react-slot";
7
7
  import { Themes } from "../utils/types";
8
8
 
9
+
9
10
  interface LocalPopOverProps extends VariantProps<typeof popoverStyles> {
10
11
  variant?: "SystemStyle" | "PresentationStyle";
11
12
  className?: string;
@@ -32,18 +33,19 @@ PopoverTrigger.displayName = PopoverPrimitive.Trigger.displayName;
32
33
  const PopoverContent = React.forwardRef<
33
34
  React.ElementRef<typeof PopoverPrimitive.Content>,
34
35
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> &
35
- LocalPopOverProps & {
36
- theme?: Themes;
37
- }
36
+ LocalPopOverProps & {
37
+ theme?: Themes;
38
+ }
38
39
  >(
39
40
  (
40
41
  {
41
42
  className,
42
43
  align = "center",
43
44
  sideOffset = 4,
44
- variant = "SystemStyle",
45
+ variant = "PresentationStyle",
45
46
  overlayBlur = false,
46
47
  theme,
48
+ children,
47
49
  ...props
48
50
  },
49
51
  ref,
@@ -58,7 +60,9 @@ const PopoverContent = React.forwardRef<
58
60
  sideOffset={sideOffset}
59
61
  className={cn(popoverStyles({ variant, overlayBlur }), className)}
60
62
  {...props}
61
- />
63
+ >
64
+ {children}
65
+ </PopoverPrimitive.Content>
62
66
  </div>
63
67
  ) : (
64
68
  <PopoverPrimitive.Content
@@ -68,7 +72,9 @@ const PopoverContent = React.forwardRef<
68
72
  sideOffset={sideOffset}
69
73
  className={cn(popoverStyles({ variant, overlayBlur }), className)}
70
74
  {...props}
71
- />
75
+ >
76
+ {children}
77
+ </PopoverPrimitive.Content>
72
78
  )}
73
79
  </PopoverPrimitive.Portal>
74
80
  ),
@@ -84,7 +90,7 @@ interface Props<T extends React.ElementType = "li">
84
90
 
85
91
  // Define the PopoverItem component with a generic type parameter
86
92
  const PopoverItem = <T extends React.ElementType = "li">({
87
- variant = "SystemStyle",
93
+ variant = "Default",
88
94
  size = "M",
89
95
  asChild,
90
96
  className,
@@ -103,72 +109,95 @@ const PopoverItem = <T extends React.ElementType = "li">({
103
109
  }
104
110
  }, [active]);
105
111
 
112
+ const itemClassName = cn(PopoverItemStyles({ variant, size, active }), className);
113
+
114
+ // The styles are a two-layer chip (see `PopoverItemStyles`): this element is the container and
115
+ // the inner div is the row that lights up, which every `[&>div]:` rule targets. It therefore has
116
+ // to exist in both branches.
117
+ if (asChild && isValidElement(children)) {
118
+ // Slot merges the item's props onto the consumer's element (e.g. a Link), so the row div has to
119
+ // be injected as that element's child — wrapping the Link instead would move the class onto the
120
+ // wrapper and leave the anchor nested inside the item rather than being it.
121
+ const child = children as React.ReactElement<{ children?: React.ReactNode }>;
122
+ return (
123
+ <Slot className={itemClassName} ref={ref as React.Ref<HTMLElement>}>
124
+ {cloneElement(child, {}, <div>{child.props.children}</div>)}
125
+ </Slot>
126
+ );
127
+ }
128
+
106
129
  return (
107
130
  <Component
108
131
  {...(props as React.ComponentPropsWithoutRef<T>)} // Spread the props dynamically
109
- className={cn(
110
- PopoverItemStyles({
111
- variant,
112
- size,
113
- active,
114
- }),
115
- className,
116
- )}
117
- /// <reference path="" />
132
+ className={itemClassName}
118
133
  ref={ref}
119
134
  >
120
- {children}
135
+ <div>{children}</div>
121
136
  </Component>
122
137
  );
123
138
  };
124
139
 
125
140
  export { Popover, PopoverTrigger, PopoverContent, PopoverItem };
126
141
 
142
+
127
143
  const PopoverItemStyles = cva(
144
+ // Ported from `MenuItemStyles` (ContextMenu/DropdownMenu) so a popover row and a menu row are the
145
+ // same object, minus the menu's grey `rgba(184,192,204,0.36)` container — a popover row sits
146
+ // directly on the panel. The inner `<div>` is still the row that lights up on hover/focus, and
147
+ // `PopoverItem` always renders it, including through `asChild`.
128
148
  [
129
- "text-content-presentation-action-light-primary",
149
+ "text-content-presentation-global-primary-light typography-body-medium-regular",
130
150
  "outline-none",
131
151
  "border",
132
152
  "border-transparent",
133
153
  "flex w-full",
134
- "gap-[8px]",
154
+ "shrink-0", // keep full row height so the popover scrolls instead of squishing
135
155
  "items-center",
136
156
  "justify-start",
137
157
  "text-overflow",
138
158
  "overflow-hidden",
139
- "px-[12px]",
140
- "rounded-[4px]",
159
+ "p-[2px]",
141
160
  "transition-all",
142
161
  "ease-in-out",
143
162
  "duration-300",
163
+ "[&>div]:flex",
164
+ "[&>div]:px-[12px]",
165
+ "[&>div]:py-[4px]",
166
+ "[&>div]:gap-2",
167
+ "[&>div]:w-full",
168
+ "[&>div]:rounded-[8px]",
169
+ "[&>div]:items-center",
170
+ "group",
144
171
  ],
145
172
  {
146
173
  variants: {
147
174
  variant: {
175
+ // The menu uses Radix's `data-highlighted`; a PopoverItem is a plain button, so the
176
+ // keyboard-highlight equivalent here is `:focus`.
148
177
  Default: [
149
- "text-content-presentation-action-light-primary",
150
- "bg-background-presentation-action-dropdown-primary",
151
- "hover:bg-background-presentation-action-hover",
152
- "hover:text-content-presentation-global-primary-inverse",
153
- "focus:bg-background-presentation-action-hover",
154
- "focus:text-content-presentation-global-primary-inverse",
155
- "disabled:text-content-presentation-state-disabled",
156
- "disabled:bg-white-00",
178
+ "text-content-presentation-global-primary-light",
179
+ "[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
180
+ "[&>div]:hover:text-black-1000",
181
+ "[&:focus>div]:bg-white-alpha-75",
182
+ "[&:focus>div]:text-black-1000",
183
+ "[&:disabled>div]:text-content-presentation-global-primary-light",
184
+ "[&:disabled>div]:opacity-50",
185
+ "[&:disabled>div]:hover:bg-transparent",
186
+ "[&:disabled>div]:hover:shadow-none",
157
187
  ],
158
188
  Warning: [
159
- "text-content-presentation-state-information",
160
- "hover:bg-background-presentation-state-information-primary",
161
- "focus:bg-background-presentation-state-information-primary",
162
- "focus:text-content-presentation-global-primary-inverse",
163
- "hover:text-content-presentation-global-primary-inverse",
189
+ "text-blue-sparkle-200",
190
+ "[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
191
+ "[&>div]:hover:text-blue-sparkle-700",
192
+ "[&:focus>div]:bg-white-alpha-75",
193
+ "[&:focus>div]:text-blue-sparkle-700",
164
194
  ],
165
195
  Negative: [
166
- "text-content-presentation-state-negative",
167
- "hover:bg-background-presentation-state-negative-primary",
168
- "hover:text-content-presentation-global-primary-inverse",
169
- "focus:bg-background-presentation-state-negative-primary",
170
- "focus:text-content-presentation-global-primary-inverse",
171
- "active:text-content-presentation-state-negative",
196
+ "text-medium-red-200",
197
+ "[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
198
+ "[&>div]:hover:text-medium-red-600",
199
+ "[&:focus>div]:bg-white-alpha-75",
200
+ "[&:focus>div]:text-medium-red-600",
172
201
  ],
173
202
  SystemStyle: [
174
203
  "bg-background-system-body-primary",
@@ -198,13 +227,13 @@ const PopoverItemStyles = cva(
198
227
  "text-content-presentation-action-light-primary",
199
228
  ],
200
229
  },
201
-
202
- defaultVariants: {
203
- variant: "Default",
204
- size: "M",
205
- active: false,
206
- disabled: false,
207
- },
230
+ },
231
+ // Same misplacement as `popoverStyles` — a sibling of `variants`, not one of them.
232
+ defaultVariants: {
233
+ variant: "Default",
234
+ size: "M",
235
+ active: false,
236
+ disabled: false,
208
237
  },
209
238
  compoundVariants: [
210
239
  {
@@ -239,22 +268,31 @@ const popoverStyles = cva(
239
268
  "bg-background-system-body-primary",
240
269
  "shadow-[0px_0px_18px_0px_rgba(0,0,0,0.75)]",
241
270
  ],
242
- // Adopts the DropdownMenu surface (`menuContentStyles`): backdrop-blurred, borderless
243
- // rounded-14 panel with a soft ambient shadow keeping the original background color.
271
+ // Figma `Dropdown-Menu-1.0` (1735:159246) — the same surface `menuContentStyles` renders,
272
+ // so a popover and a dropdown menu are indistinguishable panels.
244
273
  PresentationStyle: [
245
- "border-transparent",
274
+ // `border-0`, not `border-transparent`: the base sets `border`, and with `border-box`
275
+ // that 1px sits on top of the 4px padding, making the panel 2px wider than the design.
276
+ "border-0",
246
277
  "rounded-[14px]",
247
278
  "backdrop-blur-[21px]",
248
- "bg-background-presentation-form-base",
279
+ // The design's fill is a raw `rgba(61,64,69,0.72)` with no Figma variable behind it, so it
280
+ // is spelled out exactly as DropdownMenu/ContextMenu already spell it. It has to be
281
+ // translucent: over the previous opaque `form-base` the backdrop-blur painted nothing.
282
+ "bg-[rgba(61,64,69,0.72)]",
249
283
  "shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
284
+ // Figma's group-container stacks its rows with a 4px gutter.
285
+ "flex flex-col gap-1",
250
286
  ],
251
287
  },
252
288
  overlayBlur: {
253
289
  true: ["h-fit"],
254
290
  },
255
- defaultVariants: {
256
- variant: "PresentationStyle",
257
- },
291
+ },
292
+ // Was nested inside `variants`, where cva reads it as a variant group called
293
+ // "defaultVariants" and no default is ever applied.
294
+ defaultVariants: {
295
+ variant: "PresentationStyle",
258
296
  },
259
297
  },
260
298
  );
@@ -6,7 +6,8 @@ import { cn } from "../utils/cn";
6
6
  import { Themes } from "../utils/types";
7
7
  import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
8
8
  import { Icon, Input, Group } from "./Input";
9
- import { Button, LoadingIcon } from "./Button";
9
+ import { LoadingIcon } from "./Button";
10
+ import { ActionButton } from "./ActionButton";
10
11
  import { useClickOutside } from "../hooks/useClickOutside";
11
12
  // Reuse the exact menu-item styling so rows look identical to DropdownMenuItem.
12
13
  import { MenuItemStyles } from "./DropdownMenu";
@@ -189,7 +190,11 @@ export function SearchableSelect({
189
190
  setDropdownWidth(e.currentTarget.offsetWidth)
190
191
  }
191
192
  // Trigger styling mirrors the `Select` component's trigger so the two read identically.
192
- className={cn("flex w-full items-center gap-1 rounded-[6px] p-[4px]", className)}
193
+ // `p-[3px]` so the end button lands 4px from the outer edge once the 1px border is added.
194
+ // The old `rounded-[6px]` here was dead but dangerous: `twMerge` cannot dedupe it against
195
+ // the Group's `rounded-radius-xl` (custom scale key vs arbitrary value), so both shipped
196
+ // and the 12px only won on CSS source order.
197
+ className={cn("flex w-full items-center gap-1 p-[3px]", className)}
193
198
  >
194
199
  {icon && <Icon>{icon}</Icon>}
195
200
  <Input
@@ -212,10 +217,10 @@ export function SearchableSelect({
212
217
  "!h-[24px]": size === "M",
213
218
  })}
214
219
  />
215
- {/* Chevron toggle — boxed icon button matching the Select component. */}
216
- <Button
220
+ {/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
221
+ <ActionButton
217
222
  as="span"
218
- buttonType="icon"
223
+ size="M"
219
224
  tabIndex={-1}
220
225
  aria-label={open ? "Close" : "Open"}
221
226
  onMouseDown={(e: React.MouseEvent) => {
@@ -227,11 +232,7 @@ export function SearchableSelect({
227
232
  setOpen(true);
228
233
  }
229
234
  }}
230
- className={cn(
231
- // Same box + open-state fill as the Select trigger's chevron.
232
- "h-[32px] w-[32px] shrink-0",
233
- open && "bg-background-presentation-action-hover text-white",
234
- )}
235
+ className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
235
236
  >
236
237
  <i
237
238
  className={cn(
@@ -239,7 +240,7 @@ export function SearchableSelect({
239
240
  open && "rotate-180",
240
241
  )}
241
242
  />
242
- </Button>
243
+ </ActionButton>
243
244
  </Group>
244
245
  </PopoverTrigger>
245
246
 
@@ -5,7 +5,8 @@ import { cn } from "../utils/cn";
5
5
  import { Themes } from "../utils/types";
6
6
  import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
7
7
  import { Icon, Input, Group } from "./Input";
8
- import { Button, LoadingIcon } from "./Button";
8
+ import { LoadingIcon } from "./Button";
9
+ import { ActionButton } from "./ActionButton";
9
10
  import { useClickOutside } from "../hooks/useClickOutside";
10
11
 
11
12
  /**
@@ -236,7 +237,8 @@ export function SearchableTree<T>({
236
237
  onFocus={(e: React.FocusEvent<HTMLDivElement>) =>
237
238
  setDropdownWidth(e.currentTarget.offsetWidth)
238
239
  }
239
- className={cn("flex w-full items-center gap-1 p-1", className)}
240
+ // 3px + the 1px border = Figma's 4px from the field's outer edge.
241
+ className={cn("flex w-full items-center gap-1 p-[3px]", className)}
240
242
  >
241
243
  {icon && <Icon>{icon}</Icon>}
242
244
  {/* The field itself is the search input — typing filters the tree. */}
@@ -262,10 +264,10 @@ export function SearchableTree<T>({
262
264
  "!h-[24px]": size === "M",
263
265
  })}
264
266
  />
265
- {/* Chevron toggle — boxed icon button matching the Select component. */}
266
- <Button
267
+ {/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
268
+ <ActionButton
267
269
  as="span"
268
- buttonType="icon"
270
+ size="M"
269
271
  tabIndex={-1}
270
272
  aria-label={open ? "Close" : "Open"}
271
273
  onMouseDown={(e: React.MouseEvent) => {
@@ -278,18 +280,15 @@ export function SearchableTree<T>({
278
280
  setOpen(true);
279
281
  }
280
282
  }}
281
- className={cn(
282
- "shrink-0 h-[32px] w-[32px] rounded-[4px]",
283
- open && "bg-background-presentation-action-hover text-white",
284
- )}
283
+ className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
285
284
  >
286
285
  <i
287
286
  className={cn(
288
- "ri-arrow-down-s-line text-[20px] transition-all duration-100 ease-in-out",
287
+ "ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
289
288
  open && "rotate-180",
290
289
  )}
291
290
  />
292
- </Button>
291
+ </ActionButton>
293
292
  </Group>
294
293
  </PopoverTrigger>
295
294
 
@@ -13,7 +13,8 @@ import { cn } from "../utils/cn";
13
13
  import { Themes } from "../utils/types";
14
14
  import { Dialog, DialogTrigger, DialogContent, DialogTitle } from "./Dialog";
15
15
  import { Icon, Input, Group } from "./Input";
16
- import { Button, LoadingIcon } from "./Button";
16
+ import { LoadingIcon } from "./Button";
17
+ import { ActionButton } from "./ActionButton";
17
18
 
18
19
  /**
19
20
  * SearchableTreeDialog — a field that opens a modal Dialog to pick a node from a tree.
@@ -224,7 +225,8 @@ export function SearchableTreeDialog<T>({
224
225
  size={size === "XS" ? "S" : size}
225
226
  role="button"
226
227
  tabIndex={0}
227
- className={cn("flex w-full items-center gap-1 p-1 cursor-pointer", className)}
228
+ // 3px + the 1px border = Figma's 4px from the field's outer edge.
229
+ className={cn("flex w-full items-center gap-1 p-[3px] cursor-pointer", className)}
228
230
  >
229
231
  {icon && <Icon>{icon}</Icon>}
230
232
  <span
@@ -237,24 +239,21 @@ export function SearchableTreeDialog<T>({
237
239
  >
238
240
  {triggerLabel}
239
241
  </span>
240
- {/* Chevron toggle — boxed icon button matching the Select component. */}
241
- <Button
242
+ {/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
243
+ <ActionButton
242
244
  as="span"
243
- buttonType="icon"
245
+ size="M"
244
246
  tabIndex={-1}
245
247
  aria-label={open ? "Close" : "Open"}
246
- className={cn(
247
- "shrink-0 h-[32px] w-[32px] rounded-[4px]",
248
- open && "bg-background-presentation-action-hover text-white",
249
- )}
248
+ className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
250
249
  >
251
250
  <i
252
251
  className={cn(
253
- "ri-arrow-down-s-line text-[20px] transition-all duration-100 ease-in-out",
252
+ "ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
254
253
  open && "rotate-180",
255
254
  )}
256
255
  />
257
- </Button>
256
+ </ActionButton>
258
257
  </Group>
259
258
  </DialogTrigger>
260
259
 
@@ -14,7 +14,17 @@ const titleBadge = cva(
14
14
  Orange: "bg-red-orange-900",
15
15
  Purple: "bg-violet-900",
16
16
  Pink: "bg-medium-violet-red-900",
17
- Gray: "bg-background-presentation-badge-gray",
17
+ // Per Figma (`FormHeader10`, Color=Gray), which pairs two *variables*: the pill is
18
+ // `background/presentation/button/primary` and its label `content/presentation/global/
19
+ // primary-inverse`. They invert together — a light pill with dark text in the dark theme,
20
+ // dark pill with white text in the light one — so the text token has to ride along on the
21
+ // variant. It overrides the base `text-[#F4F4F4]` because `titleBadge` goes through `cn`
22
+ // (tailwind-merge) below; the other seven are dark palette chips that still want that base.
23
+ //
24
+ // This previously read `bg-background-presentation-badge-gray`, which is not a token at all:
25
+ // every badge colour has `-solid`/`-subtle` and none has a bare name, so the utility was
26
+ // never generated and a Gray badge painted no background whatsoever.
27
+ Gray: "bg-background-presentation-button-primary text-content-presentation-global-primary-inverse",
18
28
  },
19
29
  },
20
30
  defaultVariants: { color: "Blue" },
@@ -61,7 +71,9 @@ const rail = cva("flex w-full min-w-[300px] flex-col items-start", {
61
71
  variants: {
62
72
  variant: {
63
73
  // Hairline between each direct child (form rows).
64
- Default: "divide-y divide-gray-300",
74
+ // `divide-gray-300` was a hardcoded light gray, so in the dark theme every row separator
75
+ // rendered as a bright rgb(209,213,219) line. The token follows the theme.
76
+ Default: "divide-y divide-border-presentation-global-primary",
65
77
  // The table draws its own row borders — a divide rule would double up on the
66
78
  // table / scroller / end-action siblings.
67
79
  Table: "",
@@ -3,7 +3,7 @@ import React from "react";
3
3
  import * as SelectPrimitive from "@radix-ui/react-select";
4
4
  import { cn } from "../utils/cn";
5
5
  import { cva, VariantProps } from "class-variance-authority";
6
- import { Button } from "./Button";
6
+ import { ActionButton } from "./ActionButton";
7
7
  import { Tooltip } from "./Tooltip";
8
8
  import { MenuItemStyles } from "./DropdownMenu";
9
9
  import { Themes } from "../utils/types";
@@ -61,10 +61,11 @@ const SelectTrigger = React.forwardRef<
61
61
  {children}
62
62
  </p>
63
63
 
64
- <Button
64
+ {/* The shared in-field action button. Its box tracks the trigger height the same way
65
+ InputField's does — a 30px field takes the 22px button, a 40px field the 32px one. */}
66
+ <ActionButton
65
67
  as={"span"}
66
- buttonType="icon"
67
- size={"L"}
68
+ size={size === "XL" ? "M" : size === "S" ? "XS" : "S"}
68
69
  className={cn([
69
70
  "group-aria-expanded:bg-background-presentation-action-hover",
70
71
  "group-aria-expanded:text-white",
@@ -80,7 +81,7 @@ const SelectTrigger = React.forwardRef<
80
81
  { icon: icon },
81
82
  )}
82
83
  />
83
- </Button>
84
+ </ActionButton>
84
85
  </SelectPrimitive.Trigger>
85
86
  </Tooltip>
86
87
  );
@@ -176,7 +177,7 @@ const SelectItem = React.forwardRef<
176
177
  >
177
178
  <div>
178
179
  <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
179
- <SelectPrimitive.ItemIndicator className="ml-auto flex shrink-0">
180
+ <SelectPrimitive.ItemIndicator className="ms-auto flex shrink-0">
180
181
  <i className="ri-check-line text-[16px]" />
181
182
  </SelectPrimitive.ItemIndicator>
182
183
  </div>
@@ -253,7 +254,11 @@ const SelectContentStyles = cva(
253
254
 
254
255
  const PopoverTriggerStyles = cva(
255
256
  [
256
- "flex flex-row rounded-[8px] justify-between items-center outline-none",
257
+ // No radius here every `size` variant sets its own. `twMerge` does NOT dedupe the
258
+ // `rounded-radius-*` classes (they are custom scale keys, not values it recognises), so a
259
+ // radius on the base would survive alongside the variant's and let CSS source order pick the
260
+ // winner: `md` is generated before `lg`, so a base `lg` would silently beat size S's `md`.
261
+ "flex flex-row justify-between items-center outline-none",
257
262
  "[&_span]:text-content-presentation-action-light-primary",
258
263
  "typography-body-small-regular",
259
264
  "[&_p]:px-[10px] [&_p]:whitespace-nowrap",
@@ -299,12 +304,31 @@ const PopoverTriggerStyles = cva(
299
304
  onTable: {
300
305
  true: ["border-transparent", "bg-transparent"],
301
306
  },
307
+ // No `[&_span]:h-/w-` here: the chevron is an `ActionButton` and sizes itself from its own
308
+ // `size` prop. Those descendant rules used to force its box, and being descendant selectors
309
+ // they outranked the button's own classes — they also hit the SelectValue text span, which
310
+ // only escaped being squashed because an inline element ignores width/height.
302
311
  size: {
303
- S: ["rounded-[6px] [&_span]:h-[22px] [&_span]:w-[22px] [&_p]:typography-body-small-medium"],
304
- M: ["[&_span]:h-[26px] [&_span]:w-[26px] [&_p]:typography-body-medium-medium"],
305
- L: ["[&_span]:h-[28px] [&_span]:w-[28px] [&_p]:typography-body-large-medium"],
312
+ // 24px tall below the field scale's smallest step (30px), so it keeps its own
313
+ // `radius/md` rather than being rounded up to the 8px a real field would use.
314
+ // Heights are explicit because the trigger used to be sized by whichever chevron box the
315
+ // `[&_span]` rules imposed; now that the chevron sizes itself, the field must state its own.
316
+ //
317
+ // Every size states its padding so that `2×border + 2×padding + chevron` is exactly the
318
+ // trigger height — the chevron then sits an equal distance from all four edges instead of
319
+ // being flush to the border horizontally and centred vertically. Only XL had any padding
320
+ // before, which is why it was the only size that looked right.
321
+ //
322
+ // L and XL are the real field heights (30/40) and land on Figma's 4px. S and M have no
323
+ // Figma counterpart, so they take 3px — the most that still fits their chevron exactly.
324
+ S: ["h-[24px] p-[2px] rounded-radius-md [&_p]:typography-body-small-medium"],
325
+ // 28px and 30px — the field scale's S step (30px) is `radius/lg`.
326
+ M: ["h-[28px] p-[2px] rounded-radius-lg [&_p]:typography-body-medium-medium"],
327
+ L: ["h-[30px] p-[3px] rounded-radius-lg [&_p]:typography-body-large-medium"],
328
+ // 40px tall, so it is exactly InputField M and takes that size's `radius/xl` (12px).
329
+ // It sat at 8px before — the one place a 40px field in the system rounded like a 30px one.
306
330
  XL: [
307
- "h-[40px] p-[4px] rounded-[8px] [&_span]:h-[32px] [&_span]:w-[32px] [&_p]:typography-body-large-regular [&_p]:px-[4px]",
331
+ "h-[40px] p-[3px] rounded-radius-xl [&_p]:typography-body-large-regular [&_p]:px-[4px]",
308
332
  ],
309
333
  },
310
334
  },
@@ -13,7 +13,7 @@ const SimpleSelectDropDown = ({ className, children, onClick }: SimpleSelectDrop
13
13
  return (
14
14
  <div
15
15
  className={cn(
16
- "absolute min-w-[100px] z-[20] top-[27px] left-0",
16
+ "absolute min-w-[100px] z-[20] top-[27px] start-0",
17
17
  dropdownMenuStyles({ variant: "SystemStyle" }),
18
18
  className,
19
19
  )}
@@ -97,7 +97,7 @@ const SimpleSelectValue = ({
97
97
  "w-fit",
98
98
  "rounded-[6px]",
99
99
  "outline-none",
100
- "pl-[8px]",
100
+ "ps-[8px]",
101
101
  className,
102
102
  ])}
103
103
  >