@inui/design-system 0.0.0-stage → 1.22.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/README.md +360 -2
- package/dist/components/ui/accordion.d.ts +44 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +86 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/ai-avatar.d.ts +46 -0
- package/dist/components/ui/ai-avatar.d.ts.map +1 -0
- package/dist/components/ui/ai-avatar.js +43 -0
- package/dist/components/ui/ai-avatar.js.map +1 -0
- package/dist/components/ui/ai-label.d.ts +118 -0
- package/dist/components/ui/ai-label.d.ts.map +1 -0
- package/dist/components/ui/ai-label.js +184 -0
- package/dist/components/ui/ai-label.js.map +1 -0
- package/dist/components/ui/ai-sparkle.d.ts +91 -0
- package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
- package/dist/components/ui/ai-sparkle.js +200 -0
- package/dist/components/ui/ai-sparkle.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +79 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +147 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/alert.d.ts +71 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +108 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/aspect-ratio.d.ts +30 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +40 -0
- package/dist/components/ui/aspect-ratio.js.map +1 -0
- package/dist/components/ui/attachment.d.ts +74 -0
- package/dist/components/ui/attachment.d.ts.map +1 -0
- package/dist/components/ui/attachment.js +142 -0
- package/dist/components/ui/attachment.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +82 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +123 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +130 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +226 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +43 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +98 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/browser-tabs.d.ts +218 -0
- package/dist/components/ui/browser-tabs.d.ts.map +1 -0
- package/dist/components/ui/browser-tabs.js +528 -0
- package/dist/components/ui/browser-tabs.js.map +1 -0
- package/dist/components/ui/bubble.d.ts +80 -0
- package/dist/components/ui/bubble.d.ts.map +1 -0
- package/dist/components/ui/bubble.js +235 -0
- package/dist/components/ui/bubble.js.map +1 -0
- package/dist/components/ui/button-group.d.ts +60 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +89 -0
- package/dist/components/ui/button-group.js.map +1 -0
- package/dist/components/ui/button.d.ts +157 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +144 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +57 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +163 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +96 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +131 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/carousel.d.ts +86 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +207 -0
- package/dist/components/ui/carousel.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +54 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +78 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +45 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +65 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +111 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +264 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +71 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +133 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +70 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +163 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/count-badge.d.ts +40 -0
- package/dist/components/ui/count-badge.d.ts.map +1 -0
- package/dist/components/ui/count-badge.js +43 -0
- package/dist/components/ui/count-badge.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +1026 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +3423 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +73 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +145 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/direction.d.ts +35 -0
- package/dist/components/ui/direction.d.ts.map +1 -0
- package/dist/components/ui/direction.js +35 -0
- package/dist/components/ui/direction.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +53 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +115 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +75 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +173 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/dropzone.d.ts +141 -0
- package/dist/components/ui/dropzone.d.ts.map +1 -0
- package/dist/components/ui/dropzone.js +257 -0
- package/dist/components/ui/dropzone.js.map +1 -0
- package/dist/components/ui/empty.d.ts +94 -0
- package/dist/components/ui/empty.d.ts.map +1 -0
- package/dist/components/ui/empty.js +149 -0
- package/dist/components/ui/empty.js.map +1 -0
- package/dist/components/ui/field.d.ts +122 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/components/ui/field.js +254 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +51 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +101 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +62 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +115 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-otp.d.ts +52 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +89 -0
- package/dist/components/ui/input-otp.js.map +1 -0
- package/dist/components/ui/input.d.ts +47 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +47 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/item.d.ts +74 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +176 -0
- package/dist/components/ui/item.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +41 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +58 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-value-pair.d.ts +56 -0
- package/dist/components/ui/key-value-pair.d.ts.map +1 -0
- package/dist/components/ui/key-value-pair.js +110 -0
- package/dist/components/ui/key-value-pair.js.map +1 -0
- package/dist/components/ui/label.d.ts +83 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +85 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/marker.d.ts +54 -0
- package/dist/components/ui/marker.d.ts.map +1 -0
- package/dist/components/ui/marker.js +79 -0
- package/dist/components/ui/marker.js.map +1 -0
- package/dist/components/ui/menubar.d.ts +72 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +174 -0
- package/dist/components/ui/menubar.js.map +1 -0
- package/dist/components/ui/message-scroller.d.ts +53 -0
- package/dist/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller.js +95 -0
- package/dist/components/ui/message-scroller.js.map +1 -0
- package/dist/components/ui/message.d.ts +63 -0
- package/dist/components/ui/message.d.ts.map +1 -0
- package/dist/components/ui/message.js +92 -0
- package/dist/components/ui/message.js.map +1 -0
- package/dist/components/ui/meta-list.d.ts +55 -0
- package/dist/components/ui/meta-list.d.ts.map +1 -0
- package/dist/components/ui/meta-list.js +75 -0
- package/dist/components/ui/meta-list.js.map +1 -0
- package/dist/components/ui/native-select.d.ts +51 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +75 -0
- package/dist/components/ui/native-select.js.map +1 -0
- package/dist/components/ui/navigation-menu.d.ts +56 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +116 -0
- package/dist/components/ui/navigation-menu.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +223 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +233 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +68 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +145 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +61 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +144 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +35 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +46 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/questionnaire.d.ts +92 -0
- package/dist/components/ui/questionnaire.d.ts.map +1 -0
- package/dist/components/ui/questionnaire.js +277 -0
- package/dist/components/ui/questionnaire.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +50 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +44 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +73 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +55 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +72 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +274 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +609 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +31 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +38 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +59 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +128 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +166 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +512 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +35 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +46 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +39 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +75 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +56 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +96 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +37 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +51 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +84 -0
- package/dist/components/ui/stepper.d.ts.map +1 -0
- package/dist/components/ui/stepper.js +145 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/switch.d.ts +36 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +44 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +80 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +130 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +87 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +128 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +35 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +38 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +58 -0
- package/dist/components/ui/time-picker.d.ts.map +1 -0
- package/dist/components/ui/time-picker.js +188 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +52 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +94 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +50 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +70 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +51 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +78 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/hooks/use-dark-mode.d.ts +2 -0
- package/dist/hooks/use-dark-mode.d.ts.map +1 -0
- package/dist/hooks/use-dark-mode.js +36 -0
- package/dist/hooks/use-dark-mode.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +20 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/lib/scope.d.ts +64 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +74 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/theme-tokens.css +537 -0
- package/dist/theme.css +417 -0
- package/package.json +103 -3
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { Popover as Popover$1, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
5
|
+
import { Input } from "./input.js";
|
|
6
|
+
import { ContextMenu as ContextMenu$1, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "./context-menu.js";
|
|
7
|
+
import { Tooltip as Tooltip$1, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
8
|
+
import { TabsContent } from "./tabs.js";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { Tabs } from "radix-ui";
|
|
11
|
+
import { CopyXIcon, FileIcon, PinIcon, PinOffIcon, PlusIcon, XIcon } from "lucide-react";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region src/components/ui/browser-tabs.tsx
|
|
14
|
+
/**
|
|
15
|
+
* BrowserTabs — persistent, closable document tabs, browser-style.
|
|
16
|
+
*
|
|
17
|
+
* The same Radix Tabs primitive `tabs.tsx` wraps, plus Popover for the "+"
|
|
18
|
+
* menu, Tooltip for truncated names, and Button for the per-tab close
|
|
19
|
+
* action — a different visual/interaction language from Tabs, not a
|
|
20
|
+
* variant of it: Tabs switches between named views of one entity (2-6 fixed
|
|
21
|
+
* tabs); this is for many independent, closable documents/views open at
|
|
22
|
+
* once (an IDE's editor tabs, a browser's own tab strip) — always
|
|
23
|
+
* horizontal, and always the topmost chrome of whatever surface it sits on.
|
|
24
|
+
* Each tab floats as a rounded pill on the strip's own bg-secondary track,
|
|
25
|
+
* flush against its bottom edge — the active tab's bg-card fill then
|
|
26
|
+
* touches the card content's own bg-card directly beneath it with no grey
|
|
27
|
+
* left in between, merging the two Chrome-style, rather than reading as a
|
|
28
|
+
* colored/selected state the way Tabs' own default variant does. Pinned
|
|
29
|
+
* tabs render icon-only — a fixed favicon-size square, falling back to a
|
|
30
|
+
* generic FileIcon when the tab itself has none — same as a real browser's
|
|
31
|
+
* own pinned tabs, and never part of the overflow behavior below. Once
|
|
32
|
+
* unpinned tabs stop fitting, they shrink together down to a floor, then
|
|
33
|
+
* the strip scrolls that pool horizontally past that — the same overflow
|
|
34
|
+
* model a real browser's own tab strip uses, not a "More" menu hiding the
|
|
35
|
+
* rest.
|
|
36
|
+
* An optional "+" pill at the strip's end, outside the scrolling region
|
|
37
|
+
* (`addViews` on BrowserTabsList) opens a popover of product-defined views
|
|
38
|
+
* the user can open as new tabs — a worklist's saved filters, for
|
|
39
|
+
* instance. `addTrigger`/`renderAddContent` swap its trigger or its
|
|
40
|
+
* popover content for something fully custom.
|
|
41
|
+
*/
|
|
42
|
+
var BrowserTabsActiveContext = React.createContext(void 0);
|
|
43
|
+
function BrowserTabs({ className, value, defaultValue, onValueChange, ...props }) {
|
|
44
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
45
|
+
const activeValue = value ?? uncontrolledValue;
|
|
46
|
+
return /* @__PURE__ */ jsx(BrowserTabsActiveContext.Provider, {
|
|
47
|
+
value: activeValue,
|
|
48
|
+
children: /* @__PURE__ */ jsx(Tabs.Root, {
|
|
49
|
+
"data-slot": "browser-tabs",
|
|
50
|
+
className: cn("flex flex-col", className),
|
|
51
|
+
value,
|
|
52
|
+
defaultValue,
|
|
53
|
+
onValueChange: (next) => {
|
|
54
|
+
setUncontrolledValue(next);
|
|
55
|
+
onValueChange?.(next);
|
|
56
|
+
},
|
|
57
|
+
activationMode: "manual",
|
|
58
|
+
...props
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
var BROWSER_TAB_PILL_CLASS = cn("group/browser-tab relative flex h-7 max-w-52 cursor-pointer items-center gap-1.5 rounded-md border border-transparent px-2.5 text-left text-sm font-medium text-muted-foreground transition-colors outline-none select-none", "hover:bg-muted hover:text-foreground dark:hover:text-foreground", "focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset");
|
|
63
|
+
var ADD_VIEWS_SEARCH_THRESHOLD = 6;
|
|
64
|
+
/** Children as a flat list of elements, looking through Fragments so tabs grouped in `<>…</>` still split into pinned/unpinned. */
|
|
65
|
+
function flattenTabChildren(children) {
|
|
66
|
+
return React.Children.toArray(children).flatMap((child) => {
|
|
67
|
+
if (!React.isValidElement(child)) return [];
|
|
68
|
+
if (child.type === React.Fragment) return flattenTabChildren(child.props.children).map((inner) => React.cloneElement(inner, { key: `${child.key}${inner.key}` }));
|
|
69
|
+
return [child];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function BrowserTabsList({ className, children, addViews, onAddView, addLabel = "Open a view in a new tab", addTrigger, renderAddContent, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
|
|
73
|
+
const hasAdd = Boolean(addViews && addViews.length > 0 || renderAddContent);
|
|
74
|
+
const items = React.useMemo(() => flattenTabChildren(children), [children]);
|
|
75
|
+
const pinnedItems = React.useMemo(() => items.filter((item) => item.props.pin), [items]);
|
|
76
|
+
const restItems = React.useMemo(() => items.filter((item) => !item.props.pin), [items]);
|
|
77
|
+
const hasDivider = pinnedItems.length > 0 && restItems.length > 0;
|
|
78
|
+
const restScrollRef = React.useRef(null);
|
|
79
|
+
const activeValue = React.useContext(BrowserTabsActiveContext);
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
const container = restScrollRef.current;
|
|
82
|
+
if (!container || activeValue === void 0) return;
|
|
83
|
+
container.querySelector(`[data-slot="browser-tab"][data-value="${CSS.escape(activeValue)}"]`)?.scrollIntoView({
|
|
84
|
+
block: "nearest",
|
|
85
|
+
inline: "nearest"
|
|
86
|
+
});
|
|
87
|
+
}, [activeValue]);
|
|
88
|
+
const [canScrollLeft, setCanScrollLeft] = React.useState(false);
|
|
89
|
+
const [canScrollRight, setCanScrollRight] = React.useState(false);
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
const container = restScrollRef.current;
|
|
92
|
+
if (!container) return;
|
|
93
|
+
function measure() {
|
|
94
|
+
if (!container) return;
|
|
95
|
+
setCanScrollLeft(container.scrollLeft > 0);
|
|
96
|
+
setCanScrollRight(container.scrollLeft + container.clientWidth < container.scrollWidth);
|
|
97
|
+
}
|
|
98
|
+
measure();
|
|
99
|
+
container.addEventListener("scroll", measure);
|
|
100
|
+
const observer = new ResizeObserver(measure);
|
|
101
|
+
observer.observe(container);
|
|
102
|
+
return () => {
|
|
103
|
+
container.removeEventListener("scroll", measure);
|
|
104
|
+
observer.disconnect();
|
|
105
|
+
};
|
|
106
|
+
}, [restItems.length]);
|
|
107
|
+
const hasOverflow = canScrollLeft || canScrollRight;
|
|
108
|
+
return /* @__PURE__ */ jsxs(Tabs.List, {
|
|
109
|
+
"data-slot": "browser-tabs-list",
|
|
110
|
+
role: "presentation",
|
|
111
|
+
"aria-orientation": void 0,
|
|
112
|
+
className: cn("relative flex items-start gap-1 bg-secondary px-(--card-spacing,0.5rem) pt-[calc(var(--card-spacing,0.5rem)*0.5)]", className),
|
|
113
|
+
...props,
|
|
114
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
115
|
+
role: "tablist",
|
|
116
|
+
"aria-orientation": "horizontal",
|
|
117
|
+
"aria-label": ariaLabel,
|
|
118
|
+
"aria-labelledby": ariaLabelledBy,
|
|
119
|
+
"data-overflowing": hasOverflow || void 0,
|
|
120
|
+
className: "group/tablist flex min-h-8 min-w-0 items-start gap-1",
|
|
121
|
+
children: [
|
|
122
|
+
pinnedItems,
|
|
123
|
+
hasDivider && /* @__PURE__ */ jsx("div", {
|
|
124
|
+
"aria-hidden": "true",
|
|
125
|
+
className: "h-7 w-px shrink-0 bg-border"
|
|
126
|
+
}),
|
|
127
|
+
/* @__PURE__ */ jsx("div", {
|
|
128
|
+
ref: restScrollRef,
|
|
129
|
+
role: "presentation",
|
|
130
|
+
className: cn("-mt-[calc(var(--card-spacing,0.5rem)*0.5)] flex min-w-0 [scrollbar-width:thin] items-start gap-1 overflow-x-auto pt-[calc(var(--card-spacing,0.5rem)*0.5)] focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none focus-visible:ring-inset", pinnedItems.length === 0 && "-ml-(--card-spacing,0.5rem) pl-(--card-spacing,0.5rem)", !hasAdd && "-mr-(--card-spacing,0.5rem) pr-(--card-spacing,0.5rem)", pinnedItems.length > 0 && "-ml-2 pl-2", hasAdd && "-mr-2 pr-2"),
|
|
131
|
+
style: {
|
|
132
|
+
boxShadow: [canScrollLeft && "inset 8px 0 8px -8px color-mix(in srgb, var(--color-foreground) 20%, transparent)", canScrollRight && "inset -8px 0 8px -8px color-mix(in srgb, var(--color-foreground) 20%, transparent)"].filter(Boolean).join(", "),
|
|
133
|
+
...hasOverflow && restItems.length > 0 ? {
|
|
134
|
+
flexGrow: restItems.length,
|
|
135
|
+
flexShrink: 1,
|
|
136
|
+
flexBasis: 0
|
|
137
|
+
} : void 0
|
|
138
|
+
},
|
|
139
|
+
children: restItems
|
|
140
|
+
})
|
|
141
|
+
]
|
|
142
|
+
}), hasAdd && /* @__PURE__ */ jsx(BrowserTabsAdd, {
|
|
143
|
+
views: addViews ?? [],
|
|
144
|
+
onAddView,
|
|
145
|
+
label: addLabel,
|
|
146
|
+
trigger: addTrigger,
|
|
147
|
+
renderContent: renderAddContent,
|
|
148
|
+
className: hasOverflow ? "ml-auto" : void 0
|
|
149
|
+
})]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function BrowserTabsAdd({ views, onAddView, label, trigger, renderContent, className }) {
|
|
153
|
+
const [open, setOpen] = React.useState(false);
|
|
154
|
+
const [query, setQuery] = React.useState("");
|
|
155
|
+
const close = React.useCallback(() => {
|
|
156
|
+
setOpen(false);
|
|
157
|
+
setQuery("");
|
|
158
|
+
}, []);
|
|
159
|
+
const showSearch = views.length > ADD_VIEWS_SEARCH_THRESHOLD;
|
|
160
|
+
const visibleViews = React.useMemo(() => {
|
|
161
|
+
if (!showSearch || !query.trim()) return views;
|
|
162
|
+
const q = query.trim().toLowerCase();
|
|
163
|
+
return views.filter((view) => view.label.toLowerCase().includes(q) || view.description?.toLowerCase().includes(q));
|
|
164
|
+
}, [
|
|
165
|
+
views,
|
|
166
|
+
showSearch,
|
|
167
|
+
query
|
|
168
|
+
]);
|
|
169
|
+
return /* @__PURE__ */ jsx("div", {
|
|
170
|
+
className: cn("shrink-0", className),
|
|
171
|
+
children: /* @__PURE__ */ jsxs(Popover$1, {
|
|
172
|
+
open,
|
|
173
|
+
onOpenChange: (next) => {
|
|
174
|
+
setOpen(next);
|
|
175
|
+
if (!next) setQuery("");
|
|
176
|
+
},
|
|
177
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
178
|
+
asChild: true,
|
|
179
|
+
children: trigger ?? /* @__PURE__ */ jsx("button", {
|
|
180
|
+
type: "button",
|
|
181
|
+
"data-slot": "browser-tabs-add",
|
|
182
|
+
"aria-label": label,
|
|
183
|
+
className: cn("relative flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent text-muted-foreground transition-colors outline-none select-none", "hover:bg-muted hover:text-foreground dark:hover:text-foreground", "focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset", open && "bg-muted text-foreground"),
|
|
184
|
+
children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-4 shrink-0" })
|
|
185
|
+
})
|
|
186
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
187
|
+
align: "start",
|
|
188
|
+
className: renderContent ? void 0 : "w-64 p-1.5",
|
|
189
|
+
children: renderContent ? renderContent(close) : /* @__PURE__ */ jsxs("div", {
|
|
190
|
+
className: "flex flex-col gap-1.5",
|
|
191
|
+
children: [showSearch && /* @__PURE__ */ jsx(Input, {
|
|
192
|
+
autoFocus: true,
|
|
193
|
+
placeholder: "Search...",
|
|
194
|
+
value: query,
|
|
195
|
+
onChange: (event) => setQuery(event.target.value)
|
|
196
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
197
|
+
className: "flex flex-col gap-0.5",
|
|
198
|
+
role: "menu",
|
|
199
|
+
children: [visibleViews.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
200
|
+
className: "p-1.5 text-sm text-muted-foreground",
|
|
201
|
+
children: "No matches"
|
|
202
|
+
}), visibleViews.map((view) => {
|
|
203
|
+
const Icon = view.icon;
|
|
204
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
205
|
+
type: "button",
|
|
206
|
+
role: "menuitem",
|
|
207
|
+
disabled: view.disabled,
|
|
208
|
+
onClick: () => {
|
|
209
|
+
onAddView?.(view);
|
|
210
|
+
close();
|
|
211
|
+
},
|
|
212
|
+
className: cn(BROWSER_TAB_PILL_CLASS, "w-full max-w-none justify-start", view.description && "h-auto py-1.5", "disabled:pointer-events-none disabled:opacity-50"),
|
|
213
|
+
children: [
|
|
214
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "size-3.5 shrink-0" }),
|
|
215
|
+
/* @__PURE__ */ jsxs("span", {
|
|
216
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
217
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
218
|
+
className: "truncate",
|
|
219
|
+
children: view.label
|
|
220
|
+
}), view.description && /* @__PURE__ */ jsx("span", {
|
|
221
|
+
className: "truncate text-xs text-muted-foreground",
|
|
222
|
+
children: view.description
|
|
223
|
+
})]
|
|
224
|
+
}),
|
|
225
|
+
view.pin && /* @__PURE__ */ jsx(PinIcon, { className: "size-3.5 shrink-0 text-muted-foreground" })
|
|
226
|
+
]
|
|
227
|
+
}, view.id);
|
|
228
|
+
})]
|
|
229
|
+
})]
|
|
230
|
+
})
|
|
231
|
+
})]
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** The truncating label — wraps itself in a Tooltip showing the full name, but only once its content actually overflows (`scrollWidth > clientWidth`), and only for the real interactive tab (never the invisible measuring copy). */
|
|
236
|
+
function BrowserTabLabel({ name, interactive, hoverReveal, children }) {
|
|
237
|
+
const labelRef = React.useRef(null);
|
|
238
|
+
const [truncated, setTruncated] = React.useState(false);
|
|
239
|
+
const [tooltipOpen, setTooltipOpen] = React.useState(false);
|
|
240
|
+
React.useLayoutEffect(() => {
|
|
241
|
+
if (!interactive) return;
|
|
242
|
+
const el = labelRef.current;
|
|
243
|
+
if (!el) return;
|
|
244
|
+
const check = () => {
|
|
245
|
+
if (el.clientWidth === 0) {
|
|
246
|
+
rafId = requestAnimationFrame(check);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
setTruncated(el.scrollWidth > el.clientWidth);
|
|
250
|
+
};
|
|
251
|
+
let rafId = requestAnimationFrame(check);
|
|
252
|
+
const observer = new ResizeObserver(check);
|
|
253
|
+
observer.observe(el);
|
|
254
|
+
return () => {
|
|
255
|
+
cancelAnimationFrame(rafId);
|
|
256
|
+
observer.disconnect();
|
|
257
|
+
};
|
|
258
|
+
}, [interactive, children]);
|
|
259
|
+
const label = /* @__PURE__ */ jsxs("span", {
|
|
260
|
+
className: "relative min-w-0 flex-1",
|
|
261
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
262
|
+
"aria-hidden": "true",
|
|
263
|
+
className: "invisible block truncate",
|
|
264
|
+
children
|
|
265
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
266
|
+
ref: labelRef,
|
|
267
|
+
className: cn("absolute inset-0 truncate", hoverReveal && "transition-[padding] group-focus-within/browser-tab:pr-5 group-hover/browser-tab:pr-5"),
|
|
268
|
+
children
|
|
269
|
+
})]
|
|
270
|
+
});
|
|
271
|
+
if (!name) return label;
|
|
272
|
+
return /* @__PURE__ */ jsxs(Tooltip$1, {
|
|
273
|
+
open: truncated && tooltipOpen,
|
|
274
|
+
onOpenChange: setTooltipOpen,
|
|
275
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
276
|
+
asChild: true,
|
|
277
|
+
children: label
|
|
278
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children: name })]
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** A pinned tab's entire visible content — icon only, no label, favicon-style
|
|
282
|
+
* like a real browser's own pinned tabs. Falls back to `FileIcon` — this
|
|
283
|
+
* design system's existing entity-agnostic "generic item" icon (Dropzone's
|
|
284
|
+
* own fallback for a file type it doesn't otherwise recognize, as opposed to
|
|
285
|
+
* `FileTextIcon`, which Dropzone reserves specifically for documents) — when
|
|
286
|
+
* the tab has no `icon`, so a pinned tab is never a blank square. A tab can
|
|
287
|
+
* represent any kind of view, not just documents, so the fallback shouldn't
|
|
288
|
+
* imply one. `PinIcon` itself would be the wrong fallback here: this same
|
|
289
|
+
* file already uses it for the "pin" *action* (the context menu's "Pin tab"
|
|
290
|
+
* item, and the `addViews` pin hint), so reusing it as a tab's identity icon
|
|
291
|
+
* would read as "pin this" rather than "generic tab." The name still
|
|
292
|
+
* surfaces on hover/focus via Tooltip (there's no visible text left to
|
|
293
|
+
* convey it), and unconditionally rather than only-when-truncated the way
|
|
294
|
+
* BrowserTabLabel's does — icon-only content is "truncated" by definition. */
|
|
295
|
+
function BrowserPinnedTabIcon({ icon: Icon }) {
|
|
296
|
+
return /* @__PURE__ */ jsx(Icon ?? FileIcon, { className: "size-3.5 shrink-0" });
|
|
297
|
+
}
|
|
298
|
+
var BrowserTabShell = React.forwardRef(function BrowserTabShell({ value, icon: Icon, pin, disabled, onClose, closeLabel, onPinToggle, onCloseOthers, className, children, interactive, ...rest }, ref) {
|
|
299
|
+
const activeValue = React.useContext(BrowserTabsActiveContext);
|
|
300
|
+
const isActive = interactive && value === activeValue;
|
|
301
|
+
const isPinned = Boolean(pin);
|
|
302
|
+
const canClose = Boolean(onClose);
|
|
303
|
+
const showHoverClose = canClose && !isPinned;
|
|
304
|
+
const canTogglePin = Boolean(onPinToggle) && pin !== "product";
|
|
305
|
+
const canCloseOthers = Boolean(onCloseOthers);
|
|
306
|
+
const hasMenu = interactive;
|
|
307
|
+
const name = typeof children === "string" ? children : void 0;
|
|
308
|
+
const pill = /* @__PURE__ */ jsxs("div", {
|
|
309
|
+
ref,
|
|
310
|
+
...rest,
|
|
311
|
+
"data-slot": "browser-tab",
|
|
312
|
+
"data-value": value,
|
|
313
|
+
"aria-disabled": disabled || void 0,
|
|
314
|
+
onKeyDown: (event) => {
|
|
315
|
+
rest.onKeyDown?.(event);
|
|
316
|
+
if (event.defaultPrevented || !interactive || !canClose) return;
|
|
317
|
+
if (event.key === "Delete" || event.key === "Backspace") {
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
onClose?.();
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"aria-label": isPinned ? name ?? rest["aria-label"] : rest["aria-label"],
|
|
323
|
+
className: cn(BROWSER_TAB_PILL_CLASS, isPinned ? "size-7 shrink-0 justify-center px-0" : ["min-w-16", "group-data-overflowing/tablist:flex-1"], "data-disabled:pointer-events-none data-disabled:opacity-50", isActive && "h-8 rounded-b-none bg-card pb-1 text-card-foreground hover:bg-card hover:text-card-foreground", isActive && "before:absolute before:-bottom-px before:left-[calc(-0.5rem-1px)] before:size-2 before:bg-[radial-gradient(circle_at_0_0,transparent_0.5rem,var(--color-card)_calc(0.5rem+0.5px))] before:content-[''] after:absolute after:right-[calc(-0.5rem-1px)] after:-bottom-px after:size-2 after:bg-[radial-gradient(circle_at_100%_0,transparent_0.5rem,var(--color-card)_calc(0.5rem+0.5px))] after:content-['']", className),
|
|
324
|
+
children: [isPinned ? /* @__PURE__ */ jsx(BrowserPinnedTabIcon, { icon: Icon }) : /* @__PURE__ */ jsxs(Fragment, { children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ jsx(BrowserTabLabel, {
|
|
325
|
+
name,
|
|
326
|
+
interactive,
|
|
327
|
+
hoverReveal: showHoverClose,
|
|
328
|
+
children
|
|
329
|
+
})] }), showHoverClose && /* @__PURE__ */ jsx(Button, {
|
|
330
|
+
asChild: true,
|
|
331
|
+
variant: "secondary",
|
|
332
|
+
size: "xs",
|
|
333
|
+
iconOnly: true,
|
|
334
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
335
|
+
"aria-hidden": "true",
|
|
336
|
+
title: closeLabel ?? `Close ${name ?? value}`,
|
|
337
|
+
className: "absolute inset-y-0 right-1 my-auto size-5 cursor-pointer opacity-0 transition-opacity group-hover/browser-tab:opacity-100",
|
|
338
|
+
onClick: interactive ? (event) => {
|
|
339
|
+
event.stopPropagation();
|
|
340
|
+
onClose?.();
|
|
341
|
+
} : void 0,
|
|
342
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-3" })
|
|
343
|
+
})
|
|
344
|
+
})]
|
|
345
|
+
});
|
|
346
|
+
const pinnedTooltip = isPinned && name;
|
|
347
|
+
const trigger = pinnedTooltip ? /* @__PURE__ */ jsx(TooltipTrigger, {
|
|
348
|
+
asChild: true,
|
|
349
|
+
children: pill
|
|
350
|
+
}) : pill;
|
|
351
|
+
const withTooltip = (node) => pinnedTooltip ? /* @__PURE__ */ jsxs(Tooltip$1, { children: [node, /* @__PURE__ */ jsx(TooltipContent, { children: name })] }) : node;
|
|
352
|
+
if (!hasMenu) return withTooltip(trigger);
|
|
353
|
+
return withTooltip(/* @__PURE__ */ jsxs(ContextMenu$1, { children: [/* @__PURE__ */ jsx(ContextMenuTrigger, {
|
|
354
|
+
asChild: true,
|
|
355
|
+
children: trigger
|
|
356
|
+
}), /* @__PURE__ */ jsxs(ContextMenuContent, { children: [
|
|
357
|
+
/* @__PURE__ */ jsxs(ContextMenuItem, {
|
|
358
|
+
disabled: !canTogglePin,
|
|
359
|
+
onSelect: () => onPinToggle?.(),
|
|
360
|
+
children: [pin ? /* @__PURE__ */ jsx(PinOffIcon, {}) : /* @__PURE__ */ jsx(PinIcon, {}), pin ? "Unpin tab" : "Pin tab"]
|
|
361
|
+
}),
|
|
362
|
+
/* @__PURE__ */ jsx(ContextMenuSeparator, {}),
|
|
363
|
+
/* @__PURE__ */ jsxs(ContextMenuItem, {
|
|
364
|
+
disabled: !canClose,
|
|
365
|
+
onSelect: () => onClose?.(),
|
|
366
|
+
children: [/* @__PURE__ */ jsx(XIcon, {}), "Close"]
|
|
367
|
+
}),
|
|
368
|
+
/* @__PURE__ */ jsxs(ContextMenuItem, {
|
|
369
|
+
disabled: !canCloseOthers,
|
|
370
|
+
onSelect: () => onCloseOthers?.(),
|
|
371
|
+
children: [/* @__PURE__ */ jsx(CopyXIcon, {}), "Close other tabs"]
|
|
372
|
+
})
|
|
373
|
+
] })] }));
|
|
374
|
+
});
|
|
375
|
+
function BrowserTab(props) {
|
|
376
|
+
const { value, disabled } = props;
|
|
377
|
+
const canClose = Boolean(props.onClose) && props.pin !== "product";
|
|
378
|
+
return /* @__PURE__ */ jsx(Tabs.Trigger, {
|
|
379
|
+
value,
|
|
380
|
+
disabled,
|
|
381
|
+
asChild: true,
|
|
382
|
+
children: /* @__PURE__ */ jsx(BrowserTabShell, {
|
|
383
|
+
...props,
|
|
384
|
+
onClose: canClose ? props.onClose : void 0,
|
|
385
|
+
interactive: true
|
|
386
|
+
})
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* WHEN TO USE
|
|
391
|
+
* - Many independent, addable/closable documents or views open at once —
|
|
392
|
+
* an IDE's editor tabs, a browser's own tab strip, a multi-record
|
|
393
|
+
* workspace where each record opens in its own tab.
|
|
394
|
+
* - Always as the topmost chrome of whatever surface hosts it (a Card,
|
|
395
|
+
* a page shell) — it's edge-to-edge and expects to sit flush at that
|
|
396
|
+
* surface's top.
|
|
397
|
+
*
|
|
398
|
+
* NOT WHEN
|
|
399
|
+
* - Switching between a fixed 2-6 named views of *one* entity (an
|
|
400
|
+
* Overview/History/Notes split) — use Tabs instead; it reads as
|
|
401
|
+
* neutral navigation, not as separate closable documents.
|
|
402
|
+
* - Vertical tab placement — not supported; always horizontal.
|
|
403
|
+
*
|
|
404
|
+
* KEY RULES
|
|
405
|
+
* - Composition mirrors Tabs: `BrowserTabs` (Root, controlled or
|
|
406
|
+
* uncontrolled via `value`/`onValueChange`/`defaultValue`) wraps
|
|
407
|
+
* `BrowserTabsList` (the strip) and one `BrowserTabsContent` per tab
|
|
408
|
+
* (re-exported directly from Tabs — same primitive, nothing about it
|
|
409
|
+
* is strip-specific).
|
|
410
|
+
* - `pin`: omit for a normal tab, `"user"` for one the user pinned
|
|
411
|
+
* (closable — closing is how they unpin it), `"product"` for one the
|
|
412
|
+
* product configured (never closable, no close button rendered even if
|
|
413
|
+
* `onClose` is passed). Pinned tabs of either kind render first, in a
|
|
414
|
+
* section separated from the rest by a divider, and are never subject
|
|
415
|
+
* to the overflow behavior below.
|
|
416
|
+
* - Pinned tabs render icon-only — a fixed favicon-size square, same as a
|
|
417
|
+
* real browser's own pinned tabs — falling back to `FileIcon`
|
|
418
|
+
* when the tab itself has no `icon`, so a pinned tab is never blank.
|
|
419
|
+
* The full name still surfaces via Tooltip on hover/focus; there's no
|
|
420
|
+
* hover-revealed close button at that width, so unpinning/closing goes
|
|
421
|
+
* through the right-click menu or Delete/Backspace on the focused tab.
|
|
422
|
+
* - Every tab has the same three-item right-click menu (ContextMenu):
|
|
423
|
+
* "Pin tab"/"Unpin tab", "Close", and "Close other tabs" — always
|
|
424
|
+
* visible, each disabled when the tab wasn't given the matching
|
|
425
|
+
* handler. `onPinToggle` drives the first (read the tab's current `pin`
|
|
426
|
+
* to know the direction, then move it into — or, unpinning, to the
|
|
427
|
+
* front of — the right section in your own list; always disabled on
|
|
428
|
+
* `pin="product"`), `onClose` the second, `onCloseOthers` the third
|
|
429
|
+
* (close every other UNPINNED tab yourself and keep this one active —
|
|
430
|
+
* "close others" never closes a pinned tab of either kind; pinning is
|
|
431
|
+
* exactly the promise that a tab survives bulk closing).
|
|
432
|
+
* No visible pin button on the tab; pinning is menu-only, and pinned
|
|
433
|
+
* tabs are identified by sitting in the pinned section ahead of the
|
|
434
|
+
* divider.
|
|
435
|
+
* - Each `BrowserTab` needs its own `onClose` to get a close button at
|
|
436
|
+
* all — omit it for a tab that can't be dismissed. A tab is exactly as
|
|
437
|
+
* wide as its name at rest (nothing reserved on the right); on hover
|
|
438
|
+
* the close button appears over the end of the name, which truncates
|
|
439
|
+
* underneath it, so the tab's width never changes between rest and
|
|
440
|
+
* hover. That button is pointer-only (a focusable control can't sit
|
|
441
|
+
* inside a `role="tab"`): keyboard users press Delete or Backspace on
|
|
442
|
+
* the focused tab, or use the right-click menu's "Close".
|
|
443
|
+
* - Closing a tab is entirely the consumer's responsibility: `onClose`
|
|
444
|
+
* only reports the intent. Removing it from whatever list backs your
|
|
445
|
+
* tabs, and deciding which tab becomes active next, is app logic this
|
|
446
|
+
* component has no way to know on its own.
|
|
447
|
+
* - Overflow is browser-style, not a "More" menu: once unpinned tabs stop
|
|
448
|
+
* fitting the available width, they shrink together (flex-shrink) down
|
|
449
|
+
* to `min-w-16` (64px) each; past that floor the strip scrolls that
|
|
450
|
+
* pool horizontally instead of shrinking further. Pinned tabs are
|
|
451
|
+
* already fixed-size icon-only squares (see above), so they never
|
|
452
|
+
* shrink, grow, or join the scrolling region. The active tab scrolls
|
|
453
|
+
* into view
|
|
454
|
+
* automatically whenever it changes, so switching tabs never leaves the
|
|
455
|
+
* newly-active one scrolled out of sight. Only the "+" pill is truly
|
|
456
|
+
* fixed-size, living outside the shrink pool entirely. A soft inset
|
|
457
|
+
* shadow fades in on
|
|
458
|
+
* whichever edge(s) still have more tabs scrolled past them (both at
|
|
459
|
+
* once, mid-strip) and disappears once there's nothing left that way —
|
|
460
|
+
* a direction-aware cue, not a static "this can scroll" marker.
|
|
461
|
+
* - A tab's name overflowing `max-w-52` is truncated in the strip but
|
|
462
|
+
* shown in full in a Tooltip on hover/focus.
|
|
463
|
+
* - `addViews` on `BrowserTabsList` adds a "+" pill that opens a popover of
|
|
464
|
+
* product-defined views the user can open as new tabs — a worklist's
|
|
465
|
+
* saved filters, a set of report layouts — `{ id, label, icon?,
|
|
466
|
+
* description?, disabled?, pin? }` each. Sits immediately after the
|
|
467
|
+
* last tab at rest; pins to the strip's own right edge once the tabs
|
|
468
|
+
* overflow into their shrink/scroll region instead, same as a real
|
|
469
|
+
* browser's "+" button. Each is the same full-width tab pill (icon +
|
|
470
|
+
* label) it will become as a tab; `description` grows that row to a
|
|
471
|
+
* second, muted line underneath (a filter's own scope, a last-modified
|
|
472
|
+
* date). Past `ADD_VIEWS_SEARCH_THRESHOLD` (6) views the popover grows
|
|
473
|
+
* a search box filtering by label and description. A `pin` hint renders
|
|
474
|
+
* a small pin icon trailing the label (purely visual — opening it
|
|
475
|
+
* pinned is still your own `onAddView` handler's job). `onAddView` only
|
|
476
|
+
* reports the pick: add a tab for that view to your own list and make
|
|
477
|
+
* it active — or, if that view is already open, just activate the
|
|
478
|
+
* existing tab instead of opening a duplicate. Icon-only by default;
|
|
479
|
+
* `addLabel` names it for assistive tech (default "Open a view in a new
|
|
480
|
+
* tab"). `addTrigger` swaps in a custom trigger node in place of the
|
|
481
|
+
* default "+" pill (still needs its own `aria-label`). `renderAddContent`
|
|
482
|
+
* replaces the *entire* popover body — search box, view list, and all —
|
|
483
|
+
* with something fully custom (a grouped menu, anything) — called with
|
|
484
|
+
* a `close` function to dismiss the popover
|
|
485
|
+
* from inside it; `addViews`/`onAddView` don't render alongside it.
|
|
486
|
+
* - Each `BrowserTab` floats as a rounded pill inset from the strip's
|
|
487
|
+
* `bg-secondary` track. Unlike Tabs' own default variant (a translucent
|
|
488
|
+
* `bg-primary/10` fill), the active tab here reads as merged into the
|
|
489
|
+
* card below it, Chrome-style: `bg-card`/`text-card-foreground` (matching
|
|
490
|
+
* the content surface exactly) plus squared-off bottom corners, sitting
|
|
491
|
+
* flush against the track's own bottom edge with nothing grey left
|
|
492
|
+
* between tab and content. Inactive tabs just fill `bg-muted` on hover
|
|
493
|
+
* and otherwise float clear of the track's edges.
|
|
494
|
+
* - A leading icon uses `icon`, not the `data-icon="inline-start"` slot
|
|
495
|
+
* convention Button/Tabs use — it needs to sit outside the label's own
|
|
496
|
+
* truncation, which a plain child node sharing that flex row wouldn't.
|
|
497
|
+
*
|
|
498
|
+
* USAGE
|
|
499
|
+
* <BrowserTabs value={activeId} onValueChange={setActiveId}>
|
|
500
|
+
* <BrowserTabsList
|
|
501
|
+
* addViews={worklistViews} // [{ id: "overdue", label: "Overdue", icon: ClockIcon }, ...]
|
|
502
|
+
* onAddView={(view) => openViewTab(view)} // add to openDocs (or activate if already open)
|
|
503
|
+
* >
|
|
504
|
+
* <BrowserTab value="home" pin="product">Home</BrowserTab>
|
|
505
|
+
* {openDocs.map((doc) => (
|
|
506
|
+
* <BrowserTab
|
|
507
|
+
* key={doc.id}
|
|
508
|
+
* value={doc.id}
|
|
509
|
+
* icon={doc.icon}
|
|
510
|
+
* pin={doc.pinnedByUser ? "user" : undefined}
|
|
511
|
+
* onClose={() => closeDoc(doc.id)}
|
|
512
|
+
* >
|
|
513
|
+
* {doc.title}
|
|
514
|
+
* </BrowserTab>
|
|
515
|
+
* ))}
|
|
516
|
+
* </BrowserTabsList>
|
|
517
|
+
* <BrowserTabsContent value="home">...</BrowserTabsContent>
|
|
518
|
+
* {openDocs.map((doc) => (
|
|
519
|
+
* <BrowserTabsContent key={doc.id} value={doc.id}>
|
|
520
|
+
* <DocView doc={doc} />
|
|
521
|
+
* </BrowserTabsContent>
|
|
522
|
+
* ))}
|
|
523
|
+
* </BrowserTabs>
|
|
524
|
+
*/
|
|
525
|
+
//#endregion
|
|
526
|
+
export { BrowserTab, BrowserTabs, TabsContent as BrowserTabsContent, BrowserTabsList };
|
|
527
|
+
|
|
528
|
+
//# sourceMappingURL=browser-tabs.js.map
|