feat(desktop): unify tab organization menu and adaptive collapsed rail
This commit is contained in:
17 files changed
+212
-83
No files matched your search
@@ -55,7 +55,7 @@ const layoutClass = computed(() => {
|
||||
});
|
||||
const navigationStyle = computed<CSSProperties>(() => {
|
||||
if (!isVerticalLayout.value) return { maxHeight: "50%" };
|
||||
const width = props.tabBarCollapsed ? "3.5rem" : `${props.tabBarWidth ?? 240}px`;
|
||||
const width = props.tabBarCollapsed ? "var(--collapsed-tab-rail-width)" : `${props.tabBarWidth ?? 240}px`;
|
||||
return { width, flex: `0 0 ${width}` };
|
||||
});
|
||||
function setTabBarTarget(groupId: string, element: unknown) {
|
||||
|
||||
@@ -30,7 +30,37 @@ const TAB_DRAG_HORIZONTAL_THRESHOLD = 24;
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ArrowDown, ArrowDownUp, ArrowRight, ChevronDown, ChevronsDownUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, Copy, ListFilter, Maximize2, Minimize2, Package, PanelTop, Pencil, Pin, RotateCcw, RotateCw, Search, Settings, X } from "@lucide/vue";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowDownAZ,
|
||||
ArrowRight,
|
||||
ChevronDown,
|
||||
ChevronsDownUp,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
ChevronsUpDown,
|
||||
Clock3,
|
||||
Copy,
|
||||
Database,
|
||||
ListFilter,
|
||||
ListOrdered,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
Package,
|
||||
PanelBottom,
|
||||
PanelLeft,
|
||||
PanelRight,
|
||||
PanelTop,
|
||||
Pencil,
|
||||
Pin,
|
||||
RotateCcw,
|
||||
RotateCw,
|
||||
Search,
|
||||
Server,
|
||||
Settings,
|
||||
Ungroup,
|
||||
X,
|
||||
} from "@lucide/vue";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import LightDropdown from "@/components/ui/LightDropdown.vue";
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
||||
@@ -134,7 +164,9 @@ const isWrapLayout = computed(() => !isVerticalLayout.value && settingsStore.edi
|
||||
const isTabBarCollapsed = computed(() => isVerticalLayout.value && !!props.tabBarCollapsed);
|
||||
const tabBarStyle = computed<CSSProperties | undefined>(() => {
|
||||
if (!isVerticalLayout.value) return undefined;
|
||||
if (props.tabBarCollapsed) return { width: "3.5rem", flex: "0 0 3.5rem" };
|
||||
if (props.tabBarCollapsed) {
|
||||
return { width: "100%", flex: "0 0 100%" };
|
||||
}
|
||||
const width = props.tabBarWidth ?? 240;
|
||||
return { width: `${width}px`, flex: `0 0 ${width}px` };
|
||||
});
|
||||
@@ -725,36 +757,19 @@ function closeTabGroup(tab: QueryTab) {
|
||||
queryStore.closeTabsByIds(tabsToClose, finalActiveTabId);
|
||||
}
|
||||
|
||||
function getTabPreferenceMenuItems(): ContextMenuItem[] {
|
||||
return [
|
||||
{
|
||||
label: t("settings.tabPlacement"),
|
||||
icon: PanelTop,
|
||||
children: tabPlacementItems.value.map((item) => ({
|
||||
label: item.label,
|
||||
checked: item.value === settingsStore.editorSettings.tabPlacement,
|
||||
action: () => updateTabPlacement(item.value),
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t("settings.tabGroup"),
|
||||
icon: ListFilter,
|
||||
children: tabGroupItems.value.map((item) => ({
|
||||
label: item.label,
|
||||
checked: item.value === settingsStore.editorSettings.tabGroupMode,
|
||||
action: () => updateTabGroupMode(item.value),
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: t("settings.tabSort"),
|
||||
icon: ArrowDownUp,
|
||||
children: tabSortItems.value.map((item) => ({
|
||||
label: item.label,
|
||||
checked: item.value === settingsStore.editorSettings.tabSortMode,
|
||||
action: () => updateTabSortMode(item.value),
|
||||
})),
|
||||
},
|
||||
];
|
||||
const tabOrganizationItems = computed(() => [
|
||||
...tabPlacementItems.value.map((item, index) => ({ ...item, value: `placement:${item.value}`, icon: { top: PanelTop, bottom: PanelBottom, left: PanelLeft, right: PanelRight }[item.value], groupLabel: index === 0 ? t("settings.tabPlacement") : undefined })),
|
||||
...tabGroupItems.value.map((item, index) => ({ ...item, value: `group:${item.value}`, icon: { none: Ungroup, "database-type": Database, database: Database, connection: Server }[item.value], separatorBefore: index === 0, groupLabel: index === 0 ? t("settings.tabGroup") : undefined })),
|
||||
...tabSortItems.value.map((item, index) => ({ ...item, value: `sort:${item.value}`, icon: { manual: ListOrdered, "created-asc": Clock3, "title-asc": ArrowDownAZ }[item.value], separatorBefore: index === 0, groupLabel: index === 0 ? t("settings.tabSort") : undefined })),
|
||||
]);
|
||||
const selectedTabOrganizationItems = computed(() => [`placement:${settingsStore.editorSettings.tabPlacement}`, `group:${settingsStore.editorSettings.tabGroupMode}`, `sort:${settingsStore.editorSettings.tabSortMode}`]);
|
||||
|
||||
function selectTabOrganizationItem(value: string) {
|
||||
const [section, option] = value.split(":");
|
||||
if (!option) return;
|
||||
if (section === "placement") updateTabPlacement(option);
|
||||
else if (section === "group") updateTabGroupMode(option);
|
||||
else if (section === "sort") updateTabSortMode(option);
|
||||
}
|
||||
|
||||
function getTabGroupMenuItems(tab: QueryTab): ContextMenuItem[] {
|
||||
@@ -785,8 +800,6 @@ function getTabGroupMenuItems(tab: QueryTab): ContextMenuItem[] {
|
||||
visible: settingsStore.editorSettings.tabGroupMode !== "none",
|
||||
},
|
||||
{ label: "", separator: true },
|
||||
...getTabPreferenceMenuItems(),
|
||||
{ label: "", separator: true },
|
||||
{
|
||||
label: t("contextMenu.closeTabGroup"),
|
||||
action: () => closeTabGroup(tab),
|
||||
@@ -1085,8 +1098,6 @@ function getTabMenuItems(tab: QueryTab): ContextMenuItem[] {
|
||||
onLocate: () => emit("locate-tab", tab),
|
||||
}),
|
||||
{ label: "", separator: true },
|
||||
...getTabPreferenceMenuItems(),
|
||||
{ label: "", separator: true },
|
||||
createPinTabMenuItem({
|
||||
label: tab.pinned ? t("contextMenu.unpinTab") : t("contextMenu.pinTab"),
|
||||
onToggle: () => queryStore.togglePinnedTab(tab.id),
|
||||
@@ -1402,33 +1413,58 @@ watch([() => props.specialPageTabs?.settingsActive, () => props.specialPageTabs?
|
||||
:data-group-mode="settingsStore.editorSettings.tabGroupMode"
|
||||
:data-placement="settingsStore.editorSettings.tabPlacement"
|
||||
>
|
||||
<!-- Compact vertical toolbar: search, grouping preference, collapse. -->
|
||||
<!-- Compact vertical toolbar: search, tab organization, collapse. -->
|
||||
<div v-if="isVerticalLayout" class="flex h-9 shrink-0 items-center gap-0.5 border-b p-1" :class="isTabBarCollapsed ? 'justify-center' : ''">
|
||||
<div v-if="!isTabBarCollapsed" class="relative min-w-0 flex-1">
|
||||
<Search class="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input v-model="tabSearchQuery" type="search" :placeholder="t('tabs.searchOpenTabs')" class="h-7 w-full pl-7 text-sm" />
|
||||
</div>
|
||||
<div v-if="!isTabBarCollapsed" class="flex shrink-0 items-center gap-0">
|
||||
<LightDropdown
|
||||
:model-value="settingsStore.editorSettings.tabGroupMode"
|
||||
:items="tabGroupItems"
|
||||
:aria-label="t('settings.tabGroup')"
|
||||
:trigger-title="t('settings.tabGroup')"
|
||||
:trigger-icon="ListFilter"
|
||||
:trigger-class="verticalTabToolbarButtonClass"
|
||||
:show-trigger-label="false"
|
||||
:show-chevron="false"
|
||||
check-position="right"
|
||||
:match-trigger-width="false"
|
||||
align="end"
|
||||
@update:model-value="updateTabGroupMode"
|
||||
/>
|
||||
</div>
|
||||
<LightDropdown
|
||||
v-if="!isTabBarCollapsed"
|
||||
model-value=""
|
||||
:items="tabOrganizationItems"
|
||||
:selected-values="selectedTabOrganizationItems"
|
||||
:aria-label="t('settings.tabOrganization')"
|
||||
:trigger-title="t('settings.tabOrganization')"
|
||||
:trigger-icon="ListFilter"
|
||||
:trigger-class="verticalTabToolbarButtonClass"
|
||||
trigger-icon-class="h-4 w-4"
|
||||
item-icon-class="h-3.5 w-3.5"
|
||||
content-class="w-max min-w-0"
|
||||
selected-item-class="bg-primary/10 text-primary"
|
||||
selected-check-class="text-primary"
|
||||
:show-trigger-label="false"
|
||||
:show-chevron="false"
|
||||
:close-on-select="false"
|
||||
:match-trigger-width="false"
|
||||
align="end"
|
||||
@update:model-value="selectTabOrganizationItem"
|
||||
/>
|
||||
<button type="button" :class="verticalTabToolbarButtonClass" :title="tabBarCollapseLabel" :aria-label="tabBarCollapseLabel" :aria-expanded="!isTabBarCollapsed" @click="emit('toggle-collapse')">
|
||||
<component :is="tabBarCollapseIcon" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative flex w-full min-w-0 shrink-0 overflow-hidden" :class="[isVerticalLayout ? ['min-h-0 flex-1 flex-col items-stretch'] : isClassicLayout ? 'h-9 items-stretch' : 'h-10 items-center px-2', { 'has-tab-overflow-control': showOverflowControl }]">
|
||||
<LightDropdown
|
||||
v-if="!isVerticalLayout"
|
||||
model-value=""
|
||||
:items="tabOrganizationItems"
|
||||
:selected-values="selectedTabOrganizationItems"
|
||||
:aria-label="t('settings.tabOrganization')"
|
||||
:trigger-title="t('settings.tabOrganization')"
|
||||
:trigger-icon="ListFilter"
|
||||
trigger-class="tab-organization-button"
|
||||
trigger-icon-class="h-4 w-4"
|
||||
item-icon-class="h-3.5 w-3.5"
|
||||
content-class="w-max min-w-0"
|
||||
selected-item-class="bg-primary/10 text-primary"
|
||||
selected-check-class="text-primary"
|
||||
:show-trigger-label="false"
|
||||
:show-chevron="false"
|
||||
:close-on-select="false"
|
||||
:match-trigger-width="false"
|
||||
@update:model-value="selectTabOrganizationItem"
|
||||
/>
|
||||
<div class="app-tab-strip relative h-full min-w-0 flex-1 overflow-hidden">
|
||||
<div v-if="showOverflowControl && !hasHorizontalFixedRows" class="app-tab-scrollbar" :class="{ 'app-tab-scrollbar--dragging': isScrollbarDragging }" @pointerdown="startScrollbarDrag">
|
||||
<div class="app-tab-scrollbar__thumb" :style="tabScrollbarThumbStyle" />
|
||||
@@ -1482,7 +1518,7 @@ watch([() => props.specialPageTabs?.settingsActive, () => props.specialPageTabs?
|
||||
@contextmenu="openTabGroupContextMenu($event, onContextMenu)"
|
||||
>
|
||||
<span class="tab-group-header-content">
|
||||
<span v-if="isVerticalLayout" class="tab-group-marker" aria-hidden="true" />
|
||||
<span class="tab-group-marker" aria-hidden="true" />
|
||||
<Pin v-if="entry.pinned" class="tab-group-pin" aria-hidden="true" />
|
||||
<ChevronDown class="tab-group-chevron" :class="{ 'tab-group-chevron--collapsed': isTabGroupCollapsed(entry.tab) }" aria-hidden="true" />
|
||||
<DatabaseIcon :db-type="tabDatabaseIconType(entry.tab)" class="tab-group-database-icon" aria-hidden="true" />
|
||||
|
||||
@@ -123,7 +123,7 @@ provide(GROUP_TAB_BAR_PORTAL, {
|
||||
},
|
||||
});
|
||||
const tabNavigationStyle = computed(() => {
|
||||
const width = props.tabBarCollapsed ? "3.5rem" : `${props.tabBarWidth ?? 240}px`;
|
||||
const width = props.tabBarCollapsed ? "var(--collapsed-tab-rail-width)" : `${props.tabBarWidth ?? 240}px`;
|
||||
return { width, flex: `0 0 ${width}` };
|
||||
});
|
||||
function setTabBarTarget(groupId: string, element: unknown) {
|
||||
|
||||
@@ -363,8 +363,8 @@ describe("AppTabBar group navigation portal", () => {
|
||||
expect(bar.style.width).toBe("320px");
|
||||
navigation.collapsed = true;
|
||||
await settle();
|
||||
expect(rail.style.width).toBe("3.5rem");
|
||||
expect(bar.style.width).toBe("3.5rem");
|
||||
expect(rail.style.width).toBe("var(--collapsed-tab-rail-width)");
|
||||
expect(bar.style.width).toBe("100%");
|
||||
expect(element(rail, "[data-main-tab-bar]")).toBe(bar);
|
||||
navigation.collapsed = false;
|
||||
await settle();
|
||||
|
||||
+6
-14
@@ -61,17 +61,9 @@ describe("EditorGroupTabBar semantic tab groups", () => {
|
||||
expect(source).toContain('type="color"');
|
||||
});
|
||||
|
||||
it("exposes placement, grouping, and sorting preferences from the group context menu", () => {
|
||||
const preferences = sourceBetween("function getTabPreferenceMenuItems", "function getTabGroupMenuItems");
|
||||
it("keeps placement, grouping, and sorting out of the group context menu", () => {
|
||||
const groupMenu = sourceBetween("function getTabGroupMenuItems", "function openTabGroupContextMenu");
|
||||
expect(preferences).toContain('label: t("settings.tabPlacement")');
|
||||
expect(preferences).toContain("action: () => updateTabPlacement(item.value)");
|
||||
expect(preferences).toContain('label: t("settings.tabGroup")');
|
||||
expect(preferences).toContain("action: () => updateTabGroupMode(item.value)");
|
||||
expect(preferences).toContain('label: t("settings.tabSort")');
|
||||
expect(preferences).toContain("action: () => updateTabSortMode(item.value)");
|
||||
expect(preferences.match(/checked: item\.value === settingsStore\.editorSettings\./g)).toHaveLength(3);
|
||||
expect(groupMenu).toContain("...getTabPreferenceMenuItems()");
|
||||
expect(groupMenu).not.toContain("getTabPreferenceMenuItems");
|
||||
});
|
||||
|
||||
it("exposes preferences and the group close from each tab context menu", () => {
|
||||
@@ -80,7 +72,7 @@ describe("EditorGroupTabBar semantic tab groups", () => {
|
||||
expect(menuStart).toBeGreaterThanOrEqual(0);
|
||||
expect(menuEnd).toBeGreaterThan(menuStart);
|
||||
const menu = source.slice(menuStart, menuEnd);
|
||||
expect(menu).toContain("...getTabPreferenceMenuItems()");
|
||||
expect(menu).not.toContain("getTabPreferenceMenuItems");
|
||||
expect(menu).toContain("action: () => closeTabGroup(tab)");
|
||||
expect(menu).toContain('visible: settingsStore.editorSettings.tabGroupMode !== "none"');
|
||||
});
|
||||
@@ -89,7 +81,7 @@ describe("EditorGroupTabBar semantic tab groups", () => {
|
||||
// D1 (amended): closing a cluster is destructive and stays bar-local —
|
||||
// scoped to this pane's tabs (props.tabs), still within the trigger's
|
||||
// pinned section. Profile edits (rename/color/reset) keep global reach.
|
||||
const closeGroup = sourceBetween("function tabsInSemanticGroup", "function getTabPreferenceMenuItems");
|
||||
const closeGroup = sourceBetween("function tabsInSemanticGroup", "function getTabGroupMenuItems");
|
||||
expect(closeGroup).toContain("props.tabs.filter((item) => item.pinned === tab.pinned && tabGroupKey(item) === groupKey)");
|
||||
expect(closeGroup).toContain("queryStore.closeTabsByIds(tabsToClose, finalActiveTabId)");
|
||||
});
|
||||
@@ -572,9 +564,9 @@ describe("EditorGroupTabBar group behavior", () => {
|
||||
try {
|
||||
await settle();
|
||||
const header = host.querySelector<HTMLButtonElement>(".tab-group-header")!;
|
||||
// Horizontal headers lead with the database glyph; the rail marker stays vertical-only.
|
||||
// Group headers consistently expose the placement marker.
|
||||
expect(header.querySelector(".tab-group-database-icon")).not.toBeNull();
|
||||
expect(header.querySelector(".tab-group-marker")).toBeNull();
|
||||
expect(header.querySelector(".tab-group-marker")).not.toBeNull();
|
||||
const chevron = header.querySelector<HTMLElement>(".tab-group-chevron")!;
|
||||
expect(getComputedStyle(chevron).width).toBe("14px");
|
||||
expect(getComputedStyle(chevron).height).toBe("14px");
|
||||
|
||||
@@ -475,13 +475,38 @@
|
||||
}
|
||||
|
||||
.tab-group-marker {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
width: 0.22rem;
|
||||
height: 1rem;
|
||||
flex: none;
|
||||
border-radius: 9999px;
|
||||
background: var(--tab-group-color);
|
||||
}
|
||||
|
||||
.tab-organization-button {
|
||||
display: inline-flex;
|
||||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: flex-start;
|
||||
margin-block: 0.25rem;
|
||||
margin-inline: 0.25rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.375rem;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
.tab-organization-button:hover {
|
||||
border-color: var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tab-organization-button:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.tab-group-chevron {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
@@ -544,6 +569,35 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Modern split bars use h-7 (1.75rem) pills. Keep the group header on that
|
||||
* exact baseline; the surrounding 2.5rem strip supplies the outer breathing
|
||||
* room. Wrapped rows opt out and use their intrinsic row height below. */
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-header {
|
||||
height: 1.75rem;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-header-content {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
/* The modern top/bottom bar joins the group header and its pills into one
|
||||
* continuous horizontal rail. Rounded pseudo-elements at the seam create a
|
||||
* visible bump (the “turn” between the two pieces), so keep that rail flat. */
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-header::after,
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-tab::after {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-header::after {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.app-tab-bar.separated-tab-layout:not(.vertical-tab-layout):not(:has(.wrap-mode))[data-placement="bottom"] .tab-group-header::after {
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
/* Wrapped rows must use their own content height. In particular, avoid
|
||||
* height: 100% here: when a group expands into another row it makes the
|
||||
* auto-sized flex container feed its height back into the row measurement. */
|
||||
@@ -811,6 +865,9 @@
|
||||
|
||||
.app-tab-bar:not(.vertical-tab-layout):not(:has(.wrap-mode)) .tab-group-entry--collapsing {
|
||||
pointer-events: none;
|
||||
/* The keyframe already owns the collapse. Without this, removing the
|
||||
* class at the end starts the base max-width transition a second time. */
|
||||
transition: none;
|
||||
animation: tab-group-collapse 140ms ease forwards;
|
||||
}
|
||||
|
||||
@@ -1017,15 +1074,52 @@
|
||||
box-shadow: 0 0 0 1px var(--background);
|
||||
}
|
||||
|
||||
/* Both portal hosts size from the badges actually rendered, including search
|
||||
* filtering and multiple editor groups. All bars fill the same host width. */
|
||||
[data-workspace-tab-navigation],
|
||||
[data-special-page-navigation] {
|
||||
--collapsed-tab-rail-width: 3.5rem;
|
||||
}
|
||||
|
||||
[data-workspace-tab-navigation]:has(.vertical-tab-layout--collapsed .tab-group-count),
|
||||
[data-special-page-navigation]:has(.vertical-tab-layout--collapsed .tab-group-count) {
|
||||
--collapsed-tab-rail-width: 4.5rem;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-header {
|
||||
width: 2.5rem !important;
|
||||
width: 100% !important;
|
||||
height: 1.75rem;
|
||||
margin: 0.35rem auto 0.1rem;
|
||||
margin: 0.35rem 0 0.1rem;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-header-content {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
padding-inline: 0.55rem;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-header-content:has(.tab-group-count) {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-label,
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-pin {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-count {
|
||||
position: static;
|
||||
z-index: auto;
|
||||
margin-inline-start: auto;
|
||||
min-width: 1.15rem;
|
||||
height: 1.15rem;
|
||||
padding-inline: 0.2rem;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Reserve a separate slot for the count in the compact group header. */
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-header:has(.tab-group-count) .tab-group-database-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vertical-tab-layout.vertical-tab-layout--collapsed .tab-group-tab {
|
||||
@@ -1074,12 +1168,6 @@
|
||||
color: var(--tab-group-color);
|
||||
}
|
||||
|
||||
.vertical-tab-layout .tab-group-marker {
|
||||
width: 0.22rem;
|
||||
height: 1rem;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.vertical-tab-layout .tab-group-header:not(.tab-group-header--collapsed)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
@@ -6815,6 +6815,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "Sol tərəf",
|
||||
tabPlacementRight: "Sağ tərəf",
|
||||
tabPlacementDescription: "Yan vərəq zolaqları şaquli sürüşdürülür; bir neçə sətrə keçirmə yalnız məzmunun üstündə və ya altında tətbiq olunur.",
|
||||
tabOrganization: "Mövqe, qruplaşdırma, sıralama",
|
||||
tabGroup: "Qruplaşdırma meyarı",
|
||||
tabGroupNone: "Qruplaşdırma yoxdur",
|
||||
tabGroupDatabaseType: "Verilənlər bazasının növünə görə qruplaşdır",
|
||||
|
||||
@@ -6850,6 +6850,7 @@ export default {
|
||||
tabPlacementLeft: "Left side",
|
||||
tabPlacementRight: "Right side",
|
||||
tabPlacementDescription: "Side tab bars scroll vertically; multi-row wrapping applies only above or below content.",
|
||||
tabOrganization: "Position, grouping, sorting",
|
||||
tabGroup: "Group by",
|
||||
tabGroupNone: "No grouping",
|
||||
tabGroupDatabaseType: "Group by database type",
|
||||
|
||||
@@ -6424,6 +6424,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "Lado izquierdo",
|
||||
tabPlacementRight: "Lado derecho",
|
||||
tabPlacementDescription: "Las barras laterales se desplazan verticalmente; el ajuste multilínea solo se aplica arriba o abajo.",
|
||||
tabOrganization: "Posición, agrupación y ordenación",
|
||||
tabGroup: "Agrupar por",
|
||||
tabGroupNone: "Sin agrupación",
|
||||
tabGroupDatabaseType: "Agrupar por tipo de base de datos",
|
||||
|
||||
@@ -6424,6 +6424,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "Lato sinistro",
|
||||
tabPlacementRight: "Lato destro",
|
||||
tabPlacementDescription: "Le barre laterali scorrono verticalmente; le righe multiple valgono solo sopra o sotto il contenuto.",
|
||||
tabOrganization: "Posizione, raggruppamento e ordinamento",
|
||||
tabGroup: "Raggruppa per",
|
||||
tabGroupNone: "Nessun raggruppamento",
|
||||
tabGroupDatabaseType: "Raggruppa per tipo di database",
|
||||
|
||||
@@ -6462,6 +6462,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "左側",
|
||||
tabPlacementRight: "右側",
|
||||
tabPlacementDescription: "サイドのタブバーは縦にスクロールします。複数行表示はコンテンツの上下でのみ利用できます。",
|
||||
tabOrganization: "位置・グループ化・並び順",
|
||||
tabGroup: "グループ化",
|
||||
tabGroupNone: "グループ化しない",
|
||||
tabGroupDatabaseType: "データベース種別でグループ化",
|
||||
|
||||
@@ -6167,6 +6167,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "왼쪽",
|
||||
tabPlacementRight: "오른쪽",
|
||||
tabPlacementDescription: "사이드 탭 표시줄은 세로로 스크롤됩니다. 여러 줄 표시는 콘텐츠 위 또는 아래에서만 적용됩니다.",
|
||||
tabOrganization: "위치, 그룹화, 정렬",
|
||||
tabGroup: "그룹 기준",
|
||||
tabGroupNone: "그룹화 안 함",
|
||||
tabGroupDatabaseType: "데이터베이스 유형별 그룹화",
|
||||
|
||||
@@ -6426,6 +6426,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "Lado esquerdo",
|
||||
tabPlacementRight: "Lado direito",
|
||||
tabPlacementDescription: "As barras laterais rolam verticalmente; a quebra em múltiplas linhas só se aplica acima ou abaixo do conteúdo.",
|
||||
tabOrganization: "Posição, agrupamento e ordenação",
|
||||
tabGroup: "Agrupar por",
|
||||
tabGroupNone: "Sem agrupamento",
|
||||
tabGroupDatabaseType: "Agrupar por tipo de banco de dados",
|
||||
|
||||
@@ -6697,6 +6697,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "Sol taraf",
|
||||
tabPlacementRight: "Sağ taraf",
|
||||
tabPlacementDescription: "Yan sekme çubukları dikey kayar; çok satırlı sarma yalnızca içeriğin üstünde veya altında geçerlidir.",
|
||||
tabOrganization: "Konum, gruplama, sıralama",
|
||||
tabGroup: "Gruplama ölçütü",
|
||||
tabGroupNone: "Gruplama yok",
|
||||
tabGroupDatabaseType: "Veritabanı türüne göre grupla",
|
||||
|
||||
@@ -6833,6 +6833,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "左侧",
|
||||
tabPlacementRight: "右侧",
|
||||
tabPlacementDescription: "侧边标签栏使用纵向滚动;多行平铺仅适用于上方或下方。",
|
||||
tabOrganization: "位置、分组、排序",
|
||||
tabGroup: "分组方式",
|
||||
tabGroupNone: "不分组",
|
||||
tabGroupDatabaseType: "按数据库类型分组",
|
||||
|
||||
@@ -5743,6 +5743,7 @@ export default withEnglishFallback({
|
||||
tabPlacementLeft: "左側",
|
||||
tabPlacementRight: "右側",
|
||||
tabPlacementDescription: "側邊標籤欄使用垂直捲動;多行平鋪僅適用於上方或下方。",
|
||||
tabOrganization: "位置、分組、排序",
|
||||
tabGroup: "分組方式",
|
||||
tabGroupNone: "不分組",
|
||||
tabGroupDatabaseType: "依資料庫類型分組",
|
||||
|
||||
Reference in new issue
Block a user