Previews

No matching results.

x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<div class="max-w-2xl space-y-3" id="session-tabs-events-demo">
<div id="ui-session-tabs-49921390" class="flex w-full min-w-0 items-end gap-1 shadow-[inset_0_-1px_0_var(--color-border)]" data-controller="ui--session-tabs" data-ui--session-tabs-min-tabs-value="1" data-ui--session-tabs-return-focus-value="" data-ui--session-tabs-rename-param-value="name" data-ui--session-tabs-rename-label-value="Tab name">
<div role="tablist" aria-label="Open tabs" aria-orientation="horizontal" class="flex min-w-0 items-end gap-1 overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" data-ui--session-tabs-target="list">
<div role="presentation" class="group/tab relative flex shrink-0 items-center rounded-t-md border border-b-0 border-transparent bg-muted/60 text-muted-foreground transition-colors hover:border-primary/20 hover:bg-primary/5 hover:text-foreground data-[state=active]:border-border data-[state=active]:bg-background data-[state=active]:text-foreground data-[dragging]:z-10 data-[dragging]:border-border data-[dragging]:bg-background data-[dragging]:shadow-md data-[dragging]:transition-none h-9" data-ui--session-tabs-target="tab" data-value="pinned" data-state="active" data-rename-enabled="true" data-move-enabled="true" data-close-enabled="true">
<span class="flex h-full shrink-0 cursor-grab touch-none select-none items-center justify-center overflow-hidden rounded-tl-md text-muted-foreground hover:text-primary active:cursor-grabbing w-5 pl-1" aria-hidden="true" data-ui--session-tabs-target="handle" data-action="pointerdown->ui--session-tabs#dragStart"><i data-lucide="grip-vertical" class="inline-block shrink-0 w-3 h-3 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none opacity-0 group-hover/tab:opacity-100 group-focus-within/tab:opacity-100 group-data-[dragging]/tab:opacity-100 group-has-[[aria-expanded=true]]/tab:opacity-100 [@media(hover:none)]:opacity-100 -translate-x-2 group-hover/tab:translate-x-0 group-focus-within/tab:translate-x-0 group-data-[dragging]/tab:translate-x-0 group-has-[[aria-expanded=true]]/tab:translate-x-0 [@media(hover:none)]:translate-x-0" aria-hidden="true"></i>
</span>
<button id="ui-session-tabs-49921390-tab-pinned" role="tab" class="flex h-full min-w-0 items-center gap-2 rounded-t-md text-sm font-medium outline-none select-none [-webkit-touch-callout:none] focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset pr-1 pl-0.5" aria-selected="true" tabindex="0" draggable="false" data-ui--session-tabs-target="trigger" data-action="click->ui--session-tabs#select keydown->ui--session-tabs#keydown contextmenu->ui--session-tabs#contextMenu pointerdown->ui--session-tabs#pressStart" type="button">
<span id="ui-session-tabs-49921390-tab-pinned-label" class="truncate max-w-[20ch]" data-ui--session-tabs-target="label">Pinned</span>
</button>
<div class="relative flex shrink-0" data-controller="ui--menu"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! disabled:cursor-not-allowed mr-1 overflow-hidden text-muted-foreground hover:bg-primary/10 hover:text-primary aria-expanded:bg-primary/10 aria-expanded:text-primary size-6 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none opacity-0 group-hover/tab:opacity-100 group-focus-within/tab:opacity-100 group-data-[dragging]/tab:opacity-100 group-has-[[aria-expanded=true]]/tab:opacity-100 [@media(hover:none)]:opacity-100" aria-busy="false" tabindex="0" aria-label="Tab options" aria-describedby="ui-session-tabs-49921390-tab-pinned-label" data-ui--session-tabs-target="menuTrigger" id="ui-menu-trigger-ui-menu-a3a4448f" aria-haspopup="true" aria-expanded="false" aria-controls="ui-menu-content-ui-menu-a3a4448f" data-ui--menu-target="trigger" data-action="click->ui--menu#toggle keydown->ui--menu#triggerKeydown"><i data-lucide="ellipsis-vertical" class="inline-block shrink-0 w-3 h-3 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none translate-x-2 group-hover/tab:translate-x-0 group-focus-within/tab:translate-x-0 group-data-[dragging]/tab:translate-x-0 group-has-[[aria-expanded=true]]/tab:translate-x-0 [@media(hover:none)]:translate-x-0" aria-hidden="true"></i>
</button>
<div id="ui-menu-content-ui-menu-a3a4448f" class="z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col gap-[5px] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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" role="menu" aria-orientation="vertical" data-ui--menu-target="content" hidden="hidden" data-side="bottom" data-align="end" tabindex="-1">
<button data-action="click->ui--session-tabs#rename" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item">Rename</button>
<button data-action="click->ui--session-tabs#moveLeft" data-move="left" hidden="hidden" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item" disabled="disabled" data-disabled="true">Move left</button>
<button data-action="click->ui--session-tabs#moveRight" data-move="right" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item">Move right</button>
<div data-close-part="true" class="-mx-1 h-px bg-muted" role="separator" aria-orientation="horizontal"></div>
<button data-action="click->ui--session-tabs#close" data-close-part="true" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10 focus:text-destructive" role="menuitem" tabindex="-1" data-ui--menu-target="item">Close</button>
</div>
</div>
</div>
<div role="presentation" class="group/tab relative flex shrink-0 items-center rounded-t-md border border-b-0 border-transparent bg-muted/60 text-muted-foreground transition-colors hover:border-primary/20 hover:bg-primary/5 hover:text-foreground data-[state=active]:border-border data-[state=active]:bg-background data-[state=active]:text-foreground data-[dragging]:z-10 data-[dragging]:border-border data-[dragging]:bg-background data-[dragging]:shadow-md data-[dragging]:transition-none h-9" data-ui--session-tabs-target="tab" data-value="notes" data-state="inactive" data-rename-enabled="true" data-duplicate-enabled="true" data-move-enabled="true" data-close-enabled="true">
<span class="flex h-full shrink-0 cursor-grab touch-none select-none items-center justify-center overflow-hidden rounded-tl-md text-muted-foreground hover:text-primary active:cursor-grabbing w-5 pl-1" aria-hidden="true" data-ui--session-tabs-target="handle" data-action="pointerdown->ui--session-tabs#dragStart"><i data-lucide="grip-vertical" class="inline-block shrink-0 w-3 h-3 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none opacity-0 group-hover/tab:opacity-100 group-focus-within/tab:opacity-100 group-data-[dragging]/tab:opacity-100 group-has-[[aria-expanded=true]]/tab:opacity-100 [@media(hover:none)]:opacity-100 -translate-x-2 group-hover/tab:translate-x-0 group-focus-within/tab:translate-x-0 group-data-[dragging]/tab:translate-x-0 group-has-[[aria-expanded=true]]/tab:translate-x-0 [@media(hover:none)]:translate-x-0" aria-hidden="true"></i>
</span>
<button id="ui-session-tabs-49921390-tab-notes" role="tab" class="flex h-full min-w-0 items-center gap-2 rounded-t-md text-sm font-medium outline-none select-none [-webkit-touch-callout:none] focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset pr-1 pl-0.5" aria-selected="false" tabindex="-1" draggable="false" data-ui--session-tabs-target="trigger" data-action="click->ui--session-tabs#select keydown->ui--session-tabs#keydown contextmenu->ui--session-tabs#contextMenu pointerdown->ui--session-tabs#pressStart" type="button">
<span id="ui-session-tabs-49921390-tab-notes-label" class="truncate max-w-[20ch]" data-ui--session-tabs-target="label">Notes</span>
</button>
<div class="relative flex shrink-0" data-controller="ui--menu"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! disabled:bg-transparent! disabled:cursor-not-allowed mr-1 overflow-hidden text-muted-foreground hover:bg-primary/10 hover:text-primary aria-expanded:bg-primary/10 aria-expanded:text-primary size-6 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none opacity-0 group-hover/tab:opacity-100 group-focus-within/tab:opacity-100 group-data-[dragging]/tab:opacity-100 group-has-[[aria-expanded=true]]/tab:opacity-100 [@media(hover:none)]:opacity-100" aria-busy="false" tabindex="-1" aria-label="Tab options" aria-describedby="ui-session-tabs-49921390-tab-notes-label" data-ui--session-tabs-target="menuTrigger" id="ui-menu-trigger-ui-menu-47f1fd13" aria-haspopup="true" aria-expanded="false" aria-controls="ui-menu-content-ui-menu-47f1fd13" data-ui--menu-target="trigger" data-action="click->ui--menu#toggle keydown->ui--menu#triggerKeydown"><i data-lucide="ellipsis-vertical" class="inline-block shrink-0 w-3 h-3 transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none translate-x-2 group-hover/tab:translate-x-0 group-focus-within/tab:translate-x-0 group-data-[dragging]/tab:translate-x-0 group-has-[[aria-expanded=true]]/tab:translate-x-0 [@media(hover:none)]:translate-x-0" aria-hidden="true"></i>
</button>
<div id="ui-menu-content-ui-menu-47f1fd13" class="z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col gap-[5px] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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" role="menu" aria-orientation="vertical" data-ui--menu-target="content" hidden="hidden" data-side="bottom" data-align="end" tabindex="-1">
<button data-action="click->ui--session-tabs#rename" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item">Rename</button>
<button data-action="click->ui--session-tabs#duplicate" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item">Duplicate</button>
<button data-action="click->ui--session-tabs#moveLeft" data-move="left" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item">Move left</button>
<button data-action="click->ui--session-tabs#moveRight" data-move="right" hidden="hidden" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all" role="menuitem" tabindex="-1" data-ui--menu-target="item" disabled="disabled" data-disabled="true">Move right</button>
<div data-close-part="true" class="-mx-1 h-px bg-muted" role="separator" aria-orientation="horizontal"></div>
<button data-action="click->ui--session-tabs#close" data-close-part="true" class="relative flex cursor-default select-none items-center gap-2 rounded w-full px-2 py-1 text-sm whitespace-nowrap border-0 bg-transparent outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1 aria-checked:bg-accent aria-checked:text-accent-foreground aria-checked:font-medium data-[disabled]:pointer-events-none data-[disabled]:opacity-50 transition-all text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10 focus:text-destructive" role="menuitem" tabindex="-1" data-ui--menu-target="item">Close</button>
</div>
</div>
</div>
</div>
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:text-muted-foreground! hover:bg-accent hover:text-accent-foreground disabled:bg-transparent! disabled:cursor-not-allowed mb-1 shrink-0 size-7" aria-busy="false" aria-label="New tab" title="New tab" data-action="click->ui--session-tabs#add"><i data-lucide="plus" class="inline-block shrink-0 w-3 h-3" aria-hidden="true"></i>
</button>
<div class="contents" data-controller="ui--dialog" data-ui--session-tabs-target="dialog"><dialog id="ui-session-tabs-49921390-confirm" class="fixed inset-0 m-0 p-0 max-w-none max-h-none w-full h-full bg-transparent border-0 overflow-visible open:flex open:items-center open:justify-center backdrop:bg-black/60 backdrop:backdrop-blur-[1px]" aria-labelledby="ui-session-tabs-49921390-confirm-title" aria-describedby="ui-session-tabs-49921390-confirm-description" data-ui--dialog-target="content" data-state="closed">
<div class="relative flex flex-col w-full max-h-[calc(100vh-2rem)] overflow-hidden rounded-lg border bg-background p-6 shadow-lg data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 max-w-md" data-state="closed" data-ui--dialog-part="panel">
<header class="flex flex-col space-y-1.5 text-center sm:text-left">
<h2 id="ui-session-tabs-49921390-confirm-title" class="text-lg font-semibold leading-none tracking-tight">Close this tab?</h2>
<p id="ui-session-tabs-49921390-confirm-description" class="text-sm text-muted-foreground"><span data-ui--session-tabs-target="confirmMessage"></span></p>
</header>
<footer class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"> <button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:border-input! h-9 px-4 py-2 disabled:cursor-not-allowed" aria-busy="false" data-action="click->ui--dialog#close">Keep it open</button>
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-offset-1 ring-offset-background disabled:pointer-events-none disabled:bg-muted! disabled:text-muted-foreground! bg-destructive text-white hover:bg-destructive/90 focus:ring-destructive h-9 px-4 py-2 disabled:cursor-not-allowed" aria-busy="false" data-action="click->ui--session-tabs#confirmClose">Close tab</button>
</footer>
</div>
</dialog></div>
</div>
<pre class="h-40 overflow-auto rounded-md bg-muted p-3 text-xs" data-log></pre>
</div>
<script>
(() => {
const root = document.getElementById("session-tabs-events-demo");
const log = root.querySelector("[data-log]");
let n = 0;
const write = (line) => { log.textContent = line + "\n" + log.textContent; };
for (const action of ["select", "add", "rename", "duplicate", "move", "close"]) {
root.addEventListener(`ui--session-tabs:${action}`, (event) => {
write(`${action} ${JSON.stringify(event.detail)}`);
if (action === "close" && event.detail.value === "pinned") {
event.preventDefault();
write(" ↳ cancelled: Pinned can't be closed");
}
if (action === "add" || action === "duplicate") {
// The app supplies new markup; here, copy an existing tab.
const list = root.querySelector('[role="tablist"]');
const source = list.querySelector('[data-value="notes"]') || list.lastElementChild;
const copy = source.cloneNode(true);
n += 1;
const value = `new-${n}`;
copy.dataset.value = value;
copy.dataset.state = "inactive";
copy.querySelectorAll("[id]").forEach((el) => { el.id = `${el.id}-${value}`; });
copy.querySelector('[data-ui--session-tabs-target="label"]').textContent =
action === "add" ? `New tab ${n}` : `Copy ${n}`;
list.appendChild(copy);
}
});
}
})();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<%# Every action dispatches a cancelable ui--session-tabs:<action> event.
This page logs them, refuses to close "Pinned", and handles "add" itself
by cloning a <template>. In a real app, pass URLs instead and let the
server re-render. %>
<div class="max-w-2xl space-y-3" id="session-tabs-events-demo">
<%= render(UI::SessionTabs.new) do |tabs| %>
<% tabs.with_tab(value: "pinned", active: true, rename: true, move: true, close: true) { "Pinned" } %>
<% tabs.with_tab(value: "notes", rename: true, duplicate: true, move: true, close: true) { "Notes" } %>
<% tabs.with_add %>
<% end %>
<pre class="h-40 overflow-auto rounded-md bg-muted p-3 text-xs" data-log></pre>
</div>
<script>
(() => {
const root = document.getElementById("session-tabs-events-demo");
const log = root.querySelector("[data-log]");
let n = 0;
const write = (line) => { log.textContent = line + "\n" + log.textContent; };
for (const action of ["select", "add", "rename", "duplicate", "move", "close"]) {
root.addEventListener(`ui--session-tabs:${action}`, (event) => {
write(`${action} ${JSON.stringify(event.detail)}`);
if (action === "close" && event.detail.value === "pinned") {
event.preventDefault();
write(" ↳ cancelled: Pinned can't be closed");
}
if (action === "add" || action === "duplicate") {
// The app supplies new markup; here, copy an existing tab.
const list = root.querySelector('[role="tablist"]');
const source = list.querySelector('[data-value="notes"]') || list.lastElementChild;
const copy = source.cloneNode(true);
n += 1;
const value = `new-${n}`;
copy.dataset.value = value;
copy.dataset.state = "inactive";
copy.querySelectorAll("[id]").forEach((el) => { el.id = `${el.id}-${value}`; });
copy.querySelector('[data-ui--session-tabs-target="label"]').textContent =
action === "add" ? `New tab ${n}` : `Copy ${n}`;
list.appendChild(copy);
}
});
}
})();
</script>