@nikala-ui/core 0.11.0 → 0.12.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.
- package/package.json +43 -2
- package/registry/api-table.json +21 -0
- package/registry/banner.json +1 -1
- package/registry/button.json +1 -1
- package/registry/callout.json +19 -0
- package/registry/checkbox.json +4 -1
- package/registry/code-block.json +26 -0
- package/registry/code-group.json +20 -0
- package/registry/combobox.json +1 -1
- package/registry/command.json +3 -2
- package/registry/component-viewer.json +25 -0
- package/registry/container.json +18 -0
- package/registry/context-menu.json +1 -1
- package/registry/create-app-updater.json +13 -0
- package/registry/create-document-tabs.json +13 -0
- package/registry/create-global-shortcut.json +13 -0
- package/registry/create-tauri-window.json +13 -0
- package/registry/create-tiptap-editor.json +34 -0
- package/registry/dialog.json +1 -1
- package/registry/dropdown-menu.json +3 -2
- package/registry/field.json +1 -1
- package/registry/file-tree.json +21 -0
- package/registry/footer.json +1 -1
- package/registry/form-message.json +3 -2
- package/registry/icon-button.json +1 -1
- package/registry/index.json +336 -5
- package/registry/navbar.json +1 -1
- package/registry/navigation-menu.json +1 -1
- package/registry/number-input.json +1 -1
- package/registry/package-manager-tabs.json +24 -0
- package/registry/pager.json +21 -0
- package/registry/popover.json +4 -1
- package/registry/resizable.json +1 -1
- package/registry/rich-text-editor.json +95 -0
- package/registry/scroll-area.json +1 -1
- package/registry/section-heading.json +21 -0
- package/registry/select.json +3 -2
- package/registry/sheet.json +1 -1
- package/registry/sidebar.json +1 -1
- package/registry/status.json +1 -1
- package/registry/steps.json +20 -0
- package/registry/switch.json +4 -1
- package/registry/table-of-contents.json +23 -0
- package/registry/tabs.json +4 -1
- package/registry/theme-manager.json +6 -5
- package/registry/titlebar-tabs.json +24 -0
- package/registry/titlebar.json +26 -0
- package/registry/toggle-group.json +1 -1
- package/registry/updater-modal.json +26 -0
- package/src/index.ts +48 -0
- package/src/registry/components/ui/api-table.tsx +117 -0
- package/src/registry/components/ui/banner.tsx +0 -2
- package/src/registry/components/ui/button.tsx +1 -1
- package/src/registry/components/ui/callout.tsx +137 -0
- package/src/registry/components/ui/checkbox.tsx +1 -1
- package/src/registry/components/ui/code-block.tsx +326 -0
- package/src/registry/components/ui/code-group.tsx +105 -0
- package/src/registry/components/ui/combobox.tsx +83 -18
- package/src/registry/components/ui/command.tsx +110 -106
- package/src/registry/components/ui/component-viewer.tsx +363 -0
- package/src/registry/components/ui/container.tsx +45 -0
- package/src/registry/components/ui/context-menu.tsx +67 -22
- package/src/registry/components/ui/dialog.tsx +42 -32
- package/src/registry/components/ui/dropdown-menu.tsx +69 -31
- package/src/registry/components/ui/field.tsx +7 -3
- package/src/registry/components/ui/file-tree.tsx +181 -0
- package/src/registry/components/ui/footer.tsx +1 -1
- package/src/registry/components/ui/form-message.tsx +2 -2
- package/src/registry/components/ui/icon-button.tsx +1 -1
- package/src/registry/components/ui/navbar.tsx +18 -11
- package/src/registry/components/ui/navigation-menu.tsx +2 -2
- package/src/registry/components/ui/number-input.tsx +3 -3
- package/src/registry/components/ui/package-manager-tabs.tsx +226 -0
- package/src/registry/components/ui/pager.tsx +102 -0
- package/src/registry/components/ui/popover.tsx +1 -1
- package/src/registry/components/ui/resizable.tsx +3 -1
- package/src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
- package/src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
- package/src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
- package/src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
- package/src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
- package/src/registry/components/ui/rich-text-editor/index.ts +11 -0
- package/src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
- package/src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
- package/src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
- package/src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
- package/src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
- package/src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
- package/src/registry/components/ui/scroll-area.tsx +12 -2
- package/src/registry/components/ui/section-heading.tsx +108 -0
- package/src/registry/components/ui/select.tsx +16 -19
- package/src/registry/components/ui/sheet.tsx +58 -53
- package/src/registry/components/ui/sidebar.tsx +4 -4
- package/src/registry/components/ui/status.tsx +7 -5
- package/src/registry/components/ui/steps.tsx +198 -0
- package/src/registry/components/ui/switch.tsx +1 -1
- package/src/registry/components/ui/table-of-contents.tsx +131 -0
- package/src/registry/components/ui/tabs.tsx +1 -1
- package/src/registry/components/ui/theme-toggle.tsx +175 -139
- package/src/registry/components/ui/titlebar-tabs.tsx +773 -0
- package/src/registry/components/ui/titlebar.tsx +468 -0
- package/src/registry/components/ui/toggle-group.tsx +1 -1
- package/src/registry/components/ui/updater-modal.tsx +254 -0
- package/src/registry/metadata.ts +167 -6
- package/src/registry/providers/theme-provider.tsx +16 -8
- package/src/registry/providers/theme-script.tsx +29 -9
- package/src/registry/providers/theme-transitions.ts +81 -50
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
splitProps,
|
|
5
|
+
createSignal,
|
|
6
|
+
createEffect,
|
|
7
|
+
onMount,
|
|
8
|
+
onCleanup,
|
|
9
|
+
Show,
|
|
10
|
+
For,
|
|
11
|
+
type Component,
|
|
12
|
+
type JSX,
|
|
13
|
+
type ParentComponent,
|
|
14
|
+
type Accessor,
|
|
15
|
+
} from "solid-js";
|
|
16
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
17
|
+
import { Plus, X, Pin, XCircle, CircleX } from "lucide-solid";
|
|
18
|
+
import * as ContextMenuPrimitive from "@kobalte/core/context-menu";
|
|
19
|
+
import { Button } from "@/components/ui/button";
|
|
20
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
|
21
|
+
import {
|
|
22
|
+
type CreateDocumentTabsReturn,
|
|
23
|
+
type TabItem,
|
|
24
|
+
} from "@/hooks/create-document-tabs";
|
|
25
|
+
import { cn } from "@/lib/cn";
|
|
26
|
+
|
|
27
|
+
/* --- 1. Context & Variants --- */
|
|
28
|
+
|
|
29
|
+
export type TitlebarTabsVariant = "editor" | "chrome" | "pills";
|
|
30
|
+
|
|
31
|
+
interface TitlebarTabsContextValue {
|
|
32
|
+
value: Accessor<string | undefined>;
|
|
33
|
+
setValue: (value: string) => void;
|
|
34
|
+
variant: Accessor<TitlebarTabsVariant>;
|
|
35
|
+
manager?: CreateDocumentTabsReturn<any>;
|
|
36
|
+
reorderable?: boolean;
|
|
37
|
+
enableContextMenu?: boolean;
|
|
38
|
+
draggedTabId: Accessor<string | null>;
|
|
39
|
+
dropTargetId: Accessor<string | null>;
|
|
40
|
+
dropPosition: Accessor<"left" | "right" | null>;
|
|
41
|
+
setDragState: (draggedId: string | null, targetId: string | null, position: "left" | "right" | null) => void;
|
|
42
|
+
handleDropReorder: (sourceId: string, targetId: string, position: "left" | "right") => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const TitlebarTabsContext = createContext<TitlebarTabsContextValue>();
|
|
46
|
+
|
|
47
|
+
export function useTitlebarTabs() {
|
|
48
|
+
const ctx = useContext(TitlebarTabsContext);
|
|
49
|
+
if (!ctx) {
|
|
50
|
+
throw new Error("useTitlebarTabs must be used within a <TitlebarTabs /> container");
|
|
51
|
+
}
|
|
52
|
+
return ctx;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const titlebarTabVariants = cva(
|
|
56
|
+
"group relative inline-flex items-center gap-1.5 px-3 text-xs font-medium select-none cursor-pointer transition-colors shrink-0 max-w-[180px] min-w-[80px]",
|
|
57
|
+
{
|
|
58
|
+
variants: {
|
|
59
|
+
variant: {
|
|
60
|
+
editor:
|
|
61
|
+
"h-full py-1 border-r border-border/40 border-t-2 border-t-transparent hover:bg-muted/40 data-[active=true]:bg-background data-[active=true]:border-t-primary data-[active=true]:text-foreground data-[active=false]:text-muted-foreground",
|
|
62
|
+
chrome:
|
|
63
|
+
"h-[calc(100%-2px)] py-1 rounded-t-lg border-t border-x border-transparent hover:bg-muted/60 data-[active=true]:border-border/70 data-[active=true]:bg-background data-[active=true]:text-foreground data-[active=true]:-mb-px data-[active=true]:shadow-xs data-[active=false]:text-muted-foreground",
|
|
64
|
+
pills:
|
|
65
|
+
"h-7 py-0.5 rounded-md mx-0.5 hover:bg-muted/50 data-[active=true]:bg-primary/10 data-[active=true]:text-primary data-[active=true]:font-semibold data-[active=false]:text-muted-foreground",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
variant: "editor",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/* --- 2. TitlebarTabs Root Container --- */
|
|
75
|
+
|
|
76
|
+
export interface TitlebarTabsProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
77
|
+
/** Reactive tabs manager instance from createDocumentTabs(). */
|
|
78
|
+
manager?: CreateDocumentTabsReturn<any>;
|
|
79
|
+
/** Controlled active tab identifier value (when not using manager). */
|
|
80
|
+
value?: string;
|
|
81
|
+
/** Uncontrolled default active tab identifier value. */
|
|
82
|
+
defaultValue?: string;
|
|
83
|
+
/** Callback triggered when active tab changes. */
|
|
84
|
+
onValueChange?: (value: string) => void;
|
|
85
|
+
/** Callback triggered when the "+" add tab button is clicked. */
|
|
86
|
+
onAddTab?: () => void;
|
|
87
|
+
/** Whether tabs can be reordered via Drag & Drop. Defaults to true when manager is present. */
|
|
88
|
+
reorderable?: boolean;
|
|
89
|
+
/** Whether right-click context menu is enabled on tabs and empty space. Defaults to true. */
|
|
90
|
+
enableContextMenu?: boolean;
|
|
91
|
+
/** Custom render function for the empty tabstrip context menu. */
|
|
92
|
+
renderEmptyContextMenu?: () => JSX.Element;
|
|
93
|
+
/** Visual chrome styling variant. */
|
|
94
|
+
variant?: TitlebarTabsVariant;
|
|
95
|
+
class?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const TitlebarTabs: ParentComponent<TitlebarTabsProps> = (props) => {
|
|
99
|
+
const [local, rest] = splitProps(props, [
|
|
100
|
+
"manager",
|
|
101
|
+
"value",
|
|
102
|
+
"defaultValue",
|
|
103
|
+
"onValueChange",
|
|
104
|
+
"onAddTab",
|
|
105
|
+
"reorderable",
|
|
106
|
+
"enableContextMenu",
|
|
107
|
+
"renderEmptyContextMenu",
|
|
108
|
+
"variant",
|
|
109
|
+
"class",
|
|
110
|
+
"children",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
let scrollContainerRef: HTMLDivElement | undefined;
|
|
114
|
+
|
|
115
|
+
const [internalValue, setInternalValue] = createSignal(local.defaultValue || local.value);
|
|
116
|
+
const [draggedTabId, setDraggedTabId] = createSignal<string | null>(null);
|
|
117
|
+
const [dropTargetId, setDropTargetId] = createSignal<string | null>(null);
|
|
118
|
+
const [dropPosition, setDropPosition] = createSignal<"left" | "right" | null>(null);
|
|
119
|
+
|
|
120
|
+
const activeValue = () => {
|
|
121
|
+
if (local.manager) return local.manager.activeTabId();
|
|
122
|
+
return local.value !== undefined ? local.value : internalValue();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const variant = () => local.variant || "editor";
|
|
126
|
+
|
|
127
|
+
const setValue = (val: string) => {
|
|
128
|
+
if (local.manager) {
|
|
129
|
+
local.manager.setActiveTab(val);
|
|
130
|
+
} else {
|
|
131
|
+
setInternalValue(val);
|
|
132
|
+
local.onValueChange?.(val);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const setDragState = (
|
|
137
|
+
draggedId: string | null,
|
|
138
|
+
targetId: string | null,
|
|
139
|
+
pos: "left" | "right" | null
|
|
140
|
+
) => {
|
|
141
|
+
setDraggedTabId(draggedId);
|
|
142
|
+
setDropTargetId(targetId);
|
|
143
|
+
setDropPosition(pos);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const handleDropReorder = (sourceId: string, targetId: string, pos: "left" | "right") => {
|
|
147
|
+
if (!local.manager) return;
|
|
148
|
+
const list = local.manager.tabs();
|
|
149
|
+
const fromIndex = list.findIndex((t) => t.id === sourceId);
|
|
150
|
+
const targetIndex = list.findIndex((t) => t.id === targetId);
|
|
151
|
+
if (fromIndex === -1 || targetIndex === -1 || fromIndex === targetIndex) return;
|
|
152
|
+
|
|
153
|
+
let finalIndex = targetIndex;
|
|
154
|
+
if (fromIndex < targetIndex && pos === "left") {
|
|
155
|
+
finalIndex = targetIndex - 1;
|
|
156
|
+
} else if (fromIndex > targetIndex && pos === "right") {
|
|
157
|
+
finalIndex = targetIndex + 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (fromIndex !== finalIndex && finalIndex >= 0 && finalIndex < list.length) {
|
|
161
|
+
local.manager.reorderTabs(fromIndex, finalIndex);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Mouse wheel horizontal scroll listener (converts vertical wheel deltaY to horizontal scroll)
|
|
166
|
+
onMount(() => {
|
|
167
|
+
if (!scrollContainerRef) return;
|
|
168
|
+
|
|
169
|
+
const handleWheel = (e: WheelEvent) => {
|
|
170
|
+
if (e.deltaY !== 0) {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
scrollContainerRef!.scrollLeft += e.deltaY;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
scrollContainerRef.addEventListener("wheel", handleWheel, { passive: false });
|
|
177
|
+
onCleanup(() => {
|
|
178
|
+
scrollContainerRef?.removeEventListener("wheel", handleWheel);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Auto-scroll to end when tabs are added
|
|
183
|
+
createEffect(() => {
|
|
184
|
+
if (local.manager?.count() && scrollContainerRef) {
|
|
185
|
+
scrollContainerRef.scrollTo({
|
|
186
|
+
left: scrollContainerRef.scrollWidth,
|
|
187
|
+
behavior: "smooth",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const contextValue: TitlebarTabsContextValue = {
|
|
193
|
+
value: activeValue,
|
|
194
|
+
setValue,
|
|
195
|
+
variant,
|
|
196
|
+
manager: local.manager,
|
|
197
|
+
reorderable: local.reorderable ?? Boolean(local.manager),
|
|
198
|
+
enableContextMenu: local.enableContextMenu ?? true,
|
|
199
|
+
draggedTabId,
|
|
200
|
+
dropTargetId,
|
|
201
|
+
dropPosition,
|
|
202
|
+
setDragState,
|
|
203
|
+
handleDropReorder,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const hasEmptyContextMenu = () =>
|
|
207
|
+
(local.enableContextMenu ?? true) &&
|
|
208
|
+
(Boolean(local.onAddTab) || Boolean(local.manager) || Boolean(local.renderEmptyContextMenu));
|
|
209
|
+
|
|
210
|
+
const renderNavContent = () => (
|
|
211
|
+
<nav
|
|
212
|
+
aria-label="Window Tabs"
|
|
213
|
+
class={cn(
|
|
214
|
+
"flex flex-1 h-full overflow-hidden z-10 min-w-0 pointer-events-auto [app-region:drag] [-webkit-app-region:drag]",
|
|
215
|
+
variant() === "chrome" ? "items-end" : "items-center",
|
|
216
|
+
local.class
|
|
217
|
+
)}
|
|
218
|
+
{...rest}
|
|
219
|
+
>
|
|
220
|
+
<Show
|
|
221
|
+
when={local.children}
|
|
222
|
+
fallback={
|
|
223
|
+
/* Automatic Declarative Mode: Tab List with Mouse Wheel Scroll + Anchored Add Button */
|
|
224
|
+
<>
|
|
225
|
+
<div
|
|
226
|
+
ref={scrollContainerRef}
|
|
227
|
+
class={cn(
|
|
228
|
+
"flex h-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] gap-0 items-center min-w-0 flex-1 scroll-smooth [app-region:drag] [-webkit-app-region:drag]",
|
|
229
|
+
variant() === "chrome" && "items-end pt-1.5"
|
|
230
|
+
)}
|
|
231
|
+
>
|
|
232
|
+
<Show when={local.manager}>
|
|
233
|
+
<For each={local.manager!.tabs()}>
|
|
234
|
+
{(tab) => <TitlebarTab tab={tab} />}
|
|
235
|
+
</For>
|
|
236
|
+
</Show>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Anchored Add Tab Button (Always visible outside scroll container) */}
|
|
240
|
+
<Show when={local.onAddTab}>
|
|
241
|
+
<div class={cn("shrink-0 px-1 z-20 flex items-center", variant() === "chrome" ? "mb-1" : "my-auto")}>
|
|
242
|
+
<TitlebarTabAddButton onClick={local.onAddTab} />
|
|
243
|
+
</div>
|
|
244
|
+
</Show>
|
|
245
|
+
</>
|
|
246
|
+
}
|
|
247
|
+
>
|
|
248
|
+
{local.children}
|
|
249
|
+
</Show>
|
|
250
|
+
</nav>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<TitlebarTabsContext.Provider value={contextValue}>
|
|
255
|
+
<Show when={hasEmptyContextMenu()} fallback={renderNavContent()}>
|
|
256
|
+
<ContextMenuPrimitive.Root>
|
|
257
|
+
<ContextMenuPrimitive.Trigger
|
|
258
|
+
as="div"
|
|
259
|
+
style={("-webkit-app-region: no-drag; app-region: no-drag; outline: none !important; -webkit-focus-ring-color: transparent !important;") as any}
|
|
260
|
+
class="flex flex-1 h-full min-w-0 outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0"
|
|
261
|
+
>
|
|
262
|
+
{renderNavContent()}
|
|
263
|
+
</ContextMenuPrimitive.Trigger>
|
|
264
|
+
<ContextMenuPrimitive.Portal>
|
|
265
|
+
<ContextMenuPrimitive.Content
|
|
266
|
+
data-no-drag
|
|
267
|
+
data-tauri-drag-region="false"
|
|
268
|
+
style="-webkit-app-region: no-drag; app-region: no-drag; outline: none !important; -webkit-focus-ring-color: transparent !important;"
|
|
269
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
270
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
271
|
+
class={cn(
|
|
272
|
+
"z-50 min-w-[160px] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
|
|
273
|
+
"animate-in fade-in-80 slide-in-from-top-1 text-xs pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]",
|
|
274
|
+
"outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0"
|
|
275
|
+
)}
|
|
276
|
+
>
|
|
277
|
+
<Show
|
|
278
|
+
when={local.renderEmptyContextMenu}
|
|
279
|
+
fallback={
|
|
280
|
+
<>
|
|
281
|
+
<Show when={local.onAddTab}>
|
|
282
|
+
<ContextMenuPrimitive.Item
|
|
283
|
+
data-no-drag
|
|
284
|
+
data-tauri-drag-region="false"
|
|
285
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
286
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
287
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
288
|
+
onSelect={() => {
|
|
289
|
+
setTimeout(() => local.onAddTab?.(), 0);
|
|
290
|
+
}}
|
|
291
|
+
class="relative flex cursor-pointer select-none items-center justify-between gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
292
|
+
>
|
|
293
|
+
<span class="flex items-center gap-2">
|
|
294
|
+
<Plus class="size-3.5 text-muted-foreground" />
|
|
295
|
+
New Tab
|
|
296
|
+
</span>
|
|
297
|
+
<span class="ml-auto text-[10px] text-muted-foreground font-mono">Ctrl+T</span>
|
|
298
|
+
</ContextMenuPrimitive.Item>
|
|
299
|
+
</Show>
|
|
300
|
+
|
|
301
|
+
<Show when={local.manager && local.manager.tabs().length > 0}>
|
|
302
|
+
<Show when={local.onAddTab}>
|
|
303
|
+
<ContextMenuPrimitive.Separator class="my-1 h-px bg-border/60" />
|
|
304
|
+
</Show>
|
|
305
|
+
<ContextMenuPrimitive.Item
|
|
306
|
+
data-no-drag
|
|
307
|
+
data-tauri-drag-region="false"
|
|
308
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
309
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
310
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
311
|
+
onSelect={() => {
|
|
312
|
+
setTimeout(() => local.manager?.closeAll(), 0);
|
|
313
|
+
}}
|
|
314
|
+
class="relative flex cursor-pointer select-none items-center gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
315
|
+
>
|
|
316
|
+
<CircleX class="size-3.5 text-muted-foreground" />
|
|
317
|
+
<span>Close All Tabs</span>
|
|
318
|
+
</ContextMenuPrimitive.Item>
|
|
319
|
+
</Show>
|
|
320
|
+
</>
|
|
321
|
+
}
|
|
322
|
+
>
|
|
323
|
+
{local.renderEmptyContextMenu!()}
|
|
324
|
+
</Show>
|
|
325
|
+
</ContextMenuPrimitive.Content>
|
|
326
|
+
</ContextMenuPrimitive.Portal>
|
|
327
|
+
</ContextMenuPrimitive.Root>
|
|
328
|
+
</Show>
|
|
329
|
+
</TitlebarTabsContext.Provider>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/* --- 3. TitlebarTabList --- */
|
|
334
|
+
|
|
335
|
+
export interface TitlebarTabListProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
336
|
+
class?: string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export const TitlebarTabList: ParentComponent<TitlebarTabListProps> = (props) => {
|
|
340
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
341
|
+
const ctx = useTitlebarTabs();
|
|
342
|
+
let listRef: HTMLDivElement | undefined;
|
|
343
|
+
|
|
344
|
+
onMount(() => {
|
|
345
|
+
if (!listRef) return;
|
|
346
|
+
const handleWheel = (e: WheelEvent) => {
|
|
347
|
+
if (e.deltaY !== 0) {
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
listRef!.scrollLeft += e.deltaY;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
listRef.addEventListener("wheel", handleWheel, { passive: false });
|
|
353
|
+
onCleanup(() => listRef?.removeEventListener("wheel", handleWheel));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div
|
|
358
|
+
ref={listRef}
|
|
359
|
+
class={cn(
|
|
360
|
+
"flex h-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] gap-0 items-center min-w-0 flex-1 scroll-smooth [app-region:drag] [-webkit-app-region:drag]",
|
|
361
|
+
ctx.variant() === "chrome" && "items-end pt-1.5",
|
|
362
|
+
local.class
|
|
363
|
+
)}
|
|
364
|
+
{...rest}
|
|
365
|
+
>
|
|
366
|
+
{local.children}
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/* --- 4. TitlebarTab Individual Item --- */
|
|
372
|
+
|
|
373
|
+
export interface TitlebarTabProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onClose"> {
|
|
374
|
+
/** Tab data object from createDocumentTabs(). Automatically wires all properties. */
|
|
375
|
+
tab?: TabItem<any>;
|
|
376
|
+
value?: string;
|
|
377
|
+
isDirty?: boolean;
|
|
378
|
+
isPinned?: boolean;
|
|
379
|
+
closable?: boolean;
|
|
380
|
+
reorderable?: boolean;
|
|
381
|
+
/** Whether right-click context menu is enabled on this tab. Defaults to true. */
|
|
382
|
+
enableContextMenu?: boolean;
|
|
383
|
+
/** Custom render function for context menu content. */
|
|
384
|
+
renderContextMenu?: (tab: TabItem<any>) => JSX.Element;
|
|
385
|
+
onClose?: (e: MouseEvent) => void;
|
|
386
|
+
class?: string;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export const TitlebarTab: ParentComponent<TitlebarTabProps> = (props) => {
|
|
390
|
+
const [local, rest] = splitProps(props, [
|
|
391
|
+
"tab",
|
|
392
|
+
"value",
|
|
393
|
+
"isDirty",
|
|
394
|
+
"isPinned",
|
|
395
|
+
"closable",
|
|
396
|
+
"reorderable",
|
|
397
|
+
"enableContextMenu",
|
|
398
|
+
"renderContextMenu",
|
|
399
|
+
"onClose",
|
|
400
|
+
"class",
|
|
401
|
+
"style",
|
|
402
|
+
"children",
|
|
403
|
+
"onClick",
|
|
404
|
+
"onKeyDown",
|
|
405
|
+
"onDragStart",
|
|
406
|
+
"onDragOver",
|
|
407
|
+
"onDragLeave",
|
|
408
|
+
"onDragEnd",
|
|
409
|
+
"onDrop",
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
const ctx = useTitlebarTabs();
|
|
413
|
+
let tabRef: HTMLDivElement | undefined;
|
|
414
|
+
|
|
415
|
+
const tabId = () => local.tab?.id || local.value || "";
|
|
416
|
+
const isPinned = () => local.tab?.isPinned ?? local.isPinned ?? false;
|
|
417
|
+
const isDirty = () => local.tab?.isDirty ?? local.isDirty ?? false;
|
|
418
|
+
const closable = () => local.tab?.closable ?? local.closable ?? true;
|
|
419
|
+
const title = () => local.tab?.title || "";
|
|
420
|
+
const IconComp = () => local.tab?.icon;
|
|
421
|
+
|
|
422
|
+
const isActive = () => ctx.value() === tabId();
|
|
423
|
+
const isDragging = () => ctx.draggedTabId() === tabId();
|
|
424
|
+
const isDropTarget = () => ctx.dropTargetId() === tabId() && ctx.draggedTabId() !== tabId();
|
|
425
|
+
const dropPosition = () => (isDropTarget() ? ctx.dropPosition() : null);
|
|
426
|
+
|
|
427
|
+
const canDrag = () => !isPinned() && (local.reorderable ?? ctx.reorderable ?? true);
|
|
428
|
+
const hasContextMenu = () =>
|
|
429
|
+
(local.enableContextMenu ?? ctx.enableContextMenu ?? true) &&
|
|
430
|
+
(Boolean(ctx.manager) || Boolean(local.renderContextMenu));
|
|
431
|
+
|
|
432
|
+
// Scroll active tab horizontally inside its tablist container
|
|
433
|
+
createEffect(() => {
|
|
434
|
+
if (typeof window !== "undefined" && isActive() && tabRef && tabRef.parentElement) {
|
|
435
|
+
try {
|
|
436
|
+
const container = tabRef.parentElement;
|
|
437
|
+
const tabLeft = tabRef.offsetLeft;
|
|
438
|
+
const tabRight = tabLeft + tabRef.offsetWidth;
|
|
439
|
+
const containerLeft = container.scrollLeft;
|
|
440
|
+
const containerRight = containerLeft + container.clientWidth;
|
|
441
|
+
|
|
442
|
+
if (tabLeft < containerLeft) {
|
|
443
|
+
container.scrollTo({ left: tabLeft, behavior: "smooth" });
|
|
444
|
+
} else if (tabRight > containerRight) {
|
|
445
|
+
container.scrollTo({ left: tabRight - container.clientWidth, behavior: "smooth" });
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
// Safe fallback
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const handleClick = (e: MouseEvent) => {
|
|
454
|
+
ctx.setValue(tabId());
|
|
455
|
+
if (typeof local.onClick === "function") {
|
|
456
|
+
(local.onClick as any)(e);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
461
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
462
|
+
e.preventDefault();
|
|
463
|
+
ctx.setValue(tabId());
|
|
464
|
+
}
|
|
465
|
+
if (typeof (local as any).onKeyDown === "function") {
|
|
466
|
+
((local as any).onKeyDown)(e);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const handleClose = (e: MouseEvent) => {
|
|
471
|
+
e.stopPropagation();
|
|
472
|
+
if (local.onClose) {
|
|
473
|
+
local.onClose(e);
|
|
474
|
+
} else if (ctx.manager) {
|
|
475
|
+
ctx.manager.closeTab(tabId());
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const handleDragStart: JSX.EventHandlerUnion<HTMLDivElement, DragEvent> = (e) => {
|
|
480
|
+
if (!canDrag()) {
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (e.dataTransfer) {
|
|
485
|
+
e.dataTransfer.setData("text/plain", tabId());
|
|
486
|
+
e.dataTransfer.effectAllowed = "move";
|
|
487
|
+
}
|
|
488
|
+
ctx.setDragState(tabId(), null, null);
|
|
489
|
+
if (typeof local.onDragStart === "function") {
|
|
490
|
+
(local.onDragStart as any)(e);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const handleDragOver: JSX.EventHandlerUnion<HTMLDivElement, DragEvent> = (e) => {
|
|
495
|
+
const currentDragged = ctx.draggedTabId();
|
|
496
|
+
if (!currentDragged || currentDragged === tabId() || isPinned()) return;
|
|
497
|
+
|
|
498
|
+
e.preventDefault();
|
|
499
|
+
if (e.dataTransfer) {
|
|
500
|
+
e.dataTransfer.dropEffect = "move";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (tabRef) {
|
|
504
|
+
const rect = tabRef.getBoundingClientRect();
|
|
505
|
+
const midpoint = rect.left + rect.width / 2;
|
|
506
|
+
const pos: "left" | "right" = e.clientX < midpoint ? "left" : "right";
|
|
507
|
+
ctx.setDragState(currentDragged, tabId(), pos);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (typeof local.onDragOver === "function") {
|
|
511
|
+
(local.onDragOver as any)(e);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const handleDragLeave: JSX.EventHandlerUnion<HTMLDivElement, DragEvent> = (e) => {
|
|
516
|
+
if (tabRef && !tabRef.contains(e.relatedTarget as Node)) {
|
|
517
|
+
if (ctx.dropTargetId() === tabId()) {
|
|
518
|
+
ctx.setDragState(ctx.draggedTabId(), null, null);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (typeof local.onDragLeave === "function") {
|
|
522
|
+
(local.onDragLeave as any)(e);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const handleDragEnd: JSX.EventHandlerUnion<HTMLDivElement, DragEvent> = (e) => {
|
|
527
|
+
ctx.setDragState(null, null, null);
|
|
528
|
+
if (typeof local.onDragEnd === "function") {
|
|
529
|
+
(local.onDragEnd as any)(e);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const handleDrop: JSX.EventHandlerUnion<HTMLDivElement, DragEvent> = (e) => {
|
|
534
|
+
e.preventDefault();
|
|
535
|
+
const sourceId = e.dataTransfer?.getData("text/plain") || ctx.draggedTabId();
|
|
536
|
+
const pos = ctx.dropPosition();
|
|
537
|
+
if (sourceId && pos && sourceId !== tabId()) {
|
|
538
|
+
ctx.handleDropReorder(sourceId, tabId(), pos);
|
|
539
|
+
}
|
|
540
|
+
ctx.setDragState(null, null, null);
|
|
541
|
+
if (typeof local.onDrop === "function") {
|
|
542
|
+
(local.onDrop as any)(e);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const renderTabElement = () => (
|
|
547
|
+
<div
|
|
548
|
+
ref={tabRef}
|
|
549
|
+
role="tab"
|
|
550
|
+
tabindex="0"
|
|
551
|
+
aria-selected={isActive()}
|
|
552
|
+
draggable={canDrag()}
|
|
553
|
+
onDragStart={handleDragStart}
|
|
554
|
+
onDragOver={handleDragOver}
|
|
555
|
+
onDragLeave={handleDragLeave}
|
|
556
|
+
onDragEnd={handleDragEnd}
|
|
557
|
+
onDrop={handleDrop}
|
|
558
|
+
data-no-drag
|
|
559
|
+
data-tauri-drag-region="false"
|
|
560
|
+
style={("-webkit-app-region: no-drag; app-region: no-drag;" + (typeof local.style === "string" ? ` ${local.style}` : "")) as any}
|
|
561
|
+
data-active={isActive() ? "true" : "false"}
|
|
562
|
+
data-dragging={isDragging() ? "true" : undefined}
|
|
563
|
+
onClick={handleClick}
|
|
564
|
+
onKeyDown={handleKeyDown}
|
|
565
|
+
class={cn(
|
|
566
|
+
titlebarTabVariants({ variant: ctx.variant() }),
|
|
567
|
+
isPinned() && "min-w-fit px-2 max-w-fit",
|
|
568
|
+
isDragging() && "opacity-40 scale-95",
|
|
569
|
+
dropPosition() === "left" &&
|
|
570
|
+
"before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-primary before:z-30 before:rouded-lg",
|
|
571
|
+
dropPosition() === "right" &&
|
|
572
|
+
"after:absolute after:right-0 after:top-1 after:bottom-1 after:w-0.5 after:bg-primary after:z-30 after:rouded-lg",
|
|
573
|
+
"cursor-pointer outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 [app-region:no-drag] [-webkit-app-region:no-drag] pointer-events-auto transition-all duration-150",
|
|
574
|
+
local.class
|
|
575
|
+
)}
|
|
576
|
+
{...rest}
|
|
577
|
+
>
|
|
578
|
+
{/* Tab Icon & Title Slot */}
|
|
579
|
+
<span class="flex items-center gap-1.5 truncate flex-1 pointer-events-none">
|
|
580
|
+
<Show when={IconComp()}>
|
|
581
|
+
{(() => {
|
|
582
|
+
const Comp = IconComp()!;
|
|
583
|
+
return <Comp class="size-3.5 shrink-0 text-primary" />;
|
|
584
|
+
})()}
|
|
585
|
+
</Show>
|
|
586
|
+
<Show when={local.children} fallback={<span class="truncate">{title()}</span>}>
|
|
587
|
+
{local.children}
|
|
588
|
+
</Show>
|
|
589
|
+
</span>
|
|
590
|
+
|
|
591
|
+
{/* Pinned Tab Badge */}
|
|
592
|
+
<Show when={isPinned()}>
|
|
593
|
+
<Pin class="size-2.5 text-muted-foreground rotate-45 shrink-0" />
|
|
594
|
+
</Show>
|
|
595
|
+
|
|
596
|
+
{/* Close Button / Dirty Dot Indicator using Nikala UI Button */}
|
|
597
|
+
<Show when={!isPinned() && closable()}>
|
|
598
|
+
<Tooltip>
|
|
599
|
+
<TooltipTrigger
|
|
600
|
+
as={Button}
|
|
601
|
+
variant="ghost"
|
|
602
|
+
size="icon"
|
|
603
|
+
onClick={handleClose}
|
|
604
|
+
aria-label={`Close ${tabId()} tab`}
|
|
605
|
+
class="size-4 p-0 rounded-xs text-muted-foreground hover:bg-muted hover:text-foreground shrink-0 ml-0.5 cursor-pointer outline-none ring-0 focus:ring-0 focus-visible:ring-0"
|
|
606
|
+
>
|
|
607
|
+
<Show
|
|
608
|
+
when={isDirty()}
|
|
609
|
+
fallback={<X class="size-3 opacity-60 hover:opacity-100" />}
|
|
610
|
+
>
|
|
611
|
+
<span class="size-1.5 rouded-lg bg-primary group-hover:hidden" />
|
|
612
|
+
<X class="size-3 hidden group-hover:block" />
|
|
613
|
+
</Show>
|
|
614
|
+
</TooltipTrigger>
|
|
615
|
+
<TooltipContent class="text-[10px] py-1 px-2">
|
|
616
|
+
Close tab (Ctrl+W)
|
|
617
|
+
</TooltipContent>
|
|
618
|
+
</Tooltip>
|
|
619
|
+
</Show>
|
|
620
|
+
</div>
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<Show when={hasContextMenu()} fallback={renderTabElement()}>
|
|
625
|
+
<ContextMenuPrimitive.Root>
|
|
626
|
+
<ContextMenuPrimitive.Trigger
|
|
627
|
+
as="div"
|
|
628
|
+
data-no-drag
|
|
629
|
+
data-tauri-drag-region="false"
|
|
630
|
+
style={("-webkit-app-region: no-drag; app-region: no-drag; outline: none !important; -webkit-focus-ring-color: transparent !important;" + (typeof local.style === "string" ? ` ${local.style}` : "")) as any}
|
|
631
|
+
class="h-full flex items-center shrink-0 pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag] outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0"
|
|
632
|
+
>
|
|
633
|
+
{renderTabElement()}
|
|
634
|
+
</ContextMenuPrimitive.Trigger>
|
|
635
|
+
<ContextMenuPrimitive.Portal>
|
|
636
|
+
<ContextMenuPrimitive.Content
|
|
637
|
+
data-no-drag
|
|
638
|
+
data-tauri-drag-region="false"
|
|
639
|
+
style="-webkit-app-region: no-drag; app-region: no-drag; outline: none !important; -webkit-focus-ring-color: transparent !important;"
|
|
640
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
641
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
642
|
+
class={cn(
|
|
643
|
+
"z-50 min-w-[170px] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
|
|
644
|
+
"animate-in fade-in-80 slide-in-from-top-1 text-xs pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]",
|
|
645
|
+
"outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0"
|
|
646
|
+
)}
|
|
647
|
+
>
|
|
648
|
+
<Show
|
|
649
|
+
when={local.renderContextMenu}
|
|
650
|
+
fallback={
|
|
651
|
+
<>
|
|
652
|
+
<Show when={ctx.manager}>
|
|
653
|
+
<ContextMenuPrimitive.Item
|
|
654
|
+
data-no-drag
|
|
655
|
+
data-tauri-drag-region="false"
|
|
656
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
657
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
658
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
659
|
+
onSelect={() => {
|
|
660
|
+
setTimeout(() => ctx.manager?.togglePin(tabId()), 0);
|
|
661
|
+
}}
|
|
662
|
+
class="relative flex cursor-pointer select-none items-center gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
663
|
+
>
|
|
664
|
+
<Pin class="size-3.5 text-muted-foreground" />
|
|
665
|
+
<span>{isPinned() ? "Unpin Tab" : "Pin Tab"}</span>
|
|
666
|
+
</ContextMenuPrimitive.Item>
|
|
667
|
+
<ContextMenuPrimitive.Separator class="my-1 h-px bg-border/60" />
|
|
668
|
+
</Show>
|
|
669
|
+
|
|
670
|
+
<ContextMenuPrimitive.Item
|
|
671
|
+
data-no-drag
|
|
672
|
+
data-tauri-drag-region="false"
|
|
673
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
674
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
675
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
676
|
+
disabled={isPinned() || !closable()}
|
|
677
|
+
onSelect={() => {
|
|
678
|
+
setTimeout(() => {
|
|
679
|
+
if (local.onClose) local.onClose(new MouseEvent("click"));
|
|
680
|
+
else ctx.manager?.closeTab(tabId());
|
|
681
|
+
}, 0);
|
|
682
|
+
}}
|
|
683
|
+
class="relative flex cursor-pointer select-none items-center justify-between gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
684
|
+
>
|
|
685
|
+
<span class="flex items-center gap-2">
|
|
686
|
+
<X class="size-3.5 text-muted-foreground" />
|
|
687
|
+
Close Tab
|
|
688
|
+
</span>
|
|
689
|
+
<span class="ml-auto text-[10px] text-muted-foreground font-mono">Ctrl+W</span>
|
|
690
|
+
</ContextMenuPrimitive.Item>
|
|
691
|
+
|
|
692
|
+
<Show when={ctx.manager && ctx.manager.tabs().length > 1}>
|
|
693
|
+
<ContextMenuPrimitive.Item
|
|
694
|
+
data-no-drag
|
|
695
|
+
data-tauri-drag-region="false"
|
|
696
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
697
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
698
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
699
|
+
onSelect={() => {
|
|
700
|
+
setTimeout(() => ctx.manager?.closeOthers(tabId()), 0);
|
|
701
|
+
}}
|
|
702
|
+
class="relative flex cursor-pointer select-none items-center gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
703
|
+
>
|
|
704
|
+
<CircleX class="size-3.5 text-muted-foreground" />
|
|
705
|
+
Close Other Tabs
|
|
706
|
+
</ContextMenuPrimitive.Item>
|
|
707
|
+
<ContextMenuPrimitive.Item
|
|
708
|
+
data-no-drag
|
|
709
|
+
data-tauri-drag-region="false"
|
|
710
|
+
style="-webkit-app-region: no-drag; app-region: no-drag;"
|
|
711
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
712
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
713
|
+
onSelect={() => {
|
|
714
|
+
setTimeout(() => ctx.manager?.closeAll(), 0);
|
|
715
|
+
}}
|
|
716
|
+
class="relative flex cursor-pointer select-none items-center gap-2 rounded-xs px-2 py-1.5 text-xs outline-none transition-colors hover:bg-accent hover:text-accent-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
717
|
+
>
|
|
718
|
+
<CircleX class="size-3.5 text-muted-foreground" />
|
|
719
|
+
<span>Close All Tabs</span>
|
|
720
|
+
</ContextMenuPrimitive.Item>
|
|
721
|
+
</Show>
|
|
722
|
+
</>
|
|
723
|
+
}
|
|
724
|
+
>
|
|
725
|
+
{local.renderContextMenu!(local.tab || { id: tabId(), title: title() })}
|
|
726
|
+
</Show>
|
|
727
|
+
</ContextMenuPrimitive.Content>
|
|
728
|
+
</ContextMenuPrimitive.Portal>
|
|
729
|
+
</ContextMenuPrimitive.Root>
|
|
730
|
+
</Show>
|
|
731
|
+
);
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
/* --- 5. TitlebarTabAddButton using Nikala UI Button & Tooltip --- */
|
|
735
|
+
|
|
736
|
+
export interface TitlebarTabAddButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
|
|
737
|
+
class?: string;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export const TitlebarTabAddButton: Component<TitlebarTabAddButtonProps> = (props) => {
|
|
741
|
+
const [local, rest] = splitProps(props, ["class", "style"]);
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<div
|
|
745
|
+
data-no-drag
|
|
746
|
+
data-tauri-drag-region="false"
|
|
747
|
+
style={("-webkit-app-region: no-drag; app-region: no-drag;" + (typeof local.style === "string" ? ` ${local.style}` : "")) as any}
|
|
748
|
+
class="shrink-0 flex items-center z-20 pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]"
|
|
749
|
+
>
|
|
750
|
+
<Tooltip>
|
|
751
|
+
<TooltipTrigger
|
|
752
|
+
as={Button}
|
|
753
|
+
variant="ghost"
|
|
754
|
+
size="icon"
|
|
755
|
+
data-no-drag
|
|
756
|
+
data-tauri-drag-region="false"
|
|
757
|
+
aria-label="Add new tab"
|
|
758
|
+
class={cn(
|
|
759
|
+
"size-6 rounded-md text-muted-foreground hover:bg-muted/80 hover:text-foreground cursor-pointer shrink-0",
|
|
760
|
+
local.class
|
|
761
|
+
)}
|
|
762
|
+
{...rest}
|
|
763
|
+
>
|
|
764
|
+
<Plus class="size-3.5" />
|
|
765
|
+
</TooltipTrigger>
|
|
766
|
+
<TooltipContent class="text-[10px] py-1 px-2">
|
|
767
|
+
New tab (Ctrl+T)
|
|
768
|
+
</TooltipContent>
|
|
769
|
+
</Tooltip>
|
|
770
|
+
</div>
|
|
771
|
+
);
|
|
772
|
+
};
|
|
773
|
+
|