@vvedantb/ui 0.17.1 → 0.18.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.
Files changed (129) hide show
  1. package/README.md +251 -3
  2. package/package.json +13 -5
  3. package/shell/src/SidebarResize.tsx +1 -4
  4. package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
  5. package/src/ai-elements/activity-question-utils.ts +26 -0
  6. package/src/ai-elements/activity-question.tsx +159 -0
  7. package/src/ai-elements/activity-shared.tsx +459 -0
  8. package/src/ai-elements/activity-step-detail.tsx +115 -0
  9. package/src/ai-elements/activity-step-label.ts +981 -0
  10. package/src/ai-elements/activity-tasks-utils.ts +199 -0
  11. package/src/ai-elements/activity-tasks.tsx +770 -0
  12. package/src/ai-elements/artifact.tsx +162 -52
  13. package/src/ai-elements/attachments.tsx +62 -32
  14. package/src/ai-elements/code-block.tsx +76 -22
  15. package/src/ai-elements/context.tsx +370 -0
  16. package/src/ai-elements/conversation.tsx +186 -62
  17. package/src/ai-elements/file-chip.tsx +53 -0
  18. package/src/ai-elements/message.tsx +161 -50
  19. package/src/ai-elements/model-picker-content.tsx +505 -0
  20. package/src/ai-elements/model-picker-types.ts +56 -0
  21. package/src/ai-elements/model-picker.tsx +301 -0
  22. package/src/ai-elements/model-selector.tsx +102 -21
  23. package/src/ai-elements/plan.tsx +130 -0
  24. package/src/ai-elements/prompt-input-settings.tsx +144 -0
  25. package/src/ai-elements/prompt-input-speech.tsx +189 -0
  26. package/src/ai-elements/prompt-input-traits.tsx +281 -0
  27. package/src/ai-elements/prompt-input.tsx +320 -134
  28. package/src/ai-elements/provider-icon.tsx +133 -0
  29. package/src/ai-elements/queue.tsx +293 -9
  30. package/src/ai-elements/shimmer.tsx +72 -1
  31. package/src/ai-elements/simple-model-ladder.tsx +108 -0
  32. package/src/ai-elements/test-results.tsx +510 -0
  33. package/src/ai-elements/trace.tsx +243 -0
  34. package/src/ai-elements/use-streaming-disclosure.ts +50 -0
  35. package/src/ai-elements/web-preview.tsx +349 -0
  36. package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
  37. package/src/index.ts +66 -12
  38. package/src/kibo/avatar-stack.tsx +40 -0
  39. package/src/kibo/contribution-graph.tsx +539 -0
  40. package/src/kibo/cursor.tsx +72 -0
  41. package/src/kibo/gantt-features.tsx +439 -0
  42. package/src/kibo/gantt-header.tsx +181 -0
  43. package/src/kibo/gantt-provider.tsx +657 -0
  44. package/src/kibo/gantt-sidebar.tsx +157 -0
  45. package/src/kibo/gantt-timeline.tsx +154 -0
  46. package/src/kibo/index.ts +151 -0
  47. package/src/kibo/kanban.tsx +338 -0
  48. package/src/kibo/kanbanDropTarget.ts +25 -0
  49. package/src/kibo/list.tsx +162 -0
  50. package/src/kibo/theme-switcher.tsx +109 -0
  51. package/src/kibo/video-player.tsx +237 -0
  52. package/src/markdown/Markdown.tsx +112 -0
  53. package/src/markdown/MarkdownBlockquote.tsx +45 -0
  54. package/src/markdown/MarkdownCodeBlock.tsx +197 -0
  55. package/src/markdown/MarkdownInlineCode.tsx +40 -0
  56. package/src/markdown/MarkdownLink.tsx +92 -0
  57. package/src/markdown/codeFence.ts +57 -0
  58. package/src/markdown/codeHighlight.ts +138 -0
  59. package/src/markdown/incrementalBlocks.ts +182 -0
  60. package/src/markdown/index.ts +3 -0
  61. package/src/markdown/markdownClipboard.ts +178 -0
  62. package/src/markdown/mermaid.ts +155 -0
  63. package/src/markdown/rehypeGithubAlerts.ts +80 -0
  64. package/src/motion/presets.ts +24 -0
  65. package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
  66. package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
  67. package/src/ui/_menu-parts.tsx +29 -0
  68. package/src/ui/accordion.alt.tsx +46 -53
  69. package/src/ui/accordion.tsx +59 -10
  70. package/src/ui/avatar.tsx +56 -0
  71. package/src/ui/badge.tsx +45 -21
  72. package/src/ui/border-beam.tsx +97 -0
  73. package/src/ui/button-group.tsx +50 -21
  74. package/src/ui/button.tsx +67 -45
  75. package/src/ui/calendar.tsx +48 -5
  76. package/src/ui/card.tsx +77 -8
  77. package/src/ui/carousel.tsx +60 -0
  78. package/src/ui/centered-spinner.tsx +30 -0
  79. package/src/ui/checkbox.tsx +34 -16
  80. package/src/ui/clear-input.alt.tsx +33 -75
  81. package/src/ui/clear-input.tsx +140 -15
  82. package/src/ui/collapsible.tsx +40 -2
  83. package/src/ui/command.tsx +143 -70
  84. package/src/ui/context-menu.tsx +159 -93
  85. package/src/ui/crossfade-icon.tsx +123 -0
  86. package/src/ui/dialog.tsx +154 -70
  87. package/src/ui/dropdown-menu.tsx +177 -117
  88. package/src/ui/hover-card.tsx +59 -17
  89. package/src/ui/input-group.alt.tsx +83 -83
  90. package/src/ui/input-group.tsx +129 -58
  91. package/src/ui/input.tsx +13 -4
  92. package/src/ui/label.tsx +11 -8
  93. package/src/ui/list-row.tsx +232 -0
  94. package/src/ui/loading-state.tsx +215 -0
  95. package/src/ui/popover.tsx +56 -23
  96. package/src/ui/progress.tsx +28 -16
  97. package/src/ui/refresh-spin-icon.tsx +41 -0
  98. package/src/ui/search-input.tsx +66 -15
  99. package/src/ui/select.tsx +171 -100
  100. package/src/ui/separator.tsx +23 -14
  101. package/src/ui/slider.tsx +21 -7
  102. package/src/ui/sonner.tsx +68 -30
  103. package/src/ui/spinner.tsx +229 -11
  104. package/src/ui/status-dot.tsx +84 -0
  105. package/src/ui/surface.tsx +49 -0
  106. package/src/ui/switch.tsx +29 -15
  107. package/src/ui/table.tsx +38 -16
  108. package/src/ui/tabs.tsx +123 -35
  109. package/src/ui/tabsSliding.ts +33 -1
  110. package/src/ui/textarea.tsx +13 -4
  111. package/src/ui/tooltip.tsx +50 -14
  112. package/src/ui/ui-config.tsx +149 -0
  113. package/src/utils/cat-color.ts +123 -0
  114. package/src/utils/dayjs.ts +6 -0
  115. package/src/utils/gesture.ts +59 -0
  116. package/src/utils/runtimeVisibility.ts +167 -0
  117. package/src/utils/sharedClock.ts +96 -0
  118. package/src/utils/surface-radius.ts +16 -0
  119. package/src/utils/useDragSensors.ts +58 -0
  120. package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
  121. package/tokens/examples/tokens-moved/README.md +20 -0
  122. package/tokens/examples/tokens-moved/index.html +12 -0
  123. package/tokens/examples/tokens-moved/main.tsx +6 -0
  124. package/tokens/examples/tokens-moved/package.json +14 -0
  125. package/tokens/examples/tokens-moved/styles.css +12 -0
  126. package/tokens/examples/tokens-moved/tsconfig.json +14 -0
  127. package/tokens/examples/tokens-moved/vite.config.ts +11 -0
  128. package/tokens/src/css/border-beam.css +116 -0
  129. package/tokens/src/css/markdown.css +318 -0
