@gperezmz/bb-plugin-thread-glance 0.1.2

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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/THIRD_PARTY_NOTICES.md +230 -0
  4. package/app.tsx +12 -0
  5. package/components/ui/alert-dialog.tsx +142 -0
  6. package/components/ui/button.tsx +59 -0
  7. package/components/ui/card.tsx +83 -0
  8. package/components/ui/coarse-pointer-sizing.ts +66 -0
  9. package/components/ui/context-menu.tsx +355 -0
  10. package/components/ui/dialog.tsx +416 -0
  11. package/components/ui/dropdown-menu.tsx +640 -0
  12. package/components/ui/hooks/use-compact-viewport.tsx +37 -0
  13. package/components/ui/hooks/use-media-query.ts +70 -0
  14. package/components/ui/hooks/use-pointer-coarse.ts +7 -0
  15. package/components/ui/hover-card.tsx +36 -0
  16. package/components/ui/icon-extended.tsx +333 -0
  17. package/components/ui/icon-registry.ts +189 -0
  18. package/components/ui/icon.tsx +444 -0
  19. package/components/ui/input.tsx +30 -0
  20. package/components/ui/menu-item-hover.tsx +93 -0
  21. package/components/ui/motion.ts +4 -0
  22. package/components/ui/overlay-trigger.ts +84 -0
  23. package/components/ui/popover.tsx +223 -0
  24. package/components/ui/responsive-overlay.tsx +823 -0
  25. package/components/ui/tooltip.tsx +63 -0
  26. package/dist/app.css +2 -0
  27. package/dist/app.js +66 -0
  28. package/dist/app.meta.json +11 -0
  29. package/dist/server.js +71 -0
  30. package/dist/server.js.map +6 -0
  31. package/dist/server.meta.json +11 -0
  32. package/features/thread-list/components/Dialogs.tsx +299 -0
  33. package/features/thread-list/components/FoldRows.tsx +147 -0
  34. package/features/thread-list/components/GroupSection.tsx +370 -0
  35. package/features/thread-list/components/ProviderBadge.tsx +57 -0
  36. package/features/thread-list/components/PullRequestBadge.tsx +50 -0
  37. package/features/thread-list/components/Rails.tsx +28 -0
  38. package/features/thread-list/components/RenameEditor.tsx +118 -0
  39. package/features/thread-list/components/RowMenu.tsx +168 -0
  40. package/features/thread-list/components/SplitMiniMap.tsx +55 -0
  41. package/features/thread-list/components/ThreadDetails.tsx +138 -0
  42. package/features/thread-list/components/ThreadList.tsx +855 -0
  43. package/features/thread-list/components/ThreadRowView.tsx +631 -0
  44. package/features/thread-list/components/Toolbar.tsx +296 -0
  45. package/features/thread-list/components/controller.ts +64 -0
  46. package/features/thread-list/components/glyphs.tsx +129 -0
  47. package/features/thread-list/components/row-card.ts +127 -0
  48. package/features/thread-list/data/storage.ts +18 -0
  49. package/features/thread-list/data/useAutoExpand.ts +53 -0
  50. package/features/thread-list/data/useClientPreferences.ts +20 -0
  51. package/features/thread-list/data/useNotes.ts +41 -0
  52. package/features/thread-list/data/useNow.ts +19 -0
  53. package/features/thread-list/data/usePreferences.ts +126 -0
  54. package/features/thread-list/data/useScheduled.ts +43 -0
  55. package/features/thread-list/data/useStamps.ts +68 -0
  56. package/features/thread-list/icons.ts +44 -0
  57. package/features/thread-list/index.ts +2 -0
  58. package/features/thread-list/model/attention.ts +89 -0
  59. package/features/thread-list/model/counters.ts +67 -0
  60. package/features/thread-list/model/details.ts +79 -0
  61. package/features/thread-list/model/drag.ts +104 -0
  62. package/features/thread-list/model/expansion.ts +71 -0
  63. package/features/thread-list/model/families.ts +297 -0
  64. package/features/thread-list/model/groups.ts +263 -0
  65. package/features/thread-list/model/labels.ts +50 -0
  66. package/features/thread-list/model/layout.ts +61 -0
  67. package/features/thread-list/model/menu.ts +78 -0
  68. package/features/thread-list/model/move.ts +46 -0
  69. package/features/thread-list/model/notes.ts +52 -0
  70. package/features/thread-list/model/provider-mark.ts +59 -0
  71. package/features/thread-list/model/share.ts +101 -0
  72. package/features/thread-list/model/sort.ts +78 -0
  73. package/features/thread-list/model/state.ts +330 -0
  74. package/features/thread-list/model/time.ts +107 -0
  75. package/features/thread-list/model/toggles.ts +99 -0
  76. package/features/thread-list/model/view.ts +669 -0
  77. package/features/thread-list/model/windowing.ts +27 -0
  78. package/features/thread-list/testing/fixtures.ts +162 -0
  79. package/hooks/useBrowserDimmingModal.ts +5 -0
  80. package/lib/portal-scope.ts +15 -0
  81. package/lib/utils.ts +17 -0
  82. package/package.json +96 -0
  83. package/server/cli.ts +107 -0
  84. package/server/failures.ts +136 -0
  85. package/server/import.ts +144 -0
  86. package/server/notes.ts +236 -0
  87. package/server/preference-store.ts +96 -0
  88. package/server/scheduled.ts +129 -0
  89. package/server/serial.ts +12 -0
  90. package/server/stamps.ts +142 -0
  91. package/server/startup.ts +121 -0
  92. package/server.ts +126 -0
  93. package/shared/contract.ts +73 -0
  94. package/shared/preferences.ts +241 -0
  95. package/shared/signals.ts +71 -0
  96. package/skills/thread-glance/SKILL.md +69 -0
  97. package/tsconfig.json +36 -0
  98. package/types/global-jsx.d.ts +10 -0
