Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IValueDisposable } from '../../../lifecycle';
import { DockviewHeaderDirection } from '../../options';
import { DockviewHeaderDirection, DockviewHeaderPosition } from '../../options';
import { Tab } from '../tab/tab';
import { ITabGroup } from '../../tabGroup';
import {
Expand All @@ -14,6 +14,7 @@ export interface TabGroupIndicatorContext {
getTabMap(): Map<string, IValueDisposable<Tab>>;
getChipElement(groupId: string): HTMLElement | undefined;
getDirection(): DockviewHeaderDirection;
getHeaderPosition(): DockviewHeaderPosition;
getColorPalette(): TabGroupColorPalette | undefined;
}

Expand Down Expand Up @@ -410,6 +411,7 @@ export class WrapTabGroupIndicator extends BaseTabGroupIndicator {

const r = 6; // corner radius
const half = t / 2;
const headerPosition = this._ctx.getHeaderPosition();

if (isVertical) {
const svgW = crossSize;
Expand All @@ -419,20 +421,23 @@ export class WrapTabGroupIndicator extends BaseTabGroupIndicator {
underline.style.width = `${svgW}px`;
underline.style.height = `${svgH}px`;

const xLeft = half;
const xRight = svgW - half;
// right header: indicator on the left edge (invert x sides)
const isRightHeader = headerPosition === 'right';
const xNear = isRightHeader ? svgW - half : half;
const xFar = isRightHeader ? half : svgW - half;
const cd = isRightHeader ? -1 : 1; // curve direction

const d = [
`M ${xLeft},0`,
`L ${xLeft},${aStart - r}`,
`Q ${xLeft},${aStart} ${xLeft + r},${aStart}`,
`L ${xRight - r},${aStart}`,
`Q ${xRight},${aStart} ${xRight},${aStart + r}`,
`L ${xRight},${aEnd - r}`,
`Q ${xRight},${aEnd} ${xRight - r},${aEnd}`,
`L ${xLeft + r},${aEnd}`,
`Q ${xLeft},${aEnd} ${xLeft},${aEnd + r}`,
`L ${xLeft},${svgH}`,
`M ${xNear},0`,
`L ${xNear},${aStart - r}`,
`Q ${xNear},${aStart} ${xNear + cd * r},${aStart}`,
`L ${xFar - cd * r},${aStart}`,
`Q ${xFar},${aStart} ${xFar},${aStart + r}`,
`L ${xFar},${aEnd - r}`,
`Q ${xFar},${aEnd} ${xFar - cd * r},${aEnd}`,
`L ${xNear + cd * r},${aEnd}`,
`Q ${xNear},${aEnd} ${xNear},${aEnd + r}`,
`L ${xNear},${svgH}`,
].join(' ');

path.setAttribute('d', d);
Expand All @@ -444,20 +449,23 @@ export class WrapTabGroupIndicator extends BaseTabGroupIndicator {
underline.style.width = `${svgW}px`;
underline.style.height = `${svgH}px`;

const yBot = svgH - half;
const yTop = half;
// bottom header: indicator on the top edge (invert y sides)
const isBottomHeader = headerPosition === 'bottom';
const yNear = isBottomHeader ? half : svgH - half;
const yFar = isBottomHeader ? svgH - half : half;
const cd = isBottomHeader ? 1 : -1; // curve direction

const d = [
`M 0,${yBot}`,
`L ${aStart - r},${yBot}`,
`Q ${aStart},${yBot} ${aStart},${yBot - r}`,
`L ${aStart},${yTop + r}`,
`Q ${aStart},${yTop} ${aStart + r},${yTop}`,
`L ${aEnd - r},${yTop}`,
`Q ${aEnd},${yTop} ${aEnd},${yTop + r}`,
`L ${aEnd},${yBot - r}`,
`Q ${aEnd},${yBot} ${aEnd + r},${yBot}`,
`L ${svgW},${yBot}`,
`M 0,${yNear}`,
`L ${aStart - r},${yNear}`,
`Q ${aStart},${yNear} ${aStart},${yNear + cd * r}`,
`L ${aStart},${yFar - cd * r}`,
`Q ${aStart},${yFar} ${aStart + r},${yFar}`,
`L ${aEnd - r},${yFar}`,
`Q ${aEnd},${yFar} ${aEnd},${yFar - cd * r}`,
`L ${aEnd},${yNear + cd * r}`,
`Q ${aEnd},${yNear} ${aEnd + r},${yNear}`,
`L ${svgW},${yNear}`,
].join(' ');

path.setAttribute('d', d);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export class TabGroupManager {
getChipElement: (id) =>
this._chipRenderers.get(id)?.chip.element,
getDirection: () => this._ctx.getDirection(),
getHeaderPosition: () => this._ctx.group.model.headerPosition,
getColorPalette: () => this._ctx.accessor.tabGroupColorPalette,
});
}
Expand Down
23 changes: 10 additions & 13 deletions packages/dockview-core/src/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@
&::after {
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
content: '';
width: 100%;
height: 1px;
Expand Down Expand Up @@ -478,7 +478,7 @@
&::after {
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
content: '';
width: 100%;
height: 2px;
Expand All @@ -497,7 +497,7 @@
&::after {
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
content: '';
width: 100%;
height: 2px;
Expand Down Expand Up @@ -1113,15 +1113,12 @@
}
}

// Tab group indicator 'none' mode — uses a JS-positioned continuous bar
// (NoneTabGroupIndicator) instead of per-tab box-shadows, so the indicator
// spans the full tab group width without gaps in spaced themes.
// The flat bar always sits at the bottom (or left for vertical) edge of
// the tab bar, so we undo the header-bottom override that moves the
// wrap indicator's full-height SVG to top: 0.
.dv-tab-group-indicator-none {
.dv-groupview-header-bottom .dv-tab-group-underline {
top: auto;
bottom: 0;
// Header-bottom: flip the active-tab indicator from bottom (content side when
// tabs are on top) to top (content side when tabs are on bottom).
.dv-groupview-header-bottom {
.dv-tabs-container > .dv-tab::after {
top: 0px;
bottom: auto;
}
}

52 changes: 40 additions & 12 deletions packages/docs/templates/dockview/tab-groups/react/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GetTabContextMenuItemsParams,
GetTabGroupChipContextMenuItemsParams,
themeAbyss,
DockviewHeaderPosition,
} from 'dockview-react';
import React from 'react';
import {
Expand All @@ -27,6 +28,18 @@ const components = {

export default () => {
const [api, setApi] = React.useState<DockviewApi>();
const [headerPosition, setHeaderPosition] =
React.useState<DockviewHeaderPosition>('top');

const onHeaderPositionChange = (
position: DockviewHeaderPosition
) => {
setHeaderPosition(position);
if (!api) return;
for (const group of api.groups) {
group.api.setHeaderPosition(position);
}
};

const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
Expand Down Expand Up @@ -154,18 +167,33 @@ export default () => {
);

return (
<div style={{ width: '100%', height: '100%' }}>
<DockviewReact
className={'dockview-theme-abyss'}
onReady={onReady}
components={components}
theme={{ ...themeAbyss, tabAnimation: 'smooth' }}
disableFloatingGroups={true}
getTabContextMenuItems={getTabContextMenuItems}
getTabGroupChipContextMenuItems={
getTabGroupChipContextMenuItems
}
/>
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '4px 8px' }}>
<span style={{ fontSize: '12px', color: 'var(--dv-paneview-header-border-color, #888)' }}>Tab position:</span>
{(['top', 'bottom'] as DockviewHeaderPosition[]).map((pos) => (
<button
key={pos}
onClick={() => onHeaderPositionChange(pos)}
disabled={headerPosition === pos}
>
{pos}
</button>
))}
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<DockviewReact
className={'dockview-theme-abyss'}
onReady={onReady}
components={components}
theme={{ ...themeAbyss, tabAnimation: 'smooth', tabGroupIndicator: 'wrap' }}
disableFloatingGroups={true}
getTabContextMenuItems={getTabContextMenuItems}
getTabGroupChipContextMenuItems={
getTabGroupChipContextMenuItems
}
/>
</div>
</div>
);
};

Loading