@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.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/THIRD_PARTY_NOTICES.md +230 -0
- package/app.tsx +12 -0
- package/components/ui/alert-dialog.tsx +142 -0
- package/components/ui/button.tsx +59 -0
- package/components/ui/card.tsx +83 -0
- package/components/ui/coarse-pointer-sizing.ts +66 -0
- package/components/ui/context-menu.tsx +355 -0
- package/components/ui/dialog.tsx +416 -0
- package/components/ui/dropdown-menu.tsx +640 -0
- package/components/ui/hooks/use-compact-viewport.tsx +37 -0
- package/components/ui/hooks/use-media-query.ts +70 -0
- package/components/ui/hooks/use-pointer-coarse.ts +7 -0
- package/components/ui/hover-card.tsx +36 -0
- package/components/ui/icon-extended.tsx +333 -0
- package/components/ui/icon-registry.ts +189 -0
- package/components/ui/icon.tsx +444 -0
- package/components/ui/input.tsx +30 -0
- package/components/ui/menu-item-hover.tsx +93 -0
- package/components/ui/motion.ts +4 -0
- package/components/ui/overlay-trigger.ts +84 -0
- package/components/ui/popover.tsx +223 -0
- package/components/ui/responsive-overlay.tsx +823 -0
- package/components/ui/tooltip.tsx +63 -0
- package/dist/app.css +2 -0
- package/dist/app.js +66 -0
- package/dist/app.meta.json +11 -0
- package/dist/server.js +71 -0
- package/dist/server.js.map +6 -0
- package/dist/server.meta.json +11 -0
- package/features/thread-list/components/Dialogs.tsx +299 -0
- package/features/thread-list/components/FoldRows.tsx +147 -0
- package/features/thread-list/components/GroupSection.tsx +370 -0
- package/features/thread-list/components/ProviderBadge.tsx +57 -0
- package/features/thread-list/components/PullRequestBadge.tsx +50 -0
- package/features/thread-list/components/Rails.tsx +28 -0
- package/features/thread-list/components/RenameEditor.tsx +118 -0
- package/features/thread-list/components/RowMenu.tsx +168 -0
- package/features/thread-list/components/SplitMiniMap.tsx +55 -0
- package/features/thread-list/components/ThreadDetails.tsx +138 -0
- package/features/thread-list/components/ThreadList.tsx +855 -0
- package/features/thread-list/components/ThreadRowView.tsx +631 -0
- package/features/thread-list/components/Toolbar.tsx +296 -0
- package/features/thread-list/components/controller.ts +64 -0
- package/features/thread-list/components/glyphs.tsx +129 -0
- package/features/thread-list/components/row-card.ts +127 -0
- package/features/thread-list/data/storage.ts +18 -0
- package/features/thread-list/data/useAutoExpand.ts +53 -0
- package/features/thread-list/data/useClientPreferences.ts +20 -0
- package/features/thread-list/data/useNotes.ts +41 -0
- package/features/thread-list/data/useNow.ts +19 -0
- package/features/thread-list/data/usePreferences.ts +126 -0
- package/features/thread-list/data/useScheduled.ts +43 -0
- package/features/thread-list/data/useStamps.ts +68 -0
- package/features/thread-list/icons.ts +44 -0
- package/features/thread-list/index.ts +2 -0
- package/features/thread-list/model/attention.ts +89 -0
- package/features/thread-list/model/counters.ts +67 -0
- package/features/thread-list/model/details.ts +79 -0
- package/features/thread-list/model/drag.ts +104 -0
- package/features/thread-list/model/expansion.ts +71 -0
- package/features/thread-list/model/families.ts +297 -0
- package/features/thread-list/model/groups.ts +263 -0
- package/features/thread-list/model/labels.ts +50 -0
- package/features/thread-list/model/layout.ts +61 -0
- package/features/thread-list/model/menu.ts +78 -0
- package/features/thread-list/model/move.ts +46 -0
- package/features/thread-list/model/notes.ts +52 -0
- package/features/thread-list/model/provider-mark.ts +59 -0
- package/features/thread-list/model/share.ts +101 -0
- package/features/thread-list/model/sort.ts +78 -0
- package/features/thread-list/model/state.ts +330 -0
- package/features/thread-list/model/time.ts +107 -0
- package/features/thread-list/model/toggles.ts +99 -0
- package/features/thread-list/model/view.ts +669 -0
- package/features/thread-list/model/windowing.ts +27 -0
- package/features/thread-list/testing/fixtures.ts +162 -0
- package/hooks/useBrowserDimmingModal.ts +5 -0
- package/lib/portal-scope.ts +15 -0
- package/lib/utils.ts +17 -0
- package/package.json +96 -0
- package/server/cli.ts +107 -0
- package/server/failures.ts +136 -0
- package/server/import.ts +144 -0
- package/server/notes.ts +236 -0
- package/server/preference-store.ts +96 -0
- package/server/scheduled.ts +129 -0
- package/server/serial.ts +12 -0
- package/server/stamps.ts +142 -0
- package/server/startup.ts +121 -0
- package/server.ts +126 -0
- package/shared/contract.ts +73 -0
- package/shared/preferences.ts +241 -0
- package/shared/signals.ts +71 -0
- package/skills/thread-glance/SKILL.md +69 -0
- package/tsconfig.json +36 -0
- 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
|
+
}
|