@@ -0,0 +1,640 @@
1
+ import * as React from "react";
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
+
4
+ import { cn } from "../../lib/utils";
5
+ import { usePortalScopeProps } from "../../lib/portal-scope";
6
+ import { COARSE_POINTER_CHECK_SLOT_CLASS } from "./coarse-pointer-sizing.js";
7
+ import {
8
+ type ResponsiveOverlayContextValue,
9
+ COMPACT_SHEET_CONTENT_STYLE,
10
+ useResponsiveRoot,
11
+ MobileTrigger,
12
+ ResponsiveDrawerShell,
13
+ stripRadixContentProps,
14
+ } from "./responsive-overlay.js";
15
+ import {
16
+ blurActiveKeyboardInputBeforeOverlayOpen,
17
+ getOverlayTriggerClassName,
18
+ isLastInputKeyboard,
19
+ preventOverlayTriggerSelection,
20
+ } from "./overlay-trigger.js";
21
+ import {
22
+ MENU_ITEM_LAST_HOVERED_CLASS,
23
+ MenuHoverProvider,
24
+ useMenuItemHover,
25
+ } from "./menu-item-hover.js";
26
+ import { LIST_HOVER_TRANSITION } from "./motion.js";
27
+ import { Icon } from "../../components/ui/icon.js";
28
+
29
+ const MENU_ITEM_NEUTRAL_STATE_CLASS =
30
+ "focus:bg-state-hover focus:text-foreground data-[last-hovered]:bg-state-hover data-[last-hovered]:text-foreground";
31
+ const MENU_ITEM_DESTRUCTIVE_STATE_CLASS =
32
+ "text-destructive focus:bg-destructive/15 focus:text-destructive data-[last-hovered]:bg-destructive/15";
33
+ const MENU_ITEM_DESTRUCTIVE_TOUCH_CLASS =
34
+ "text-destructive focus:bg-destructive/15 focus:text-destructive active:bg-destructive/20 active:text-destructive";
35
+
36
+ const ResponsiveMenuContext =
37
+ React.createContext<ResponsiveOverlayContextValue>({
38
+ isCompactViewport: false,
39
+ open: false,
40
+ onOpenChange: () => {},
41
+ });
42
+
43
+ function useResponsiveMenu() {
44
+ return React.useContext(ResponsiveMenuContext);
45
+ }
46
+
47
+ function DropdownMenu({
48
+ children,
49
+ open: controlledOpen,
50
+ onOpenChange: controlledOnChange,
51
+ defaultOpen,
52
+ ...props
53
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
54
+ const ctx = useResponsiveRoot(
55
+ controlledOpen,
56
+ controlledOnChange,
57
+ defaultOpen,
58
+ );
59
+
60
+ if (ctx.isCompactViewport) {
61
+ return (
62
+ <ResponsiveMenuContext.Provider value={ctx}>
63
+ {children}
64
+ </ResponsiveMenuContext.Provider>
65
+ );
66
+ }
67
+
68
+ return (
69
+ <DropdownMenuPrimitive.Root
70
+ open={ctx.open}
71
+ onOpenChange={ctx.onOpenChange}
72
+ {...props}
73
+ >
74
+ <ResponsiveMenuContext.Provider value={ctx}>
75
+ {children}
76
+ </ResponsiveMenuContext.Provider>
77
+ </DropdownMenuPrimitive.Root>
78
+ );
79
+ }
80
+
81
+ const DropdownMenuTrigger = React.forwardRef<
82
+ HTMLButtonElement,
83
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
84
+ >(({ asChild, children, className, ...props }, ref) => {
85
+ const { isCompactViewport, open, onOpenChange } = useResponsiveMenu();
86
+
87
+ if (isCompactViewport) {
88
+ return (
89
+ <MobileTrigger
90
+ ref={ref}
91
+ asChild={asChild}
92
+ open={open}
93
+ onOpenChange={onOpenChange}
94
+ haspopup="menu"
95
+ className={className}
96
+ {...props}
97
+ >
98
+ {children}
99
+ </MobileTrigger>
100
+ );
101
+ }
102
+
103
+ return (
104
+ <DropdownMenuPrimitive.Trigger
105
+ ref={ref}
106
+ asChild={asChild}
107
+ className={getOverlayTriggerClassName(className)}
108
+ onMouseDown={(event) => {
109
+ if (!open) {
110
+ blurActiveKeyboardInputBeforeOverlayOpen();
111
+ }
112
+ preventOverlayTriggerSelection(event);
113
+ }}
114
+ {...props}
115
+ >
116
+ {children}
117
+ </DropdownMenuPrimitive.Trigger>
118
+ );
119
+ });
120
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
121
+
122
+ const DropdownMenuContent = React.forwardRef<
123
+ HTMLDivElement,
124
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
125
+ mobileTitle?: string;
126
+ }
127
+ >(
128
+ (
129
+ {
130
+ className,
131
+ sideOffset = 4,
132
+ children,
133
+ mobileTitle,
134
+ onCloseAutoFocus,
135
+ ...props
136
+ },
137
+ ref,
138
+ ) => {
139
+ const { isCompactViewport, open, onOpenChange } = useResponsiveMenu();
140
+ const scopeProps = usePortalScopeProps();
141
+
142
+ if (isCompactViewport) {
143
+ const { style, ...domProps } = stripRadixContentProps(props);
144
+ return (
145
+ <ResponsiveDrawerShell
146
+ open={open}
147
+ onOpenChange={onOpenChange}
148
+ srLabel={mobileTitle ?? "Menu"}
149
+ >
150
+ <div
151
+ ref={ref}
152
+ className={cn(
153
+ "flex flex-col gap-0.5 overflow-y-auto p-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]",
154
+ className,
155
+ )}
156
+ {...domProps}
157
+ style={{ ...style, ...COMPACT_SHEET_CONTENT_STYLE }}
158
+ >
159
+ {children}
160
+ </div>
161
+ </ResponsiveDrawerShell>
162
+ );
163
+ }
164
+
165
+ return (
166
+ <DropdownMenuPrimitive.Portal>
167
+ <DropdownMenuPrimitive.Content
168
+ ref={ref}
169
+ {...scopeProps}
170
+ sideOffset={sideOffset}
171
+ onCloseAutoFocus={(event) => {
172
+ if (!isLastInputKeyboard()) {
173
+ event.preventDefault();
174
+ }
175
+ onCloseAutoFocus?.(event);
176
+ }}
177
+ className={cn(
178
+ "z-50 min-w-28 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
179
+ className,
180
+ )}
181
+ {...props}
182
+ >
183
+ <MenuHoverProvider>{children}</MenuHoverProvider>
184
+ </DropdownMenuPrimitive.Content>
185
+ </DropdownMenuPrimitive.Portal>
186
+ );
187
+ },
188
+ );
189
+ DropdownMenuContent.displayName = "DropdownMenuContent";
190
+
191
+ function createSelectEvent(): Event {
192
+ return new Event("select", { cancelable: true });
193
+ }
194
+
195
+ type DropdownMenuItemProps = Omit<
196
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>,
197
+ "onBlur" | "onFocus"
198
+ > & {
199
+ inset?: boolean;
200
+ variant?: "default" | "destructive";
201
+ onBlur?: React.FocusEventHandler<HTMLElement>;
202
+ onFocus?: React.FocusEventHandler<HTMLElement>;
203
+ };
204
+
205
+ const DropdownMenuItem = React.forwardRef<
206
+ React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
207
+ DropdownMenuItemProps
208
+ >(
209
+ (
210
+ {
211
+ className,
212
+ inset,
213
+ variant = "default",
214
+ onSelect,
215
+ disabled,
216
+ role = "menuitem",
217
+ "aria-checked": ariaChecked,
218
+ textValue: _textValue,
219
+ children,
220
+ onPointerEnter: callerPointerEnter,
221
+ onKeyDown: callerKeyDown,
222
+ onFocus: callerFocus,
223
+ onBlur: callerBlur,
224
+ ...domProps
225
+ },
226
+ ref,
227
+ ) => {
228
+ const { isCompactViewport, onOpenChange } = useResponsiveMenu();
229
+ const { hoverProps } = useMenuItemHover({
230
+ onPointerEnter: callerPointerEnter,
231
+ onKeyDown: callerKeyDown,
232
+ });
233
+
234
+ if (isCompactViewport) {
235
+ return (
236
+ <button
237
+ ref={ref as React.RefCallback<HTMLButtonElement> | null}
238
+ type="button"
239
+ role={role}
240
+ disabled={disabled}
241
+ aria-disabled={disabled || undefined}
242
+ aria-checked={ariaChecked}
243
+ className={cn(
244
+ "relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-2 text-left text-xs outline-none transition-colors focus:bg-state-hover focus:text-foreground active:bg-state-active active:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>[data-icon-root]]:size-4 [&>[data-icon-root]]:shrink-0",
245
+ inset && "pl-8",
246
+ variant === "destructive" && MENU_ITEM_DESTRUCTIVE_TOUCH_CLASS,
247
+ className,
248
+ )}
249
+ data-disabled={disabled ? "" : undefined}
250
+ onFocus={callerFocus}
251
+ onBlur={callerBlur}
252
+ onClick={() => {
253
+ if (disabled) return;
254
+ const event = createSelectEvent();
255
+ onSelect?.(event);
256
+ if (!event.defaultPrevented) {
257
+ onOpenChange(false);
258
+ }
259
+ }}
260
+ >
261
+ {children}
262
+ </button>
263
+ );
264
+ }
265
+
266
+ return (
267
+ <DropdownMenuPrimitive.Item
268
+ ref={ref}
269
+ className={cn(
270
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-[0.3125rem] text-xs outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>[data-icon-root]]:size-4 [&>[data-icon-root]]:shrink-0",
271
+ LIST_HOVER_TRANSITION,
272
+ variant === "destructive"
273
+ ? MENU_ITEM_DESTRUCTIVE_STATE_CLASS
274
+ : MENU_ITEM_NEUTRAL_STATE_CLASS,
275
+ inset && "pl-8",
276
+ className,
277
+ )}
278
+ disabled={disabled}
279
+ role={role}
280
+ aria-checked={ariaChecked}
281
+ onSelect={onSelect}
282
+ textValue={_textValue}
283
+ onFocus={callerFocus}
284
+ onBlur={callerBlur}
285
+ {...domProps}
286
+ {...hoverProps}
287
+ >
288
+ {children}
289
+ </DropdownMenuPrimitive.Item>
290
+ );
291
+ },
292
+ );
293
+ DropdownMenuItem.displayName = "DropdownMenuItem";
294
+
295
+ const DropdownMenuCheckboxItem = React.forwardRef<
296
+ React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,
297
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
298
+ >(
299
+ (
300
+ {
301
+ className,
302
+ children,
303
+ checked,
304
+ onSelect,
305
+ onCheckedChange,
306
+ disabled,
307
+ textValue: _textValue,
308
+ onPointerEnter: callerPointerEnter,
309
+ onKeyDown: callerKeyDown,
310
+ ...domProps
311
+ },
312
+ ref,
313
+ ) => {
314
+ const { isCompactViewport, onOpenChange } = useResponsiveMenu();
315
+ const { hoverProps } = useMenuItemHover({
316
+ onPointerEnter: callerPointerEnter,
317
+ onKeyDown: callerKeyDown,
318
+ });
319
+
320
+ if (isCompactViewport) {
321
+ return (
322
+ <button
323
+ ref={ref as React.RefCallback<HTMLButtonElement> | null}
324
+ type="button"
325
+ role="menuitemcheckbox"
326
+ aria-checked={
327
+ checked === "indeterminate" ? "mixed" : checked === true
328
+ }
329
+ disabled={disabled}
330
+ aria-disabled={disabled || undefined}
331
+ className={cn(
332
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-2 pl-2 pr-8 text-left text-xs outline-none transition-colors focus:bg-state-hover focus:text-foreground active:bg-state-active active:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
333
+ className,
334
+ )}
335
+ data-disabled={disabled ? "" : undefined}
336
+ onClick={() => {
337
+ if (disabled) return;
338
+ const event = createSelectEvent();
339
+ onSelect?.(event);
340
+ onCheckedChange?.(checked === "indeterminate" ? true : !checked);
341
+ if (!event.defaultPrevented) {
342
+ onOpenChange(false);
343
+ }
344
+ }}
345
+ >
346
+ <span
347
+ className={cn(
348
+ "absolute right-2 flex items-center justify-center",
349
+ COARSE_POINTER_CHECK_SLOT_CLASS,
350
+ )}
351
+ >
352
+ {(checked === true || checked === "indeterminate") && (
353
+ <Icon name="Check" className={COARSE_POINTER_CHECK_SLOT_CLASS} />
354
+ )}
355
+ </span>
356
+ {children}
357
+ </button>
358
+ );
359
+ }
360
+
361
+ return (
362
+ <DropdownMenuPrimitive.CheckboxItem
363
+ ref={ref}
364
+ className={cn(
365
+ "relative flex cursor-default select-none items-center rounded-sm py-[0.3125rem] pl-2 pr-8 text-xs outline-none focus:bg-state-hover focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
366
+ LIST_HOVER_TRANSITION,
367
+ MENU_ITEM_LAST_HOVERED_CLASS,
368
+ className,
369
+ )}
370
+ checked={checked}
371
+ onSelect={onSelect}
372
+ onCheckedChange={onCheckedChange}
373
+ disabled={disabled}
374
+ textValue={_textValue}
375
+ {...domProps}
376
+ {...hoverProps}
377
+ >
378
+ <span
379
+ className={cn(
380
+ "absolute right-2 flex items-center justify-center",
381
+ COARSE_POINTER_CHECK_SLOT_CLASS,
382
+ )}
383
+ >
384
+ <DropdownMenuPrimitive.ItemIndicator>
385
+ <Icon name="Check" className={COARSE_POINTER_CHECK_SLOT_CLASS} />
386
+ </DropdownMenuPrimitive.ItemIndicator>
387
+ </span>
388
+ {children}
389
+ </DropdownMenuPrimitive.CheckboxItem>
390
+ );
391
+ },
392
+ );
393
+ DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
394
+
395
+ function DropdownMenuRadioGroup({
396
+ children,
397
+ ...props
398
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
399
+ const { isCompactViewport } = useResponsiveMenu();
400
+
401
+ if (isCompactViewport) {
402
+ return null;
403
+ }
404
+
405
+ return (
406
+ <DropdownMenuPrimitive.RadioGroup {...props}>
407
+ {children}
408
+ </DropdownMenuPrimitive.RadioGroup>
409
+ );
410
+ }
411
+
412
+ const DropdownMenuRadioItem = React.forwardRef<
413
+ React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,
414
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
415
+ >(
416
+ (
417
+ {
418
+ className,
419
+ children,
420
+ onPointerEnter: callerPointerEnter,
421
+ onKeyDown: callerKeyDown,
422
+ ...props
423
+ },
424
+ ref,
425
+ ) => {
426
+ const { isCompactViewport } = useResponsiveMenu();
427
+ const { hoverProps } = useMenuItemHover({
428
+ onPointerEnter: callerPointerEnter,
429
+ onKeyDown: callerKeyDown,
430
+ });
431
+
432
+ if (isCompactViewport) {
433
+ return null;
434
+ }
435
+
436
+ return (
437
+ <DropdownMenuPrimitive.RadioItem
438
+ ref={ref}
439
+ className={cn(
440
+ "relative flex cursor-default select-none items-center rounded-sm py-[0.3125rem] pl-8 pr-2 text-xs outline-none focus:bg-state-hover focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
441
+ LIST_HOVER_TRANSITION,
442
+ MENU_ITEM_LAST_HOVERED_CLASS,
443
+ className,
444
+ )}
445
+ {...props}
446
+ {...hoverProps}
447
+ >
448
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
449
+ <DropdownMenuPrimitive.ItemIndicator>
450
+ <Icon name="Circle" className="h-2 w-2 fill-current" />
451
+ </DropdownMenuPrimitive.ItemIndicator>
452
+ </span>
453
+ {children}
454
+ </DropdownMenuPrimitive.RadioItem>
455
+ );
456
+ },
457
+ );
458
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
459
+
460
+ const DropdownMenuLabel = React.forwardRef<
461
+ HTMLDivElement,
462
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
463
+ inset?: boolean;
464
+ }
465
+ >(({ className, inset, children, ...props }, ref) => {
466
+ const { isCompactViewport } = useResponsiveMenu();
467
+
468
+ if (isCompactViewport) {
469
+ return (
470
+ <div
471
+ ref={ref}
472
+ className={cn(
473
+ "px-2 py-1.5 text-xs font-medium text-muted-foreground",
474
+ inset && "pl-8",
475
+ className,
476
+ )}
477
+ >
478
+ {children}
479
+ </div>
480
+ );
481
+ }
482
+
483
+ return (
484
+ <DropdownMenuPrimitive.Label
485
+ ref={ref}
486
+ className={cn(
487
+ "px-2 py-[0.3125rem] text-xs font-medium text-muted-foreground",
488
+ inset && "pl-8",
489
+ className,
490
+ )}
491
+ {...props}
492
+ >
493
+ {children}
494
+ </DropdownMenuPrimitive.Label>
495
+ );
496
+ });
497
+ DropdownMenuLabel.displayName = "DropdownMenuLabel";
498
+
499
+ const DropdownMenuSeparator = React.forwardRef<
500
+ HTMLHRElement,
501
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
502
+ >(({ className, ...props }, ref) => {
503
+ const { isCompactViewport } = useResponsiveMenu();
504
+
505
+ if (isCompactViewport) {
506
+ return (
507
+ <hr
508
+ ref={ref as React.RefCallback<HTMLHRElement> | null}
509
+ className={cn("-mx-1 my-1 h-px border-0 bg-muted", className)}
510
+ />
511
+ );
512
+ }
513
+
514
+ return (
515
+ <DropdownMenuPrimitive.Separator
516
+ ref={ref}
517
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
518
+ {...props}
519
+ />
520
+ );
521
+ });
522
+ DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
523
+
524
+ const DropdownMenuGroup = React.forwardRef<
525
+ HTMLDivElement,
526
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group>
527
+ >(({ children, ...props }, ref) => {
528
+ const { isCompactViewport } = useResponsiveMenu();
529
+
530
+ if (isCompactViewport) {
531
+ return (
532
+ <div ref={ref} role="group" {...props}>
533
+ {children}
534
+ </div>
535
+ );
536
+ }
537
+
538
+ return (
539
+ <DropdownMenuPrimitive.Group ref={ref} {...props}>
540
+ {children}
541
+ </DropdownMenuPrimitive.Group>
542
+ );
543
+ });
544
+ DropdownMenuGroup.displayName = "DropdownMenuGroup";
545
+
546
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
547
+
548
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
549
+
550
+ const DropdownMenuSubTrigger = React.forwardRef<
551
+ React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,
552
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
553
+ inset?: boolean;
554
+ }
555
+ >(
556
+ (
557
+ {
558
+ className,
559
+ inset,
560
+ children,
561
+ onPointerEnter: callerPointerEnter,
562
+ onKeyDown: callerKeyDown,
563
+ ...props
564
+ },
565
+ ref,
566
+ ) => {
567
+ const { hoverProps } = useMenuItemHover({
568
+ onPointerEnter: callerPointerEnter,
569
+ onKeyDown: callerKeyDown,
570
+ });
571
+
572
+ return (
573
+ <DropdownMenuPrimitive.SubTrigger
574
+ ref={ref}
575
+ className={cn(
576
+ "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-[0.3125rem] text-xs outline-none focus:bg-state-hover focus:text-foreground data-[state=open]:bg-state-active data-[state=open]:text-foreground [&_[data-icon-root]]:pointer-events-none [&_[data-icon-root]]:size-4 [&_[data-icon-root]]:shrink-0",
577
+ LIST_HOVER_TRANSITION,
578
+ MENU_ITEM_LAST_HOVERED_CLASS,
579
+ inset && "pl-8",
580
+ className,
581
+ )}
582
+ {...props}
583
+ {...hoverProps}
584
+ >
585
+ {children}
586
+ <Icon name="ChevronRight" className="ml-auto" />
587
+ </DropdownMenuPrimitive.SubTrigger>
588
+ );
589
+ },
590
+ );
591
+ DropdownMenuSubTrigger.displayName =
592
+ DropdownMenuPrimitive.SubTrigger.displayName;
593
+
594
+ const DropdownMenuSubContent = React.forwardRef<
595
+ React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,
596
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
597
+ >(({ className, ...props }, ref) => (
598
+ <DropdownMenuPrimitive.SubContent
599
+ ref={ref}
600
+ {...usePortalScopeProps()}
601
+ className={cn(
602
+ "z-50 min-w-28 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
603
+ className,
604
+ )}
605
+ {...props}
606
+ />
607
+ ));
608
+ DropdownMenuSubContent.displayName =
609
+ DropdownMenuPrimitive.SubContent.displayName;
610
+
611
+ const DropdownMenuShortcut = ({
612
+ className,
613
+ ...props
614
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
615
+ return (
616
+ <span
617
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
618
+ {...props}
619
+ />
620
+ );
621
+ };
622
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
623
+
624
+ export {
625
+ DropdownMenu,
626
+ DropdownMenuTrigger,
627
+ DropdownMenuContent,
628
+ DropdownMenuItem,
629
+ DropdownMenuCheckboxItem,
630
+ DropdownMenuRadioItem,
631
+ DropdownMenuLabel,
632
+ DropdownMenuSeparator,
633
+ DropdownMenuShortcut,
634
+ DropdownMenuGroup,
635
+ DropdownMenuPortal,
636
+ DropdownMenuSub,
637
+ DropdownMenuSubContent,
638
+ DropdownMenuSubTrigger,
639
+ DropdownMenuRadioGroup,
640
+ };
@@ -0,0 +1,37 @@
1
+ import {
2
+ createContext,
3
+ createElement,
4
+ useContext,
5
+ type ReactNode,
6
+ } from "react";
7
+
8
+ import { useMediaQuery } from "./use-media-query.js";
9
+
10
+ export const COMPACT_VIEWPORT_QUERY = "(max-width: 767px)";
11
+
12
+ const CompactViewportOverrideContext = createContext<boolean | null>(null);
13
+
14
+ interface CompactViewportOverrideProviderProps {
15
+ children: ReactNode;
16
+ isCompactViewport: boolean;
17
+ }
18
+
19
+ export function CompactViewportOverrideProvider({
20
+ children,
21
+ isCompactViewport,
22
+ }: CompactViewportOverrideProviderProps) {
23
+ return createElement(
24
+ CompactViewportOverrideContext.Provider,
25
+ { value: isCompactViewport },
26
+ children,
27
+ );
28
+ }
29
+
30
+ export function useIsCompactViewport(): boolean {
31
+ const override = useContext(CompactViewportOverrideContext);
32
+ const isCompactViewport = useMediaQuery(COMPACT_VIEWPORT_QUERY);
33
+ if (override !== null) {
34
+ return override;
35
+ }
36
+ return isCompactViewport;
37
+ }