fix(web): align segmented controls, vertical nav and button padding with published guidance

- Selected segments get a 3:1 accent border, and dark mode uses an indigo fill to match the rails
- Rail and main nav selection use a straight 4x20 inset bar in place of the bent stripe
- Rail rows are 32px with 8px side padding, and titles, labels and icons share one line
- Rail and dark main nav hover are neutral, so hover no longer reads as selected
- Toolbar and small buttons get wider side padding at 1366px and up; narrower windows keep the old padding
- Logs mode buttons have accessible names when icon-only, and pin budgets match rendered widths
This commit is contained in:
Kirtan Thakkar
2026-11-04 11:24:31 +05:30
parent f7fa93e0db
commit fbcf1fbefe
25 changed files with 117 additions and 105 deletions
+5 -7
View File
@@ -263,13 +263,11 @@ export default defineComponent({
const activePillClass = computed(() => {
if (slideActive.value) {
return isDark.value
? "text-tabs-active-text border-s-2 border-transparent"
: "text-accent border-s-2 border-transparent";
return isDark.value ? "text-tabs-active-text" : "text-accent";
}
return isDark.value
? "text-tabs-active-text bg-tabs-active-bg border-s-2 border-accent"
: "text-accent bg-surface-base border-s-2 border-accent";
? "text-tabs-active-text bg-tabs-active-bg"
: "text-accent bg-surface-base shadow-xs";
});
const activeIconClass = computed(() =>
isDark.value ? "text-tabs-active-text!" : "text-accent!",
@@ -396,10 +394,10 @@ export default defineComponent({
// Sit above the rail's sliding pill so icon/label stay readable.
slideActive.value ? "z-10" : "",
props.locked
? "cursor-pointer text-tabs-inactive-text border-s-2 border-transparent bg-transparent hover:bg-tabs-hover-bg"
? "cursor-pointer text-tabs-inactive-text bg-transparent hover:bg-tabs-hover-bg dark:hover:bg-tabs-rail-hover-bg"
: isActive.value
? activePillClass.value
: "text-tabs-inactive-text border-s-2 border-transparent bg-transparent hover:bg-tabs-hover-bg",
: "text-tabs-inactive-text bg-transparent hover:bg-tabs-hover-bg dark:hover:bg-tabs-rail-hover-bg",
isActive.value ? "nav-menu-item--active" : "",
props.title === "Functions" ? "menu-link-function" : "",
// Reset native <button> chrome so the trigger looks EXACTLY like a link.
@@ -25,7 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OTabs
:model-value="tabName(section)"
orientation="vertical"
class="w-full shrink-0 md:w-60"
class="w-full shrink-0 px-1 md:w-60"
data-test="autoobserve-policy-tabs"
@update:model-value="select"
>
+3 -3
View File
@@ -68,7 +68,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</OButton>
</div>
<div class="flex-1 overflow-y-auto px-1.5 pt-1 pb-3">
<div class="flex-1 overflow-y-auto px-1 pt-1 pb-3">
<OTabs
:model-value="activeKey ?? ''"
orientation="vertical"
@@ -78,11 +78,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template v-for="(group, idx) in visibleGroups" :key="group.label || idx">
<!-- Section label. Each group after the first gets top spacing so the
sub-sections read as separate blocks rather than one merged list. -->
<!-- ps-1.5 (on top of the container's px-1.5) puts the section label on
<!-- ps-2 (on top of the container's px-1) puts the section label on
the same 12px item-label grid line as the tabs below it. -->
<div
v-if="!collapsed && group.label"
class="text-text-secondary py-1 ps-1.5 text-xs font-semibold"
class="text-text-secondary py-1 ps-2 text-xs font-semibold"
:class="{ 'mt-3': idx > 0 }"
>
{{ group.label }}
@@ -74,7 +74,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
</div>
<!-- Search Input -->
<div class="px-1.5 pb-1.5" :class="mobileRail ? 'pt-1' : ''">
<div class="px-1 pb-1.5" :class="mobileRail ? 'pt-1' : ''">
<OSearchInput
v-model="searchQuery"
data-test="folder-search"
@@ -84,7 +84,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
</div>
<div class="folders-tabs flex-1 overflow-y-auto px-1.5">
<div class="folders-tabs flex-1 overflow-y-auto px-1">
<OTabs
orientation="vertical"
dense
+1 -1
View File
@@ -29,7 +29,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:aria-label="ariaLabel"
data-test="dbm-metrics-rail"
>
<div class="flex-1 overflow-y-auto px-1.5 pt-2 pb-3 max-md:pt-0 max-md:pb-0">
<div class="flex-1 overflow-y-auto px-1 pt-2 pb-3 max-md:pt-0 max-md:pb-0">
<OTabs
:model-value="activeKey ?? ''"
:orientation="isMobile ? 'horizontal' : 'vertical'"
+2 -2
View File
@@ -33,7 +33,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</OButton>
</div>
<div class="flex items-center gap-1.5 px-1.5 pb-1.5 max-md:pt-1.5">
<div class="flex items-center gap-1.5 px-1 pb-1.5 max-md:pt-1.5">
<OSearchInput
v-model="search"
class="w-full max-md:hidden"
@@ -60,7 +60,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
<div class="min-h-0 flex-1 overflow-y-auto px-1.5 max-md:hidden">
<div class="min-h-0 flex-1 overflow-y-auto px-1 max-md:hidden">
<OEmptyState
v-if="forbidden"
size="inline"
+2 -2
View File
@@ -27,7 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</span>
</div>
<div class="flex shrink-0 flex-col gap-1.5 px-1.5 pt-1 pb-1">
<div class="flex shrink-0 flex-col gap-1.5 px-1 pt-1 pb-1">
<OSearchInput
v-model="query"
clearable
@@ -58,7 +58,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</OToggleGroup>
</div>
<div class="flex-1 overflow-y-auto px-1.5 pt-1 pb-3">
<div class="flex-1 overflow-y-auto px-1 pt-1 pb-3">
<OTabs
orientation="vertical"
class="w-full"
@@ -35,7 +35,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
</div>
<div class="px-1.5 pb-1.5">
<div class="px-1 pb-1.5">
<OSearchInput
v-model="searchQuery"
:placeholder="t('synthetics.environments.searchPlaceholder')"
@@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
<div class="flex-1 overflow-y-auto px-1.5">
<div class="flex-1 overflow-y-auto px-1">
<OTabs
orientation="vertical"
dense
@@ -46,7 +46,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template v-slot:before>
<div class="h-full w-full ps-2.5 pt-2 pb-2.5">
<div class="h-full overflow-y-auto">
<OTabs v-model="billingtab" orientation="vertical">
<OTabs v-model="billingtab" orientation="vertical" class="px-1">
<ORouteTab
exact
name="plans"
@@ -97,7 +97,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:model-value="String(currentIndex)"
orientation="vertical"
dense
class="min-h-0 flex-1 overflow-y-auto px-1.5 py-1"
class="min-h-0 flex-1 overflow-y-auto px-1 py-1"
data-test="ai-queue-workbench-nav"
>
<OTab
+8 -5
View File
@@ -386,20 +386,23 @@ const unavailableClasses = computed(() => {
});
const sizeClasses: Record<NonNullable<ButtonProps["size"]>, string> = {
xs: "h-7 ps-2.5 pe-2.5 text-xs gap-1.5 rounded-default",
xs: "h-7 ps-3 pe-3 max-[85.5rem]:ps-2.5 max-[85.5rem]:pe-2.5 text-xs gap-1.5 rounded-default",
// 34px control height — the workhorse compact button that pairs with 34px
// inputs in toolbars/headers. (radius 8 = rounded-default.)
sm: "h-[2.125rem] ps-3 pe-3 text-sm gap-2 rounded-default",
sm: "h-[2.125rem] ps-3.5 pe-3.5 max-[85.5rem]:ps-3 max-[85.5rem]:pe-3 text-sm gap-2 rounded-default",
// Every toolbar control shares this box so a bar never mixes heights or padding.
"sm-toolbar": "h-8.5 ps-2.5 pe-2.5 text-xs gap-1.5 rounded-default",
"xs-grouped": "h-6.5 ps-2 pe-2 text-xs gap-1.5 rounded-default",
"sm-toolbar":
"h-8.5 ps-4 pe-4 max-[85.5rem]:ps-2.5 max-[85.5rem]:pe-2.5 text-xs gap-1.5 rounded-default",
"xs-grouped":
"h-6.5 ps-2.5 pe-2.5 max-[85.5rem]:ps-2 max-[85.5rem]:pe-2 text-xs gap-1.5 rounded-default",
// Compact labeled size for inline field chips (axis items) — ~28px, matches the dense button size
// Extra-compact chip size — 24px height for axis field chips in query builder
chip: "h-6 ps-2 pe-1.5 text-xs gap-1 rounded-default leading-none",
// Same as chip but with fixed 12px font — for dashboard query builder axis field chips
// (needed because the html font-size is 14px, making text-xs = 10.5px instead of 12px)
"chip-12": "h-6 ps-2 pe-1.5 !text-xs gap-1 rounded-default leading-none",
"sm-action": "h-[2.125rem] ps-3 pe-3 min-w-20 text-sm gap-2 rounded-default",
"sm-action":
"h-[2.125rem] ps-3.5 pe-3.5 max-[85.5rem]:ps-3 max-[85.5rem]:pe-3 min-w-20 text-sm gap-2 rounded-default",
// h-8.5 sorts before h-9 and up, so a caller's own plain height class still overrides md.
md: "h-8.5 ps-4 pe-4 text-sm gap-2 rounded-default",
cta: "h-10 ps-4 pe-4 text-sm gap-2 rounded-default",
+3 -2
View File
@@ -357,8 +357,9 @@ onBeforeUnmount(() => {
// tinted selected pill + lighter accent in dark, where a white pill would vanish
// on the near-black rail.
const indicatorClass = computed(() => [
"pointer-events-none absolute left-0 top-0 z-0 rounded-surface border-s-2",
"bg-surface-base border-accent dark:bg-tabs-active-bg dark:border-accent",
"pointer-events-none absolute left-0 top-0 z-0 rounded-surface",
"bg-surface-base shadow-xs dark:bg-tabs-active-bg dark:shadow-none",
"before:absolute before:top-2 before:h-5 before:start-0 before:w-1 before:rounded-full before:bg-accent",
transitionOn.value &&
"transition-[transform,width,height] duration-300 ease-out motion-reduce:transition-none",
indicatorVisible.value ? "opacity-100" : "opacity-0",
@@ -183,7 +183,7 @@ onBeforeUnmount(() => {
});
const indicatorClasses = computed(() => [
"pointer-events-none absolute left-0 top-0 rounded-default bg-toggle-item-active-bg",
"pointer-events-none absolute left-0 top-0 rounded-default bg-toggle-item-active-bg border border-toggle-item-active-border shadow-xs",
transitionOn.value &&
"transition-[transform,width,height] duration-300 ease-out motion-reduce:transition-none",
indicatorVisible.value ? "opacity-100" : "opacity-0",
@@ -7,7 +7,7 @@ import type { I18nText } from "@/types/i18n";
import type { AcceptableValue } from "reka-ui";
/** Size controls padding and font-size — height is determined by content */
export type ToggleGroupItemSize = "md" | "sm" | "xs";
export type ToggleGroupItemSize = "md" | "sm" | "toolbar" | "xs";
export interface ToggleGroupItemProps {
/** Unique value for this option — required */
@@ -76,6 +76,7 @@ const dropIndicatorClass = computed<string>(() => {
const sizeClasses: Record<NonNullable<ToggleGroupItemProps["size"]>, string> = {
md: "h-9 gap-2 px-3 text-sm max-md:px-2",
sm: "h-7 gap-1.5 px-2.5 text-xs max-md:px-1.5",
toolbar: "h-7 gap-1.5 px-3 text-xs max-[85.5rem]:px-2.5 max-md:px-1.5",
xs: "h-5 gap-2 px-1.5 text-xs max-md:px-1",
};
@@ -83,6 +84,7 @@ const sizeClasses: Record<NonNullable<ToggleGroupItemProps["size"]>, string> = {
const iconSize: Record<NonNullable<ToggleGroupItemProps["size"]>, "xs" | "sm" | "md"> = {
md: "md",
sm: "sm",
toolbar: "sm",
xs: "xs",
};
</script>
@@ -147,11 +149,12 @@ const iconSize: Record<NonNullable<ToggleGroupItemProps["size"]>, "xs" | "sm" |
'cursor-pointer outline-none',
// Hover (inactive only) — scoped to data-state=off so hovering the
// active item never repaints over the sliding indicator / active fill.
'data-[state=off]:hover:bg-toggle-item-hover-bg',
'data-[state=off]:hover:bg-toggle-item-hover-bg data-[state=off]:hover:text-text-heading',
// Active fill: normally painted per-item. When the group animates the
// selection (opt-in), a single sliding indicator paints the fill instead,
// so suppress the per-item background and just keep the active text colour.
!isAnimated && 'data-[state=on]:bg-toggle-item-active-bg',
!isAnimated &&
'data-[state=on]:bg-toggle-item-active-bg data-[state=on]:ring-toggle-item-active-border data-[state=on]:shadow-xs data-[state=on]:ring-1 data-[state=on]:ring-inset',
'data-[state=on]:text-toggle-item-active-text',
// Sit above the sliding indicator so the icon/label stay readable.
isAnimated && 'relative z-10',
@@ -194,7 +197,7 @@ const iconSize: Record<NonNullable<ToggleGroupItemProps["size"]>, "xs" | "sm" |
v-else-if="props.iconRight"
:name="props.iconRight"
:size="iconSize[props.size]"
:class="props.iconRight === 'lock' ? 'text-warning-600' : ''"
:class="props.iconRight === 'lock' ? 'text-text-secondary' : ''"
/>
</ToggleGroupItem>
<span v-if="unavailable && props.tooltip" :id="reasonId" aria-hidden="true" class="sr-only">{{
+2 -2
View File
@@ -225,9 +225,9 @@ describe("OTab", () => {
expect(classes).not.toContain("focus-visible:outline-offset-2");
});
it("uses a positive focus outline offset on a vertical tab", () => {
it("keeps the focus outline inside the rail inset on a vertical tab", () => {
const classes = mountTab({ isVertical: true }).find('[role="tab"]').classes();
expect(classes).toContain("focus-visible:outline-offset-2");
expect(classes).toContain("focus-visible:outline-offset-0");
expect(classes).not.toContain("focus-visible:-outline-offset-2");
});
+30 -34
View File
@@ -71,71 +71,65 @@ const baseClasses = computed<string>(() =>
// Named group so the grip (and consumer affordances like a rename pencil)
// can fade in on tab hover without colliding with consumer `group` usage.
"group/otab",
"relative items-center gap-1.5",
isVertical.value ? "flex justify-start" : "inline-flex justify-center",
// Horizontal inset. A vertical (side-rail) tab is a selectable PILL, so the
// rail container insets it (px-1.5) to give the pill breathing room from the
// rail edges. With that 6px container inset + the tab's own 2px active border
// + this ps-1, the label lands on the page-edge grid line (12px) while the
// pill never touches the rail edge. Rails add the px-1.5; tabs don't hand-roll
// their own padding override.
// Reorderable tabs widen the left inset to a 0.75rem gutter that hosts the
// hover-revealed drag grip. The gutter is tied to `reorderable` (not
// per-press state), so a tab is pixel-identical at rest, on hover, and
// while its label is being edited — the grip only ever changes opacity.
"relative items-center",
isVertical.value ? "flex justify-start gap-2" : "inline-flex justify-center gap-1.5",
// The rail's px-1 plus this px-2 puts a vertical tab's icon on the 12px page-edge grid line.
// Reorderable tabs keep a fixed 0.75rem gutter for the grip, so hovering or editing never shifts the label.
isReorderable.value
? "ps-3 pe-2"
: isVertical.value
? "ps-1 pe-2"
? "px-2"
: slots.trailing
? "ps-2 pe-1"
: "px-2",
"font-normal text-sm whitespace-nowrap",
isVertical.value && isActive.value ? "font-semibold" : "font-normal",
"text-sm whitespace-nowrap",
isVertical.value ? "rounded-default" : "rounded-t-default",
"outline-none transition-[color,background-color,border-color,text-decoration-color,fill,stroke] duration-(--duration-hover-out) hover:duration-(--duration-hover-in)",
"select-none",
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-focus-ring-accent",
// The horizontal strip scrolls on x, which clips y as well, so an outside outline would lose its bottom edge.
isVertical.value ? "focus-visible:outline-offset-2" : "focus-visible:-outline-offset-2",
isVertical.value ? "focus-visible:outline-offset-0" : "focus-visible:-outline-offset-2",
].join(" "),
);
const stateClasses = computed<string>(() => {
if (props.disable) {
return [
"text-tabs-disabled-text cursor-not-allowed opacity-60",
// Keep the transparent left border so disabled items don't shift width.
isVertical.value ? "border-s-2 border-transparent" : "",
].join(" ");
return "text-tabs-disabled-text cursor-not-allowed opacity-60";
}
if (isActive.value) {
// Horizontal tabs: colored text only. The active underline is a single
// shared bar in OTabs that slides between tabs, so each tab keeps a
// transparent 2px border (layout parity with inactive) rather than drawing
// its own colored one.
// Vertical tabs (side rail): tint bg + primary text + a colored left accent
// border that reads as the active rail marker.
return [
"text-tabs-active-text cursor-pointer",
isVertical.value
? "bg-tabs-active-bg border-s-2 border-tabs-indicator"
? "bg-tabs-active-bg before:bg-tabs-indicator before:absolute before:inset-y-1.5 before:start-0 before:w-1 before:rounded-full"
: "border-b-2 border-transparent",
].join(" ");
}
return [
"text-tabs-inactive-text cursor-pointer",
isVertical.value
? "enabled:hover:text-tabs-hover-text enabled:hover:bg-tabs-hover-bg"
: "enabled:hover:text-tabs-hover-text",
// Transparent border (left for vertical, bottom for horizontal) keeps inactive
// items the same size as the active one — no layout shift on activation.
isVertical.value ? "border-s-2 border-transparent" : "border-b-2 border-transparent",
].join(" ");
if (isVertical.value) {
return "text-text-body cursor-pointer enabled:hover:text-tabs-hover-text enabled:hover:bg-tabs-rail-hover-bg";
}
return "text-tabs-inactive-text cursor-pointer enabled:hover:text-tabs-hover-text border-b-2 border-transparent";
});
// A muted glyph lets the rail's labels lead; the active tab's icon inherits its accent text.
const iconToneClass = computed<string>(() =>
isVertical.value && !isActive.value && !props.disable
? "text-text-secondary group-hover/otab:text-tabs-hover-text"
: "",
);
// One padlock per locked row would make amber the rail's loudest colour.
const lockToneClass = computed<string>(() =>
isVertical.value ? "text-text-secondary" : "text-warning-600",
);
const heightClasses = computed<string>(() => {
if (isVertical.value) {
return isDense.value ? "py-1.5" : "py-2";
return "py-1.5";
}
return isDense.value ? "h-8" : "h-10";
});
@@ -220,11 +214,13 @@ const heightClasses = computed<string>(() => {
:name="icon as any"
size="sm"
class="o-tab__icon shrink-0"
:class="iconToneClass"
/>
<!-- Fallback: Material icon font glyph (legacy underscore names) -->
<span
v-else-if="icon"
class="o-tab__icon material-icons-outlined shrink-0 text-base leading-none"
:class="iconToneClass"
>{{ icon }}</span
>
</slot>
@@ -240,7 +236,7 @@ const heightClasses = computed<string>(() => {
:name="suffixIcon as any"
size="xs"
class="ms-auto shrink-0"
:class="suffixIcon === 'lock' ? 'text-warning-600' : ''"
:class="suffixIcon === 'lock' ? lockToneClass : ''"
/>
</template>
<slot v-else />
+1 -1
View File
@@ -362,7 +362,7 @@ const alignClasses: Record<NonNullable<OTabsProps["align"]>, string> = {
<div
ref="tablistRef"
:class="[
'o-tabs relative flex flex-col gap-0.5 p-1',
'o-tabs relative flex flex-col gap-0.5 py-1',
alignClasses[align],
{ 'border-card-glass-border border-b border-solid': bordered },
]"
+2 -2
View File
@@ -389,8 +389,8 @@ describe("the real token sheets", () => {
});
describe.each([
["light", 1086, 1074],
["dark", 1087, 1075],
["light", 1088, 1076],
["dark", 1089, 1077],
] as const)("%s theme", (theme, total, resolved) => {
const scope = THEMES[theme];
const names = colorTokens(scope);
+8 -4
View File
@@ -141,12 +141,13 @@
/* ToggleGroup */
--color-toggle-track-bg: var(--color-surface-subtle);
--color-toggle-item-bg: transparent;
--color-toggle-item-text: var(--color-text-heading);
--color-toggle-item-hover-bg: var(--color-primary-50);
--color-toggle-item-active-bg: var(--color-surface-accent-active);
--color-toggle-item-text: var(--color-text-body);
--color-toggle-item-hover-bg: var(--color-grey-150);
--color-toggle-item-active-bg: var(--color-surface-base);
--color-toggle-item-active-text: var(--color-primary-700);
--color-toggle-item-active-border: var(--color-primary-600);
--color-toggle-item-disabled: var(--color-text-disabled);
--color-toggle-border: var(--color-border-default);
--color-toggle-border: var(--color-border-subtle);
--color-toggle-focus-ring: var(--color-focus-ring-accent);
/* Insertion line shown while dragging to reorder (reorderable groups) */
--color-toggle-drop-indicator: var(--color-primary-600);
@@ -196,6 +197,7 @@
--color-tabs-inactive-text: var(--color-text-heading);
--color-tabs-hover-text: var(--color-text-heading);
--color-tabs-hover-bg: color-mix(in srgb, var(--color-primary-100) 50%, var(--color-primary-50));
--color-tabs-rail-hover-bg: var(--color-grey-150);
--color-tabs-disabled-text: var(--color-text-disabled);
--color-tabs-bar-border: var(--color-border-default);
--spacing-tabs-height: var(--spacing-10);
@@ -1468,6 +1470,7 @@
--color-toggle-item-hover-bg: var(--color-toggle-item-hover-bg);
--color-toggle-item-active-bg: var(--color-toggle-item-active-bg);
--color-toggle-item-active-text: var(--color-toggle-item-active-text);
--color-toggle-item-active-border: var(--color-toggle-item-active-border);
--color-toggle-item-disabled: var(--color-toggle-item-disabled);
--color-toggle-border: var(--color-toggle-border);
--color-toggle-focus-ring: var(--color-toggle-focus-ring);
@@ -1511,6 +1514,7 @@
--color-tabs-inactive-text: var(--color-tabs-inactive-text);
--color-tabs-hover-text: var(--color-tabs-hover-text);
--color-tabs-hover-bg: var(--color-tabs-hover-bg);
--color-tabs-rail-hover-bg: var(--color-tabs-rail-hover-bg);
--color-tabs-disabled-text: var(--color-tabs-disabled-text);
--color-tabs-bar-border: var(--color-tabs-bar-border);
--spacing-tabs-height: var(--spacing-tabs-height);
+5 -3
View File
@@ -233,16 +233,18 @@
--color-tooltip-arrow: var(--d-overlay);
/* ToggleGroup */
--color-toggle-item-hover-bg: var(--color-primary-950);
--color-toggle-item-hover-bg: var(--d-divider);
--color-toggle-item-active-bg: var(--color-primary-900);
--color-toggle-item-active-text: var(--color-white);
--color-toggle-item-active-border: var(--color-primary-400);
/* Accent lightens on dark so the insertion line stays visible on the track */
--color-toggle-drop-indicator: var(--color-primary-400);
/* Tabs */
--color-tabs-active-bg: var(--color-surface-accent-active);
--color-tabs-active-bg: var(--color-primary-900);
--color-tabs-active-text: var(--color-white);
--color-tabs-hover-bg: var(--color-primary-900);
--color-tabs-hover-bg: var(--d-hover);
--color-tabs-rail-hover-bg: var(--color-grey-800);
--color-tabs-bar-border: var(--color-grey-700);
--color-nav-label-accent: var(--color-accent);
@@ -259,8 +259,8 @@ describe("SearchBar — pinned saved views", () => {
// 93px = 1px border + 58px list trigger + 1px separator + 32px create button + 1px border.
it.each([
[680, false],
[681, true],
[426, false],
[427, true],
])(
"budgets the group at its rendered 93px width (toolbar %ipx → shown: %s)",
async (width, shown) => {
+22 -21
View File
@@ -64,8 +64,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
<OToggleGroupItem
data-test="logs-logs-toggle"
:aria-label="t('common.search')"
value="logs"
size="sm"
size="toolbar"
:tooltip="toolbarToggleIconOnly ? t('common.search') : undefined"
>
<template #icon-left>
@@ -90,7 +91,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
: undefined
"
value="visualize"
size="sm"
size="toolbar"
>
<template #icon-left>
<OIcon name="timeline" size="sm" class="shrink-0" />
@@ -100,18 +101,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OToggleGroupItem
data-test="logs-build-toggle"
:aria-label="t('search.buildQuery')"
value="build"
size="sm"
size="toolbar"
:tooltip="toolbarToggleIconOnly ? t('search.buildQuery') : undefined"
>
<template #icon-left>
<OIcon name="build" size="sm" class="shrink-0" />
<OIcon name="show-chart" size="sm" class="shrink-0" />
</template>
<span v-if="!toolbarToggleIconOnly">{{ t("search.buildQuery") }}</span>
</OToggleGroupItem>
<OToggleGroupItem
data-test="logs-drilldown-toggle"
:aria-label="t('search.drillDown')"
:disabled="searchObj.meta.sqlMode"
:tooltip="
searchObj.meta.sqlMode
@@ -121,7 +124,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
: undefined
"
value="drilldown"
size="sm"
size="toolbar"
>
<template #icon-left>
<OIcon name="query-stats" size="sm" class="shrink-0" />
@@ -131,20 +134,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OToggleGroupItem
data-test="logs-patterns-toggle"
:aria-label="t('search.showPatternsLabel')"
value="patterns"
size="sm"
:tooltip="
patternsAccess.allowed && toolbarToggleIconOnly
? t('search.showPatternsLabel')
: undefined
"
size="toolbar"
:tooltip="toolbarToggleIconOnly ? t('search.showPatternsLabel') : undefined"
>
<template #icon-left>
<OIcon name="layers" size="sm" class="shrink-0" />
</template>
<span v-if="!toolbarToggleIconOnly">{{ t("search.showPatternsLabel") }}</span>
<template v-if="!patternsAccess.allowed" #icon-right>
<OIcon name="lock" size="xs" class="text-warning-600 shrink-0" />
<OIcon name="lock" size="xs" class="text-text-secondary shrink-0" />
</template>
</OToggleGroupItem>
</OToggleGroup>
@@ -2424,7 +2424,8 @@ export default defineComponent({
// Approximate rendered widths (px) of each pinned control and of the fixed
// left-section content, used only to decide how many pinned items fit before
// they would clip. Hidden pinned items stay reachable inside the More menu.
const PIN_ITEM_WIDTH = { histogram: 72, sqlMode: 72, quickMode: 72, savedViews: 93 };
const PIN_BUTTON_WIDTH = computed(() => (windowWidth.value < 1366 ? 72 : 84));
const SAVED_VIEWS_PIN_WIDTH = 93;
const SYNTAX_GUIDE_LABEL_WIDTH = 108;
const SYNTAX_GUIDE_ICON_WIDTH = 40;
const PIN_ITEM_GAP = 8;
@@ -2434,10 +2435,10 @@ export default defineComponent({
const baseReservedWidth = computed(() => {
let w = 0;
if (toolbarToggleAsDropdown.value) w += 120;
else if (toolbarToggleIconOnly.value) w += 230;
else w += 440;
else if (toolbarToggleIconOnly.value) w += 186;
else w += 400;
if (!toolbarMoveResetToMenu.value) w += shouldHideToolbarButtonText.value ? 34 : 80;
w += 76; // More button (always visible)
w += windowWidth.value < 1366 ? 76 : 84; // More button (always visible)
w += 24; // inter-item gaps / padding buffer
return w;
});
@@ -2467,10 +2468,10 @@ export default defineComponent({
}
return false;
};
if (isPinned("histogram")) res.histogram = tryFit(PIN_ITEM_WIDTH.histogram);
if (isPinned("sqlMode")) res.sqlMode = tryFit(PIN_ITEM_WIDTH.sqlMode);
if (isPinned("quickMode")) res.quickMode = tryFit(PIN_ITEM_WIDTH.quickMode);
if (isPinned("savedViews")) res.savedViews = tryFit(PIN_ITEM_WIDTH.savedViews);
if (isPinned("histogram")) res.histogram = tryFit(PIN_BUTTON_WIDTH.value);
if (isPinned("sqlMode")) res.sqlMode = tryFit(PIN_BUTTON_WIDTH.value);
if (isPinned("quickMode")) res.quickMode = tryFit(PIN_BUTTON_WIDTH.value);
if (isPinned("savedViews")) res.savedViews = tryFit(SAVED_VIEWS_PIN_WIDTH);
if (isPinned("syntaxGuide")) {
if (tryFit(SYNTAX_GUIDE_LABEL_WIDTH)) {
res.syntaxGuide = true;
@@ -2507,7 +2508,7 @@ export default defineComponent({
},
]
: []),
{ value: "build", icon: "build", label: t("search.buildQuery"), disabled: false },
{ value: "build", icon: "show-chart", label: t("search.buildQuery"), disabled: false },
{
value: "drilldown",
icon: "query-stats",
@@ -33,7 +33,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OSplitter class="h-full" v-model="splitterModel" unit="px" disabled :horizontal="isMobile">
<template v-slot:before>
<div class="functions-tabs w-full">
<OTabs v-model="activeTab" :orientation="isMobile ? 'horizontal' : 'vertical'">
<OTabs
v-model="activeTab"
:orientation="isMobile ? 'horizontal' : 'vertical'"
:class="isMobile ? undefined : 'px-1'"
>
<OTab
name="generalSettings"
icon="settings"
@@ -118,7 +118,7 @@ const setOpen = (id: string, open: boolean) => {
/>
</div>
<div class="min-h-0 flex-1 overflow-y-auto px-1.5 pb-3">
<div class="min-h-0 flex-1 overflow-y-auto px-1 pb-3">
<OTabs
:model-value="active"
orientation="vertical"