@@ -8,21 +8,53 @@ import {
8
8
  DialogHeader,
9
9
  DialogTitle,
10
10
  } from "./dialog";
11
- import { cn } from "../utils/cn";
11
+ import { useCn, useUiConfig } from "./ui-config";
12
+
13
+ /** App defaults for `Command*` (see `UiConfigProvider`). */
14
+ export interface CommandConfig {
15
+ /** Replaces the built-in classes of a part. */
16
+ classes?: {
17
+ root?: string;
18
+ /** `CommandDialog` content. */
19
+ dialogContent?: string;
20
+ /** The `Command` inside `CommandDialog`, for every `listLayout`. */
21
+ dialogCommand?: string;
22
+ inputWrapper?: string;
23
+ input?: string;
24
+ list?: string;
25
+ empty?: string;
26
+ group?: string;
27
+ separator?: string;
28
+ item?: string;
29
+ shortcut?: string;
30
+ };
31
+ /** Replace the built-in icons. */
32
+ icons?: {
33
+ /** `CommandInput` search icon. */
34
+ search?: React.ReactNode;
35
+ };
36
+ /** A `className` on `CommandEmpty` replaces its classes instead of merging with them. */
37
+ emptyClassNameReplaces?: boolean;
38
+ }
12
39
 
13
40
  const Command = React.forwardRef<
