torch-glare-mcp 1.7.2 → 1.7.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 (68) 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 +71 -67
  4. package/apps/lib/components/Button.tsx +4 -4
  5. package/apps/lib/components/Calendar.tsx +7 -17
  6. package/apps/lib/components/Card.tsx +2 -1
  7. package/apps/lib/components/ColorPicker.tsx +1 -1
  8. package/apps/lib/components/ContextMenu.tsx +65 -22
  9. package/apps/lib/components/DataViews/context.ts +2 -2
  10. package/apps/lib/components/DataViews/data-views.tsx +4 -4
  11. package/apps/lib/components/DataViews/filters/filters.tsx +1 -3
  12. package/apps/lib/components/DataViews/header.tsx +5 -2
  13. package/apps/lib/components/DataViews/panel/section.tsx +3 -1
  14. package/apps/lib/components/DataViews/views/board-view.tsx +3 -1
  15. package/apps/lib/components/DataViews/views/inbox-view.tsx +4 -1
  16. package/apps/lib/components/DataViews/views/pane-views.tsx +5 -1
  17. package/apps/lib/components/DataViews/views/table-view.tsx +201 -134
  18. package/apps/lib/components/DataViews/views/tree-view.tsx +5 -3
  19. package/apps/lib/components/Drawer.tsx +18 -1
  20. package/apps/lib/components/DropdownMenu.tsx +65 -22
  21. package/apps/lib/components/FormBuilder/context.ts +9 -1
  22. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +19 -5
  23. package/apps/lib/components/FormBuilder/submit.tsx +11 -2
  24. package/apps/lib/components/FormBuilder/types.ts +21 -1
  25. package/apps/lib/components/FormRenderer/form-renderer.tsx +30 -7
  26. package/apps/lib/components/FormRenderer/stepper.tsx +35 -14
  27. package/apps/lib/components/FormRenderer/types.ts +1 -1
  28. package/apps/lib/components/HeaderBar.tsx +51 -53
  29. package/apps/lib/components/Input.tsx +19 -4
  30. package/apps/lib/components/InputField.tsx +46 -47
  31. package/apps/lib/components/Popover.tsx +108 -56
  32. package/apps/lib/components/SearchableSelect.tsx +22 -17
  33. package/apps/lib/components/SearchableTree.tsx +33 -17
  34. package/apps/lib/components/SearchableTreeDialog.tsx +21 -12
  35. package/apps/lib/components/SectionBlock.tsx +14 -2
  36. package/apps/lib/components/Select.tsx +86 -54
  37. package/apps/lib/components/SimpleSelect.tsx +2 -2
  38. package/apps/lib/components/SlideDatePicker.tsx +15 -4
  39. package/apps/lib/components/Stepper.tsx +329 -180
  40. package/apps/lib/components/Switch.tsx +2 -2
  41. package/apps/lib/components/TabSwitch.tsx +18 -12
  42. package/apps/lib/components/Table.tsx +55 -30
  43. package/apps/lib/components/Textarea.tsx +29 -4
  44. package/apps/lib/components/TreeDropDown.tsx +18 -6
  45. package/apps/lib/components/TreeFolder/TreeFolder.tsx +61 -61
  46. package/apps/lib/layouts/FieldSection.tsx +28 -2
  47. package/docs/components/action-button.md +3 -1
  48. package/docs/components/badge-field.md +4 -4
  49. package/docs/components/context-menu.md +3 -1
  50. package/docs/components/data-views/examples/filters.md +0 -1
  51. package/docs/components/data-views/index.md +8 -17
  52. package/docs/components/dropdown-menu.md +3 -0
  53. package/docs/components/form-builder.md +31 -3
  54. package/docs/components/form-renderer.md +1 -1
  55. package/docs/components/header-bar.md +3 -2
  56. package/docs/components/input-field.md +3 -3
  57. package/docs/components/select.md +1 -1
  58. package/docs/components/stepper.md +119 -24
  59. package/docs/components/table.md +33 -0
  60. package/docs/how-to/forms-with-form-builder.md +3 -1
  61. package/docs/migration/changelog.md +13 -0
  62. package/docs/reference/tailwind-plugins.md +42 -0
  63. package/docs/tutorials/getting-started.md +32 -11
  64. package/package.json +1 -1
  65. package/registry.json +7 -17
  66. package/apps/lib/components/DataViews/filters/summary.tsx +0 -65
  67. package/apps/lib/components/FormStepper.tsx +0 -272
  68. package/docs/components/form-stepper.md +0 -250
@@ -4,12 +4,24 @@ import { Button } from "./Button";
4
4
  import { cn } from "../utils/cn";
5
5
  import { ButtonVariant, Themes } from "../utils/types";
6
6
 
7
- const buttonVariants = cva("!rounded-[4px]", {
7
+ /**
8
+ * The button that sits at the end of an input field — Figma `ActionButton` (Type=DropDown).
9
+ *
10
+ * Sizes are Figma's exactly: Xs 18, S 22, M 32, with `radius/sm` on the two small ones and
11
+ * `radius/lg` on M.
12
+ *
13
+ * The `!` on the radius is load-bearing. `twMerge` does not dedupe `rounded-radius-*` — they are
14
+ * custom scale keys, not values it recognises — so this radius and the one coming from `Button`'s
15
+ * own `size` variant both survive, and CSS source order picks the winner. Tailwind emits the scale
16
+ * in config order (sm before md before lg), so without `!` size S would render Button M's 6px
17
+ * instead of its own 4px.
18
+ */
19
+ const buttonVariants = cva("", {
8
20
  variants: {
9
21
  size: {
10
- XS: "h-[18px] w-[18px] text-[12px]",
11
- S: "h-[22px] w-[22px] text-[12px]",
12
- M: "h-[32px] w-[32px] text-[18px]",
22
+ XS: "h-[18px] w-[18px] text-[12px] !rounded-radius-sm",
23
+ S: "h-[22px] w-[22px] text-[12px] !rounded-radius-sm",
24
+ M: "h-[32px] w-[32px] text-[18px] !rounded-radius-lg",
13
25
  },
14
26
  },
15
27
  defaultVariants: {
@@ -17,6 +29,23 @@ const buttonVariants = cva("!rounded-[4px]", {
17
29
  },
18
30
  });
19
31
 
32
+ /**
33
+ * Default/hover/disabled colours as Figma binds them. Applied only when the caller passes no
34
+ * `variant`, so anything asking for a specific Button variant still gets it.
35
+ *
36
+ * `action-secondary` and `action-disabled` already match `Button`'s `PrimeStyle` equivalents; the
37
+ * real difference is hover, where `PrimeStyle` uses `button-hover` and the design uses
38
+ * `action-hover`.
39
+ */
40
+ const actionTokens = [
41
+ "bg-background-presentation-action-secondary",
42
+ "text-content-presentation-action-light-primary",
43
+ "hover:bg-background-presentation-action-hover",
44
+ "hover:text-content-presentation-global-hover",
45
+ "disabled:bg-background-presentation-action-disabled",
46
+ "disabled:text-content-presentation-state-disabled",
47
+ ];
48
+
20
49
  interface Props
21
50
  extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
22
51
  is_loading?: boolean;
@@ -29,7 +58,6 @@ interface Props
29
58
  export const ActionButton = function ({
30
59
  size,
31
60
  asChild,
32
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from {...props} spread
33
61
  as: Tag = "button",
34
62
  className,
35
63
  variant,
@@ -42,14 +70,19 @@ export const ActionButton = function ({
42
70
  <Button
43
71
  theme={theme}
44
72
  asChild={asChild}
73
+ // Forwarded, not swallowed: the chevron inside a field renders `as="span"` because it is
74
+ // nested in a clickable trigger, and a <button> cannot contain a <button>.
75
+ as={Tag}
45
76
  buttonType="icon"
46
- type={type}
77
+ // `type` is only meaningful on a real button; on a span it would be an invalid attribute.
78
+ type={Tag === "button" ? type : undefined}
47
79
  size={size == "XS" ? "S" : size == "S" ? "M" : size == "M" ? "L" : "S"}
48
80
  variant={variant}
49
81
  className={cn(
50
82
  buttonVariants({
51
83
  size,
52
84
  }),
85
+ !variant && actionTokens,
53
86
  className,
54
87
  )}
55
88
  {...props}
@@ -118,7 +118,10 @@ const subtleCompoundVariants = [
118
118
  export const badgeStyles = cva(
119
119
  [
120
120
  "inline-flex items-center justify-center w-fit",
121
- "rounded-[6px]",
121
+ // `radius/md` (6px), read off the Badge master component in Figma. Note the badge instances
122
+ // nested inside InnerLabelField render at `radius/lg` — that is an instance override, not the
123
+ // component's own value, and reading it as the spec is how this briefly shipped as 8px.
124
+ "rounded-radius-md",
122
125
  "transition-all duration-200 ease-in-out",
123
126
  "whitespace-nowrap",
124
127
  "[&_i]:!leading-none",
@@ -215,7 +218,7 @@ export const Badge: React.FC<BadgeProps> = ({
215
218
  }
216
219
  }}
217
220
  className={cn(
218
- "rounded-[4px]",
221
+ "rounded-radius-sm",
219
222
  "flex items-center justify-center cursor-pointer",
220
223
  "hover:bg-background-presentation-action-secondary",
221
224
  "transition-colors duration-150",
@@ -9,7 +9,7 @@ import {
9
9
  FocusEvent,
10
10
  } from "react";
11
11
  import { cn } from "../utils/cn";
12
- import { Tooltip, ToolTipSide } from "./Tooltip";
12
+ import { ToolTipSide } from "./Tooltip";
13
13
  import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
14
14
  import { Themes } from "../utils/types";
15
15
  import { Icon, Input, Group, Trilling } from "./Input";
@@ -22,8 +22,13 @@ interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "va
22
22
  size?: "XS" | "S" | "M"; // this is used to change the size style of the component
23
23
  variant?: "SystemStyle" | "PresentationStyle";
24
24
  icon?: ReactNode; // to add left side icon if you pass it
25
- errorMessage?: string; // to show tooltip component when error_message not null
25
+ /** Marks the field invalid: any non-undefined value turns on the negative border. */
26
+ errorMessage?: string;
26
27
  onTable?: boolean; // to change the border style of the component when it is on table
28
+ /**
29
+ * @deprecated Ignored. The error tooltip was removed — an invalid field is shown by its negative
30
+ * border alone. Kept so existing call sites keep compiling; it will go in a future major.
31
+ */
27
32
  toolTipSide?: ToolTipSide;
28
33
  label?: string;
29
34
  required?: boolean;
@@ -45,6 +50,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
45
50
  errorMessage,
46
51
  onTable,
47
52
  variant = "PresentationStyle",
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op, destructured to keep it out of the {...props} spread
48
54
  toolTipSide,
49
55
  className,
50
56
  actionButton,
@@ -102,75 +108,73 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
102
108
 
103
109
  return (
104
110
  <Popover open={isPopoverOpen}>
105
- <Tooltip toolTipSide={toolTipSide} open={errorMessage !== undefined} text={errorMessage}>
106
- <PopoverTrigger asChild>
107
- <Group
108
- dir={dir}
109
- error={errorMessage !== undefined}
110
- onTable={onTable}
111
- data-theme={theme}
112
- variant={variant}
113
- tabIndex={isPopoverOpen ? 0 : -1}
114
- onKeyDown={handleKeyDown}
115
- size={size === "XS" ? "S" : size}
116
- ref={inputGroupRef}
117
- onFocus={(e: FocusEvent<HTMLDivElement>) => {
118
- setDropDownListWidth(e.currentTarget.offsetWidth);
111
+ <PopoverTrigger asChild>
112
+ <Group
113
+ dir={dir}
114
+ error={errorMessage !== undefined}
115
+ onTable={onTable}
116
+ data-theme={theme}
117
+ variant={variant}
118
+ tabIndex={isPopoverOpen ? 0 : -1}
119
+ onKeyDown={handleKeyDown}
120
+ size={size === "XS" ? "S" : size}
121
+ ref={inputGroupRef}
122
+ onFocus={(e: FocusEvent<HTMLDivElement>) => {
123
+ setDropDownListWidth(e.currentTarget.offsetWidth);
124
+ }}
125
+ className={cn(
126
+ "flex gap-1 flex-row w-full relative p-1 flex-nowrap overflow-hidden justify-end items-center",
127
+ {
128
+ "flex-wrap justify-start": isPopoverOpen,
129
+ "h-fit": isPopoverOpen,
130
+ },
131
+ className,
132
+ )}
133
+ >
134
+ {icon && <Icon>{icon}</Icon>}
135
+
136
+ {selectedTagsStack.map((tag, index) => (
137
+ <Badge
138
+ key={tag.id}
139
+ size={size}
140
+ color={tag.variant as VariantProps<typeof badgeStyles>["color"]}
141
+ label={tag.name}
142
+ isClosable={true}
143
+ onClose={() => handleUnselectTag(tag.id)}
144
+ className={focusedTagIndex === index ? "ring-2 ring-blue-500" : ""}
145
+ tabIndex={focusedTagIndex === index ? 0 : -1}
146
+ />
147
+ ))}
148
+
149
+ <Input
150
+ {...props}
151
+ value={searchTags}
152
+ onChange={(e) => {
153
+ filterTagsBySearch(e.target.value);
119
154
  }}
155
+ onFocus={(e) => {
156
+ props.onFocus?.(e);
157
+ setFocusedTagIndex(null);
158
+ setIsPopoverOpen(true);
159
+ }}
160
+ ref={inputRef}
120
161
  className={cn(
121
- "flex gap-1 flex-row w-full relative p-1 flex-nowrap overflow-hidden justify-end items-center",
162
+ "min-w-[100px] w-full", // Added w-full to Input
122
163
  {
123
- "flex-wrap justify-start": isPopoverOpen,
124
- "h-fit": isPopoverOpen,
164
+ "!h-[18px]": size === "XS",
165
+ "!h-[22px]": size === "S",
166
+ "!h-[24px]": size === "M",
125
167
  },
126
- className,
127
- )}
128
- >
129
- {icon && <Icon>{icon}</Icon>}
130
-
131
- {selectedTagsStack.map((tag, index) => (
132
- <Badge
133
- key={tag.id}
134
- size={size}
135
- color={tag.variant as VariantProps<typeof badgeStyles>["color"]}
136
- label={tag.name}
137
- isClosable={true}
138
- onClose={() => handleUnselectTag(tag.id)}
139
- className={focusedTagIndex === index ? "ring-2 ring-blue-500" : ""}
140
- tabIndex={focusedTagIndex === index ? 0 : -1}
141
- />
142
- ))}
143
-
144
- <Input
145
- {...props}
146
- value={searchTags}
147
- onChange={(e) => {
148
- filterTagsBySearch(e.target.value);
149
- }}
150
- onFocus={(e) => {
151
- props.onFocus?.(e);
152
- setFocusedTagIndex(null);
153
- setIsPopoverOpen(true);
154
- }}
155
- ref={inputRef}
156
- className={cn(
157
- "min-w-[100px] w-full", // Added w-full to Input
158
- {
159
- "!h-[18px]": size === "XS",
160
- "!h-[22px]": size === "S",
161
- "!h-[24px]": size === "M",
162
- },
163
- )}
164
- />
165
- {actionButton && (
166
- <Trilling className="py-0">
167
- {/* Keep the ActionButton right aligned */}
168
- {actionButton}
169
- </Trilling>
170
168
  )}
171
- </Group>
172
- </PopoverTrigger>
173
- </Tooltip>
169
+ />
170
+ {actionButton && (
171
+ <Trilling className="py-0">
172
+ {/* Keep the ActionButton right aligned */}
173
+ {actionButton}
174
+ </Trilling>
175
+ )}
176
+ </Group>
177
+ </PopoverTrigger>
174
178
 
175
179
  <PopoverContent
176
180
  dir={dir}
@@ -222,7 +226,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
222
226
  fill="black"
223
227
  />
224
228
  </svg>
225
- <p className="text-black-1000 text-right text-[12px] font-[510] leading-[148%]">
229
+ <p className="text-black-1000 text-end text-[12px] font-[510] leading-[148%]">
226
230
  {addLabel}
227
231
  </p>
228
232
  </div>
@@ -248,10 +248,10 @@ export const buttonVariants = cva(
248
248
  ],
249
249
  },
250
250
  size: {
251
- S: "h-[22px] py-[2px] px-[6px] typography-body-small-medium rounded-[4px] [&_i]:text-[12px]",
252
- M: "h-[28px] py-[2px] px-[14px] typography-body-large-medium rounded-[6px] [&_i]:text-[18px]",
253
- L: "h-[34px] py-[5px] px-[22px] typography-body-large-medium rounded-[8px] [&_i]:text-[20px]",
254
- XL: "h-[40px] py-[8px] px-[30px] typography-headers-medium-medium rounded-[8px] [&_i]:text-[22px]",
251
+ S: "h-[22px] py-[2px] px-[6px] typography-body-small-medium rounded-radius-sm [&_i]:text-[12px]",
252
+ M: "h-[28px] py-[2px] px-[14px] typography-body-large-medium rounded-radius-md [&_i]:text-[18px]",
253
+ L: "h-[34px] py-[5px] px-[22px] typography-body-large-medium rounded-radius-lg [&_i]:text-[20px]",
254
+ XL: "h-[40px] py-[8px] px-[30px] typography-headers-medium-medium rounded-radius-lg [&_i]:text-[22px]",
255
255
  },
256
256
  is_loading: {
257
257
  true: "[&_i]:hidden",
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { DayPicker } from "react-day-picker";
5
5
  import "react-day-picker/style.css";
6
6
  import { cn } from "../utils/cn";
7
- import { Button } from "./Button";
7
+ import { ActionButton } from "./ActionButton";
8
8
  import { SimpleSelectValue, SimpleSelectItem } from "./SimpleSelect";
9
9
 
10
10
  export type CalendarProps = React.ComponentProps<typeof DayPicker>;
@@ -30,24 +30,14 @@ const Calendar = ({
30
30
  Nav: (e) => {
31
31
  return (
32
32
  <div className="w-full flex items-center justify-between absolute top-0 left-0 p-[6px]">
33
- <Button
34
- onClick={e.onPreviousClick}
35
- buttonType="icon"
36
- variant="PrimeStyle"
37
- size="M"
38
- className="w-[26px] h-[26px]"
39
- >
33
+ {/* Popover navigation rather than an in-field button, so it keeps its own 26px box
34
+ and only adopts the shared action colours and radius. */}
35
+ <ActionButton onClick={e.onPreviousClick} size="S" className="w-[26px] h-[26px]">
40
36
  <i className="ri-arrow-left-s-line"></i>
41
- </Button>
42
- <Button
43
- onClick={e.onNextClick}
44
- buttonType="icon"
45
- variant="PrimeStyle"
46
- size="M"
47
- className="w-[26px] h-[26px]"
48
- >
37
+ </ActionButton>
38
+ <ActionButton onClick={e.onNextClick} size="S" className="w-[26px] h-[26px]">
49
39
  <i className="ri-arrow-right-s-line"></i>
50
- </Button>
40
+ </ActionButton>
51
41
  </div>
52
42
  );
53
43
  },
@@ -6,7 +6,8 @@ import { cva, type VariantProps } from "class-variance-authority";
6
6
  export const cardStyles = cva(
7
7
  [
8
8
  "flex flex-col justify-start",
9
- "gap-2 rounded-[12px] border",
9
+ // `radius/xl` is 12px — the same value this literal had, now taken from the shared scale.
10
+ "gap-2 rounded-radius-xl border",
10
11
  "transition-all ease-in-out duration-200",
11
12
  "p-[16px]",
12
13
  "border-border-presentation-global-primary",
@@ -404,7 +404,7 @@ export const ColorPicker = forwardRef<HTMLElement, ColorPickerProps>(
404
404
  }}
405
405
  className={NUM_INPUT_CLS}
406
406
  />
407
- <span className="pr-1 typography-body-small-regular text-content-presentation-action-light-secondary">
407
+ <span className="pe-1 typography-body-small-regular text-content-presentation-action-light-secondary">
408
408
  %
409
409
  </span>
410
410
  </Group>
@@ -139,16 +139,27 @@ const ContextMenuContent = React.forwardRef<
139
139
  data-theme={theme}
140
140
  ref={ref}
141
141
  collisionPadding={collisionPadding}
142
- // Cap at maxHeight, but never exceed the space Radix has after collision
143
- // handling. The menu scrolls (overflow on the surface) past this height.
142
+ // Cap at maxHeight, but never exceed the space Radix has after collision handling. The
143
+ // `100vh` fallback is load-bearing: an undefined var invalidates the whole `min()`, so
144
+ // `max-height` would resolve to `none` and the panel — which no longer scrolls itself —
145
+ // would grow unbounded with its rows clipped and unreachable.
144
146
  style={{
145
- maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height))`,
147
+ maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height, 100vh))`,
146
148
  ...style,
147
149
  }}
148
150
  className={cn(menuContentStyles({ variant }), className)}
149
151
  {...props}
150
152
  >
151
- {autoGroup ? autoGroupChildren(children) : children}
153
+ {/* Dedicated scroll viewport, matching Select's: the cap lives on the panel above, this
154
+ fills what is left and scrolls. `min-h-0` is what makes it work — a flex item will not
155
+ shrink below its content, so without it the list grows past the panel and the panel's
156
+ `overflow-hidden` just clips the rows with no scrollbar.
157
+
158
+ `gap-1` is re-declared here because `autoGroupChildren` emits several siblings (a group,
159
+ a label, a separator…) and this is now the element they are siblings within. */}
160
+ <div className="flex flex-col gap-1 flex-1 min-h-0 overflow-y-auto overflow-x-hidden rounded-[10px] scrollbar-hide">
161
+ {autoGroup ? autoGroupChildren(children) : children}
162
+ </div>
152
163
  </ContextMenuPrimitive.Content>
153
164
  </ContextMenuPrimitive.Portal>
154
165
  ),
@@ -180,18 +191,45 @@ const ContextMenuSubContent = React.forwardRef<
180
191
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> & {
181
192
  variant?: "PresentationStyle";
182
193
  autoGroup?: boolean;
194
+ maxHeight?: number;
183
195
  }
184
- >(({ className, variant = "PresentationStyle", autoGroup = true, children, ...props }, ref) => (
185
- <ContextMenuPrimitive.Portal>
186
- <ContextMenuPrimitive.SubContent
187
- ref={ref}
188
- className={cn(menuContentStyles({ variant }), className)}
189
- {...props}
190
- >
191
- {autoGroup ? autoGroupChildren(children) : children}
192
- </ContextMenuPrimitive.SubContent>
193
- </ContextMenuPrimitive.Portal>
194
- ));
196
+ >(
197
+ (
198
+ {
199
+ className,
200
+ variant = "PresentationStyle",
201
+ autoGroup = true,
202
+ collisionPadding = 8,
203
+ maxHeight = 320,
204
+ // Destructured out of `{...props}` so the spread below cannot clobber the cap. Radix
205
+ // re-publishes the namespaced available-height var on SubContent, so the same expression
206
+ // Content uses works here unchanged.
207
+ style,
208
+ children,
209
+ ...props
210
+ },
211
+ ref,
212
+ ) => (
213
+ <ContextMenuPrimitive.Portal>
214
+ <ContextMenuPrimitive.SubContent
215
+ ref={ref}
216
+ collisionPadding={collisionPadding}
217
+ style={{
218
+ maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height, 100vh))`,
219
+ ...style,
220
+ }}
221
+ className={cn(menuContentStyles({ variant }), className)}
222
+ {...props}
223
+ >
224
+ {/* Same panel-clips / viewport-scrolls split as Content. A submenu is a peer surface, so it
225
+ shares the 320px default rather than getting a smaller one of its own. */}
226
+ <div className="flex flex-col gap-1 flex-1 min-h-0 overflow-y-auto overflow-x-hidden rounded-[10px] scrollbar-hide">
227
+ {autoGroup ? autoGroupChildren(children) : children}
228
+ </div>
229
+ </ContextMenuPrimitive.SubContent>
230
+ </ContextMenuPrimitive.Portal>
231
+ ),
232
+ );
195
233
  ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
196
234
 
197
235
  const ContextMenuItem = React.forwardRef<
@@ -487,16 +525,19 @@ const menuContentStyles = cva(
487
525
  "rounded-[14px]",
488
526
  "min-w-[240px]",
489
527
  "outline-none",
490
- "overflow-y-auto",
491
- "overflow-x-hidden",
528
+ // The panel clips; the inner viewport below owns scrolling. Height is capped inline on the
529
+ // Content element from `min(maxHeight, available-height)` — no `max-h-*` class here on
530
+ // purpose, so the inline value governs.
531
+ "overflow-hidden",
492
532
  // Only animate the OPEN (enter) state. An exit animation on [data-state=closed]
493
533
  // holds the old DOM node during close, which breaks close/reposition on a
494
534
  // second right-click (Radix issue #2572).
495
535
  "data-[state=open]:animate-in",
496
536
  "data-[state=open]:fade-in-0",
497
- "scrollbar-hide",
498
537
  "backdrop-blur-[21px]",
499
- "flex gap-1 flex-col",
538
+ // No `gap` here: the panel has exactly one child (the scroll viewport), so a gap between
539
+ // siblings has nothing to act on. The 4px between groups/labels lives on that viewport.
540
+ "flex flex-col",
500
541
  ],
501
542
  {
502
543
  variants: {
@@ -506,9 +547,11 @@ const menuContentStyles = cva(
506
547
  "shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
507
548
  ],
508
549
  },
509
- defaultVariants: {
510
- variant: "PresentationStyle",
511
- },
550
+ },
551
+ // Was nested inside `variants`, where cva reads it as a variant group named
552
+ // "defaultVariants" and no default is ever applied. `menuGroupStyles` below has it right.
553
+ defaultVariants: {
554
+ variant: "PresentationStyle",
512
555
  },
513
556
  },
514
557
  );
@@ -129,8 +129,8 @@ export interface FiltersContextValue {
129
129
  setFilters: (filters: FilterState) => void;
130
130
  /**
131
131
  * What each filter control is, read off the `FormBuilder` children of `DataViews.Filters` —
132
- * never derived from the rows. The root collects them as well, so `Filters.Summary` can label a
133
- * chip even when it is rendered outside `Filters`.
132
+ * never derived from the rows. The root collects them as well, so a consumer such as
133
+ * `Filters.Presets` can resolve a field by path even when it is rendered outside `Filters`.
134
134
  */
135
135
  filterFields: readonly FilterFieldDescriptor[];
136
136
  }
@@ -264,9 +264,9 @@ function DataViewsRoot({
264
264
  [isPanelOpen, setPanelOpen],
265
265
  );
266
266
 
267
- // The descriptors as well as the value: `Filters.Summary` needs labels for its chips, and it is
268
- // routinely rendered outside `Filters` — above the table, in a toolbar — where it cannot reach
269
- // the context `Filters` provides to its own children.
267
+ // The descriptors as well as the value: a consumer such as `Filters.Presets` resolves a field by
268
+ // path, and may be rendered outside `Filters` — above the table, in a toolbar — where it cannot
269
+ // reach the context `Filters` provides to its own children.
270
270
  const filterFields = useMemo(() => collectFilterFields(children), [children]);
271
271
 
272
272
  const setFilters = useCallback(
@@ -311,7 +311,7 @@ function DataViewsRoot({
311
311
  inherits it rather than landing on the black shell.
312
312
 
313
313
  No border: Figma draws one, but every view already brings its own edge — the
314
- table its header rule, the split views their panel borders — so it only ever
314
+ table and the split views each draw their own panel border — so it only ever
315
315
  read as a second outline around the first. */}
316
316
  <div className="bg-background-presentation-form-base flex flex-1 overflow-hidden rounded-[16px]">
317
317
  {/* Clip the scrollable surface to the parent radius minus its 1px border
@@ -10,7 +10,6 @@ import { FiltersContext, useDataViewsFilters } from "../context";
10
10
  import { collectFilterFields, renderFields } from "./children";
11
11
  import { Custom } from "./custom";
12
12
  import { Presets } from "./presets";
13
- import { Summary } from "./summary";
14
13
  import { Sync } from "./sync";
15
14
  import { toFormValues } from "./values";
16
15
  import type { FiltersProps } from "../types";
@@ -95,7 +94,7 @@ function FiltersRoot({
95
94
  ) : (
96
95
  // Reading the colour from a token rather than a `text-white` literal is what lets
97
96
  // the same controls sit in the dark rail and in the light content area.
98
- <h3 className="text-content-presentation-global-primary text-[18px] font-[510] leading-[1.32] tracking-[-0.01em]">
97
+ <h3 className="text-content-presentation-global-primary min-w-0 truncate text-[18px] font-[510] leading-[1.32] tracking-[-0.01em]">
99
98
  {title}
100
99
  </h3>
101
100
  ))}
@@ -155,5 +154,4 @@ function FiltersRoot({
155
154
  export const Filters = Object.assign(FiltersRoot, {
156
155
  Presets,
157
156
  Custom,
158
- Summary,
159
157
  });
@@ -44,8 +44,11 @@ export function Header({ title, children, className }: HeaderProps) {
44
44
  >
45
45
  {title !== undefined && (
46
46
  <>
47
- <div className="flex h-9 shrink-0 items-center gap-2 rounded-[12px] border border-[#434446] bg-[#252729] px-[10px]">
48
- <span className="text-[28px] font-[510] uppercase leading-[1.19] text-white">
47
+ {/* `min-w-0`, not `shrink-0`: the pill has to be able to give up width, or a long title
48
+ pushes the view switch, actions and panel toggle off the end of the bar. It yields
49
+ first and the title ellipsises instead. */}
50
+ <div className="flex h-9 min-w-0 items-center gap-2 rounded-[12px] border border-[#434446] bg-[#252729] px-[10px]">
51
+ <span className="truncate text-[28px] font-[510] uppercase leading-[1.19] text-white">
49
52
  {title}
50
53
  </span>
51
54
  </div>
@@ -40,7 +40,9 @@ export function Section({
40
40
  aria-controls={bodyId}
41
41
  />
42
42
  ) : (
43
- <h3 className="text-content-presentation-global-primary text-[18px] font-[510] leading-[1.32] tracking-[-0.01em]">
43
+ // No `min-w-0` needed here, unlike the other headings: the parent is `flex flex-col`,
44
+ // so this already stretches to the column's width rather than sizing to its text.
45
+ <h3 className="text-content-presentation-global-primary truncate text-[18px] font-[510] leading-[1.32] tracking-[-0.01em]">
44
46
  {title}
45
47
  </h3>
46
48
  ))}
@@ -356,7 +356,9 @@ function ColumnHeader({
356
356
  COLUMN_BG[group.color ?? "gray"],
357
357
  )}
358
358
  >
359
- <h3 className="typography-headers-small-medium text-content-presentation-global-primary-light">
359
+ {/* `min-w-0` because this is a flex item: without it the heading refuses to shrink below its
360
+ text and a long group name pushes the action button out of the column. */}
361
+ <h3 className="typography-headers-small-medium text-content-presentation-global-primary-light min-w-0 truncate">
360
362
  {group.label}
361
363
  </h3>
362
364
  {onAction && (
@@ -67,9 +67,12 @@ function InboxViewImpl({
67
67
  )}
68
68
  >
69
69
  <li className="border-border-presentation-global-primary border-b px-3 py-2">
70
+ {/* `block` before `truncate`: the parent is a plain `<li>`, so this span would otherwise
71
+ be inline — and `overflow` has no effect on an inline box, which would leave the
72
+ ellipsis silently doing nothing. */}
70
73
  <span
71
74
  style={{ fontFeatureSettings: "'cv05' on" }}
72
- className="typography-display-medium-medium text-content-presentation-global-primary uppercase"
75
+ className="typography-display-medium-medium text-content-presentation-global-primary block truncate uppercase"
73
76
  >
74
77
  {titleField?.label ?? "inbox"}
75
78
  </span>
@@ -134,7 +134,11 @@ function useIsActive(element: { value?: string }, defaultValue: string) {
134
134
  export const PaneTable = markPaneView(function PaneTable(props: TreePaneTableProps) {
135
135
  const active = useIsActive({ value: props.value }, "table");
136
136
  if (!active) return null;
137
- return <TableView {...withoutTabProps(props)} />;
137
+ // Standalone, `TableView` draws its own border and radius so it reads as a separated surface.
138
+ // In here it is not standalone: the pane around it already draws exactly that border and radius
139
+ // (`tree-view.tsx`), so leaving the table's on nests one inside the other a pixel apart.
140
+ // `TreePaneTableProps` omits `className`, so there is no caller value to merge with.
141
+ return <TableView {...withoutTabProps(props)} className="rounded-none border-0" />;
138
142
  }, { defaultValue: "table", defaultLabel: "List", defaultIcon: <Table2 /> });
139
143
 
140
144
  /**