14
41
  React.ComponentRef<typeof CommandPrimitive>,
15
42
  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
16
- >(({ className, ...props }, ref) => (
17
- <CommandPrimitive
18
- ref={ref}
19
- className={cn(
20
- "flex h-full w-full flex-col overflow-hidden rounded-lg bg-overlay text-overlay-foreground",
21
- className,
22
- )}
23
- {...props}
24
- />
25
- ));
43
+ >(({ className, ...props }, ref) => {
44
+ const root = useUiConfig("Command")?.classes?.root;
45
+ const merge = useCn();
46
+ return (
47
+ <CommandPrimitive
48
+ ref={ref}
49
+ className={merge(
50
+ root ??
51
+ "flex h-full w-full flex-col overflow-hidden rounded-lg bg-overlay text-overlay-foreground",
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ );
57
+ });
26
58
  Command.displayName = CommandPrimitive.displayName;
27
59
 
28
60
  // The list is pinned rather than `max-h`: a palette that resizes on every
@@ -56,9 +88,11 @@ function CommandDialog({
56
88
  listLayout = "pinned",
57
89
  ...props
58
90
  }: CommandDialogProps) {
91
+ const classes = useUiConfig("Command")?.classes;
92
+ const merge = useCn();
59
93
  return (
60
94
  <Dialog {...props}>
61
- <DialogContent className={cn("overflow-hidden p-0 sm:max-w-xl", className)}>
95
+ <DialogContent className={merge(classes?.dialogContent ?? "overflow-hidden p-0 sm:max-w-xl", className)}>
62
96
  {title !== undefined || description !== undefined ? (
63
97
  <DialogHeader className="sr-only">
64
98
  <DialogTitle>{title}</DialogTitle>
@@ -67,7 +101,10 @@ function CommandDialog({
67
101
  ) : null}
68
102
  <Command
69
103
  shouldFilter={shouldFilter}
70
- className={listLayout === "auto" ? commandDialogAutoClass : commandDialogPinnedClass}
104
+ className={
105
+ classes?.dialogCommand ??
106
+ (listLayout === "auto" ? commandDialogAutoClass : commandDialogPinnedClass)
107
+ }
71
108
  >
72
109
  {children}
73
110
  </Command>
@@ -79,97 +116,133 @@ function CommandDialog({
79
116
  const CommandInput = React.forwardRef<
80
117
  React.ComponentRef<typeof CommandPrimitive.Input>,
81
118
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
82
- >(({ className, ...props }, ref) => (
83
- <div
84
- className="flex items-center border-b border-separator px-3.5"
85
- cmdk-input-wrapper=""
86
- >
87
- <IconSearch className="mr-2 h-4 w-4 shrink-0 opacity-50" stroke={1.5} />
88
- <CommandPrimitive.Input
89
- ref={ref}
90
- className={cn(
91
- "flex h-11 w-full max-sm:min-w-0 bg-transparent text-base sm:text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
92
- className,
119
+ >(({ className, ...props }, ref) => {
120
+ const config = useUiConfig("Command");
121
+ const merge = useCn();
122
+ return (
123
+ <div
124
+ className={config?.classes?.inputWrapper ?? "flex items-center border-b border-separator px-3.5"}
125
+ cmdk-input-wrapper=""
126
+ >
127
+ {config?.icons?.search ?? (
128
+ <IconSearch className="mr-2 h-4 w-4 shrink-0 opacity-50" stroke={1.5} />
93
129
  )}
94
- {...props}
95
- />
96
- </div>
97
- ));
130
+ <CommandPrimitive.Input
131
+ ref={ref}
132
+ className={merge(
133
+ config?.classes?.input ??
134
+ "flex h-11 w-full max-sm:min-w-0 bg-transparent text-base sm:text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
135
+ className,
136
+ )}
137
+ {...props}
138
+ />
139
+ </div>
140
+ );
141
+ });
98
142
  CommandInput.displayName = CommandPrimitive.Input.displayName;
99
143
 
100
144
  const CommandList = React.forwardRef<
101
145
  React.ComponentRef<typeof CommandPrimitive.List>,
102
146
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
103
- >(({ className, ...props }, ref) => (
104
- <CommandPrimitive.List
105
- ref={ref}
106
- className={cn("max-h-80 overflow-y-auto overflow-x-hidden pb-2", className)}
107
- {...props}
108
- />
109
- ));
147
+ >(({ className, ...props }, ref) => {
148
+ const list = useUiConfig("Command")?.classes?.list;
149
+ const merge = useCn();
150
+ return (
151
+ <CommandPrimitive.List
152
+ ref={ref}
153
+ className={merge(list ?? "max-h-80 overflow-y-auto overflow-x-hidden pb-2", className)}
154
+ {...props}
155
+ />
156
+ );
157
+ });
110
158
  CommandList.displayName = CommandPrimitive.List.displayName;
111
159
 
112
160
  const CommandEmpty = React.forwardRef<
113
161
  React.ComponentRef<typeof CommandPrimitive.Empty>,
114
162
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
115
- >(({ className, ...props }, ref) => (
116
- <CommandPrimitive.Empty
117
- ref={ref}
118
- className={cn("py-8 text-center text-sm text-muted-foreground", className)}
119
- {...props}
120
- />
121
- ));
163
+ >(({ className, ...props }, ref) => {
164
+ const config = useUiConfig("Command");
165
+ const merge = useCn();
166
+ const classes = config?.classes?.empty ?? "py-8 text-center text-sm text-muted-foreground";
167
+ return (
168
+ <CommandPrimitive.Empty
169
+ ref={ref}
170
+ className={
171
+ config?.emptyClassNameReplaces && className !== undefined
172
+ ? className
173
+ : merge(classes, className)
174
+ }
175
+ {...props}
176
+ />
177
+ );
178
+ });
122
179
  CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
123
180
 
124
181
  const CommandGroup = React.forwardRef<
125
182
  React.ComponentRef<typeof CommandPrimitive.Group>,
126
183
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
127
- >(({ className, ...props }, ref) => (
128
- <CommandPrimitive.Group
129
- ref={ref}
130
- className={cn(
131
- "overflow-hidden p-1.5 text-foreground [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted-foreground",
132
- className,
133
- )}
134
- {...props}
135
- />
136
- ));
184
+ >(({ className, ...props }, ref) => {
185
+ const group = useUiConfig("Command")?.classes?.group;
186
+ const merge = useCn();
187
+ return (
188
+ <CommandPrimitive.Group
189
+ ref={ref}
190
+ className={merge(
191
+ group ??
192
+ "overflow-hidden p-1.5 text-foreground [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-muted-foreground",
193
+ className,
194
+ )}
195
+ {...props}
196
+ />
197
+ );
198
+ });
137
199
  CommandGroup.displayName = CommandPrimitive.Group.displayName;
138
200
 
139
201
  const CommandSeparator = React.forwardRef<
140
202
  React.ComponentRef<typeof CommandPrimitive.Separator>,
141
203
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
142
- >(({ className, ...props }, ref) => (
143
- <CommandPrimitive.Separator
144
- ref={ref}
145
- className={cn("-mx-1.5 h-px bg-separator", className)}
146
- {...props}
147
- />
148
- ));
204
+ >(({ className, ...props }, ref) => {
205
+ const separator = useUiConfig("Command")?.classes?.separator;
206
+ const merge = useCn();
207
+ return (
208
+ <CommandPrimitive.Separator
209
+ ref={ref}
210
+ className={merge(separator ?? "-mx-1.5 h-px bg-separator", className)}
211
+ {...props}
212
+ />
213
+ );
214
+ });
149
215
  CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
150
216
 
151
217
  const CommandItem = React.forwardRef<
152
218
  React.ComponentRef<typeof CommandPrimitive.Item>,
153
219
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
154
- >(({ className, ...props }, ref) => (
155
- <CommandPrimitive.Item
156
- ref={ref}
157
- className={cn(
158
- "relative flex cursor-default gap-2 select-none items-center rounded-menu-item px-2.5 py-2 text-sm outline-none transition-colors data-[disabled=true]:pointer-events-none data-[selected=true]:bg-surface-tertiary/80 data-[selected=true]:text-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
159
- className,
160
- )}
161
- {...props}
162
- />
163
- ));
220
+ >(({ className, ...props }, ref) => {
221
+ const item = useUiConfig("Command")?.classes?.item;
222
+ const merge = useCn();
223
+ return (
224
+ <CommandPrimitive.Item
225
+ ref={ref}
226
+ className={merge(
227
+ item ??
228
+ "relative flex cursor-default gap-2 select-none items-center rounded-menu-item px-2.5 py-2 text-sm outline-none transition-colors data-[disabled=true]:pointer-events-none data-[selected=true]:bg-surface-tertiary/80 data-[selected=true]:text-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
229
+ className,
230
+ )}
231
+ {...props}
232
+ />
233
+ );
234
+ });
164
235
  CommandItem.displayName = CommandPrimitive.Item.displayName;
165
236
 
166
237
  function CommandShortcut({
167
238
  className,
168
239
  ...props
169
240
  }: React.HTMLAttributes<HTMLSpanElement>) {
241
+ const shortcut = useUiConfig("Command")?.classes?.shortcut;
242
+ const merge = useCn();
170
243
  return (
171
244
  <span
172
- className={cn("ml-auto text-xs tracking-wide text-muted-foreground/90", className)}
245
+ className={merge(shortcut ?? "ml-auto text-xs tracking-wide text-muted-foreground/90", className)}
173
246
  {...props}
174
247
  />
175
248
  );
@@ -5,7 +5,6 @@ import {
5
5
  IconChevronRight,
6
6
  IconCircleFilled,
7
7
  } from "@tabler/icons-react";
8
- import { cn } from "../utils/cn";
9
8
  import {
10
9
  menuCheckboxRadioItemClass,
11
10
  menuContentClass,
@@ -15,10 +14,34 @@ import {
15
14
  menuShortcutClass,
16
15
  menuSubTriggerClass,
17
16
  } from "./_menu-classes";
18
- import { MenuItemRow, SubTriggerRow, suppressAltKeyDown } from "./_menu-parts";
17
+ import {
18
+ type MenuConfig,
19
+ MenuItemRow,
20
+ SubTriggerRow,
21
+ suppressAltKeyDown,
22
+ } from "./_menu-parts";
23
+ import { useCn, useUiConfig } from "./ui-config";
24
+
25
+ /** App defaults for `ContextMenu*` (see `UiConfigProvider`). */
26
+ export interface ContextMenuConfig extends MenuConfig {
27
+ /** Default for `ContextMenuSubContent` `portal`. */
28
+ subContentPortal?: boolean;
29
+ }
19
30
 
20
31
  const ContextMenu = ContextMenuPrimitive.Root;
21
- const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
32
+ const ContextMenuTrigger = React.forwardRef<
33
+ React.ComponentRef<typeof ContextMenuPrimitive.Trigger>,
34
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Trigger>
35
+ >((props, ref) => {
36
+ const trigger = useUiConfig("ContextMenu")?.classes?.trigger;
37
+ const merge = useCn();
38
+ // Without config, pass props untouched: Radix's Slot turns an explicit
39
+ // `className={undefined}` into `class=""`.
40
+ if (trigger === undefined) return <ContextMenuPrimitive.Trigger ref={ref} {...props} />;
41
+ const { className, ...rest } = props;
42
+ return <ContextMenuPrimitive.Trigger ref={ref} className={merge(trigger, className)} {...rest} />;
43
+ });
44
+ ContextMenuTrigger.displayName = ContextMenuPrimitive.Trigger.displayName;
22
45
  const ContextMenuGroup = ContextMenuPrimitive.Group;
23
46
  const ContextMenuPortal = ContextMenuPrimitive.Portal;
24
47
  const ContextMenuSub = ContextMenuPrimitive.Sub;
@@ -31,30 +54,46 @@ const ContextMenuSubTrigger = React.forwardRef<
31
54
  /** Leading icon or mark. The label then fills the row. */
32
55
  startContent?: React.ReactNode;
33
56
  }
34
- >(({ className, inset, startContent, children, ...props }, ref) => (
35
- <ContextMenuPrimitive.SubTrigger
36
- ref={ref}
37
- className={cn(menuSubTriggerClass, inset && "pl-8", className)}
38
- {...props}
39
- >
40
- {startContent === undefined ? children : (
41
- <SubTriggerRow startContent={startContent}>{children}</SubTriggerRow>
42
- )}
43
- <IconChevronRight className="ml-auto" />
44
- </ContextMenuPrimitive.SubTrigger>
45
- ));
57
+ >(({ className, inset, startContent, children, ...props }, ref) => {
58
+ const classes = useUiConfig("ContextMenu")?.classes;
59
+ const merge = useCn();
60
+ return (
61
+ <ContextMenuPrimitive.SubTrigger
62
+ ref={ref}
63
+ className={merge(classes?.subTrigger ?? menuSubTriggerClass, inset && "pl-8", className)}
64
+ {...props}
65
+ >
66
+ {startContent === undefined ? children : (
67
+ <SubTriggerRow startContent={startContent}>{children}</SubTriggerRow>
68
+ )}
69
+ <IconChevronRight className="ml-auto" />
70
+ </ContextMenuPrimitive.SubTrigger>
71
+ );
72
+ });
46
73
  ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
47
74
 
48
75
  const ContextMenuSubContent = React.forwardRef<
49
76
  React.ComponentRef<typeof ContextMenuPrimitive.SubContent>,
50
- React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
51
- >(({ className, ...props }, ref) => (
52
- <ContextMenuPrimitive.SubContent
53
- ref={ref}
54
- className={cn(menuContentClass, "w-40", className)}
55
- {...props}
56
- />
57
- ));
77
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> & {
78
+ /** Render into `document.body` so a clipping parent cannot cut it off. */
79
+ portal?: boolean;
80
+ }
81
+ >(({ className, portal, ...props }, ref) => {
82
+ const config = useUiConfig("ContextMenu");
83
+ const merge = useCn();
84
+ const content = (
85
+ <ContextMenuPrimitive.SubContent
86
+ ref={ref}
87
+ className={merge(config?.classes?.subContent ?? [menuContentClass, "w-40"], className)}
88
+ {...props}
89
+ />
90
+ );
91
+ return (portal ?? config?.subContentPortal) ? (
92
+ <ContextMenuPrimitive.Portal>{content}</ContextMenuPrimitive.Portal>
93
+ ) : (
94
+ content
95
+ );
96
+ });
58
97
  ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
59
98
 
60
99
  const ContextMenuContent = React.forwardRef<
@@ -63,16 +102,21 @@ const ContextMenuContent = React.forwardRef<
63
102
  /** Stop a bare Alt press from moving focus (e.g. into the browser menu bar). */
64
103
  suppressAltKey?: boolean;
65
104
  }
66
- >(({ className, suppressAltKey, onKeyDown, ...props }, ref) => (
67
- <ContextMenuPrimitive.Portal>
68
- <ContextMenuPrimitive.Content
69
- ref={ref}
70
- className={cn(menuContentClass, "w-40", className)}
71
- onKeyDown={suppressAltKey ? suppressAltKeyDown(onKeyDown) : onKeyDown}
72
- {...props}
73
- />
74
- </ContextMenuPrimitive.Portal>
75
- ));
105
+ >(({ className, suppressAltKey, collisionPadding, onKeyDown, ...props }, ref) => {
106
+ const config = useUiConfig("ContextMenu");
107
+ const merge = useCn();
108
+ return (
109
+ <ContextMenuPrimitive.Portal>
110
+ <ContextMenuPrimitive.Content
111
+ ref={ref}
112
+ collisionPadding={collisionPadding ?? config?.collisionPadding}
113
+ className={merge(config?.classes?.content ?? [menuContentClass, "w-40"], className)}
114
+ onKeyDown={suppressAltKey ? suppressAltKeyDown(onKeyDown) : onKeyDown}
115
+ {...props}
116
+ />
117
+ </ContextMenuPrimitive.Portal>
118
+ );
119
+ });
76
120
  ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
77
121
 
78
122
  const ContextMenuItem = React.forwardRef<
@@ -90,63 +134,75 @@ const ContextMenuItem = React.forwardRef<
90
134
  (
91
135
  { className, inset, startContent, description, endContent, children, asChild, ...props },
92
136
  ref,
93
- ) => (
94
- <ContextMenuPrimitive.Item
95
- ref={ref}
96
- asChild={asChild}
97
- className={cn(menuItemClass, inset && "pl-8", className)}
98
- {...props}
99
- >
100
- {asChild || (startContent === undefined && description === undefined && endContent === undefined) ? (
101
- children
102
- ) : (
103
- <MenuItemRow startContent={startContent} description={description} endContent={endContent}>
104
- {children}
105
- </MenuItemRow>
106
- )}
107
- </ContextMenuPrimitive.Item>
108
- ),
137
+ ) => {
138
+ const item = useUiConfig("ContextMenu")?.classes?.item;
139
+ const merge = useCn();
140
+ return (
141
+ <ContextMenuPrimitive.Item
142
+ ref={ref}
143
+ asChild={asChild}
144
+ className={merge(item ?? menuItemClass, inset && "pl-8", className)}
145
+ {...props}
146
+ >
147
+ {asChild || (startContent === undefined && description === undefined && endContent === undefined) ? (
148
+ children
149
+ ) : (
150
+ <MenuItemRow startContent={startContent} description={description} endContent={endContent}>
151
+ {children}
152
+ </MenuItemRow>
153
+ )}
154
+ </ContextMenuPrimitive.Item>
155
+ );
156
+ },
109
157
  );
110
158
  ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
111
159
 
112
160
  const ContextMenuCheckboxItem = React.forwardRef<
113
161
  React.ComponentRef<typeof ContextMenuPrimitive.CheckboxItem>,
114
162
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
115
- >(({ className, children, checked, ...props }, ref) => (
116
- <ContextMenuPrimitive.CheckboxItem
117
- ref={ref}
118
- className={cn(menuCheckboxRadioItemClass, className)}
119
- checked={checked}
120
- {...props}
121
- >
122
- <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
123
- <ContextMenuPrimitive.ItemIndicator>
124
- <IconCheck className="h-4 w-4" />
125
- </ContextMenuPrimitive.ItemIndicator>
126
- </span>
127
- {children}
128
- </ContextMenuPrimitive.CheckboxItem>
129
- ));
163
+ >(({ className, children, checked, ...props }, ref) => {
164
+ const config = useUiConfig("ContextMenu");
165
+ const merge = useCn();
166
+ return (
167
+ <ContextMenuPrimitive.CheckboxItem
168
+ ref={ref}
169
+ className={merge(config?.classes?.checkboxItem ?? menuCheckboxRadioItemClass, className)}
170
+ checked={checked}
171
+ {...props}
172
+ >
173
+ <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
174
+ <ContextMenuPrimitive.ItemIndicator>
175
+ {config?.icons?.check ?? <IconCheck className="h-4 w-4" />}
176
+ </ContextMenuPrimitive.ItemIndicator>
177
+ </span>
178
+ {children}
179
+ </ContextMenuPrimitive.CheckboxItem>
180
+ );
181
+ });
130
182
  ContextMenuCheckboxItem.displayName =
131
183
  ContextMenuPrimitive.CheckboxItem.displayName;
132
184
 
133
185
  const ContextMenuRadioItem = React.forwardRef<
134
186
  React.ComponentRef<typeof ContextMenuPrimitive.RadioItem>,
135
187
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
136
- >(({ className, children, ...props }, ref) => (
137
- <ContextMenuPrimitive.RadioItem
138
- ref={ref}
139
- className={cn(menuCheckboxRadioItemClass, className)}
140
- {...props}
141
- >
142
- <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
143
- <ContextMenuPrimitive.ItemIndicator>
144
- <IconCircleFilled className="h-2 w-2 fill-current" />
145
- </ContextMenuPrimitive.ItemIndicator>
146
- </span>
147
- {children}
148
- </ContextMenuPrimitive.RadioItem>
149
- ));
188
+ >(({ className, children, ...props }, ref) => {
189
+ const config = useUiConfig("ContextMenu");
190
+ const merge = useCn();
191
+ return (
192
+ <ContextMenuPrimitive.RadioItem
193
+ ref={ref}
194
+ className={merge(config?.classes?.radioItem ?? menuCheckboxRadioItemClass, className)}
195
+ {...props}
196
+ >
197
+ <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
198
+ <ContextMenuPrimitive.ItemIndicator>
199
+ {config?.icons?.radio ?? <IconCircleFilled className="h-2 w-2 fill-current" />}
200
+ </ContextMenuPrimitive.ItemIndicator>
201
+ </span>
202
+ {children}
203
+ </ContextMenuPrimitive.RadioItem>
204
+ );
205
+ });
150
206
  ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
151
207
 
152
208
  const ContextMenuLabel = React.forwardRef<
@@ -154,32 +210,42 @@ const ContextMenuLabel = React.forwardRef<
154
210
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
155
211
  inset?: boolean;
156
212
  }
157
- >(({ className, inset, ...props }, ref) => (
158
- <ContextMenuPrimitive.Label
159
- ref={ref}
160
- className={cn(menuLabelClass, inset && "pl-8", className)}
161
- {...props}
162
- />
163
- ));
213
+ >(({ className, inset, ...props }, ref) => {
214
+ const label = useUiConfig("ContextMenu")?.classes?.label;
215
+ const merge = useCn();
216
+ return (
217
+ <ContextMenuPrimitive.Label
218
+ ref={ref}
219
+ className={merge(label ?? menuLabelClass, inset && "pl-8", className)}
220
+ {...props}
221
+ />
222
+ );
223
+ });
164
224
  ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
165
225
 
166
226
  const ContextMenuSeparator = React.forwardRef<
167
227
  React.ComponentRef<typeof ContextMenuPrimitive.Separator>,
168
228
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
169
- >(({ className, ...props }, ref) => (
170
- <ContextMenuPrimitive.Separator
171
- ref={ref}
172
- className={cn(menuSeparatorClass, className)}
173
- {...props}
174
- />
175
- ));
229
+ >(({ className, ...props }, ref) => {
230
+ const separator = useUiConfig("ContextMenu")?.classes?.separator;
231
+ const merge = useCn();
232
+ return (
233
+ <ContextMenuPrimitive.Separator
234
+ ref={ref}
235
+ className={merge(separator ?? menuSeparatorClass, className)}
236
+ {...props}
237
+ />
238
+ );
239
+ });
176
240
  ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
177
241
 
178
242
  const ContextMenuShortcut = ({
179
243
  className,
180
244
  ...props
181
245
  }: React.HTMLAttributes<HTMLSpanElement>) => {
182
- return <span className={cn(menuShortcutClass, className)} {...props} />;
246
+ const shortcut = useUiConfig("ContextMenu")?.classes?.shortcut;
247
+ const merge = useCn();
248
+ return <span className={merge(shortcut ?? menuShortcutClass, className)} {...props} />;
183
249
  };
184
250
  ContextMenuShortcut.displayName = "ContextMenuShortcut";
185
251