1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Improve dock tabs UX (#4754)

* Add separators and scroll button

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add tabs controlls

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Update values on resize

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix right button

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add change tab on keydown

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix flickering and arrows position

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix pr comments. Cleanup function. Simplify reaction

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add disposer cleanup

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add separators and scroll button

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add tabs controlls

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Update values on resize

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix right button

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add change tab on keydown

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix flickering and arrows position

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Fix pr comments. Cleanup function. Simplify reaction

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add disposer cleanup

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* PR fixes and improvements

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Add reaction cleanup

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Cleanup. Remove reaction.

Signed-off-by: DmitriyNoa <dmytro.zharkov@gmail.com>

* Active tab soft background

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Show close btn on hover

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Removing custom left/right arrow buttons

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Remove dock-tabs styles from dock.scss

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add dock-tabs.module.scss

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add useResizeObserver hook

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Set tabs scrollable on small window sizes

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add custom scrollbar on hover

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Update scrollbar overflow on tabs change

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding shadow corners to scrollable area

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Update material icons font

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Change terminal and chart install icons

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add hover tooltip

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Controls scrollable within Tabs

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Move tooltips to top

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Set dock tabs theme colors

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Mock ResizeObserver

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Increase tooltip show delay

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Scroll active tab into view

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Scroll horizontally with mouse wheel

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add tiny shadow to cropped tab

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Remove dock-tab.scss

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding tab role attributes

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Handle dock open/closed state

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Increase shadow corner size

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Selecting next or previous tab after closing selected one

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add tiny test

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Use scrollIntoViewIfNeeded in tabs

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Small cleaning

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Small fix for useResizeObserver deps array

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix plus button padding on empty dock

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix close button position in active tab

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Remove min-width for tab title

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Clean up

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Dmitriy Noa 2022-03-21 13:55:51 +01:00 committed by GitHub
parent a76fc6df84
commit 9f6c3e230a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 406 additions and 73 deletions

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import { DockStore, DockTab, TabKind } from "../dock/store";
import dockStoreInjectable from "../dock/store.injectable";
import fse from "fs-extra";
const initialTabs: DockTab[] = [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
{ id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource", pinned: false },
{ id: "edit", kind: TabKind.EDIT_RESOURCE, title: "Edit resource", pinned: false },
{ id: "install", kind: TabKind.INSTALL_CHART, title: "Install chart", pinned: false },
{ id: "logs", kind: TabKind.POD_LOGS, title: "Logs", pinned: false },
];
describe("DockStore", () => {
let dockStore: DockStore;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(
directoryForUserDataInjectable,
() => "some-test-suite-specific-directory-for-user-data",
);
await di.runSetups();
dockStore = di.inject(dockStoreInjectable);
await dockStore.whenReady;
});
afterEach(() => {
fse.remove("some-test-suite-specific-directory-for-user-data");
dockStore.closeAllTabs();
});
it("closes tab and selects one from right", () => {
dockStore.tabs = initialTabs;
dockStore.closeTab(dockStore.tabs[0].id);
expect(dockStore.selectedTabId).toBe("create");
dockStore.selectTab("edit");
dockStore.closeTab("edit");
expect(dockStore.selectedTabId).toBe("install");
});
it("closes last tab and selects one from right", () => {
dockStore.tabs = initialTabs;
dockStore.selectTab("logs");
dockStore.closeTab("logs");
expect(dockStore.selectedTabId).toBe("install");
});
it("closes tab and selects the last one", () => {
dockStore.tabs = [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
{ id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource", pinned: false },
];
dockStore.closeTab("terminal");
expect(dockStore.selectedTabId).toBe("create");
});
it("closes last tab and selects none", () => {
dockStore.tabs = [
{ id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource", pinned: false },
];
dockStore.closeTab("create");
expect(dockStore.selectedTabId).toBeUndefined();
});
it("doesn't change selected tab if other tab closed", () => {
dockStore.tabs = initialTabs;
dockStore.closeTab("install");
expect(dockStore.selectedTabId).toBe("terminal");
});
});

View File

@ -39,6 +39,14 @@ jest.mock("electron", () => ({
},
}));
Object.defineProperty(window, "ResizeObserver", {
writable: true,
value: jest.fn().mockImplementation(() => ({
observe: jest.fn(),
disconnect: jest.fn(),
})),
});
const initialTabs: DockTab[] = [
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
{ id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource", pinned: false },

View File

@ -0,0 +1,98 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.DockTab {
--color-active: var(--dockTabActiveBackground);
--color-text-active: var(--textColorAccent);
--color-border-active: var(--primary);
padding: var(--padding);
height: 32px;
position: relative;
border-right: 1px solid var(--dockTabBorderColor);
background-size: 1px 3ch;
overflow: hidden;
/* Allow tabs to shrink and take all parent space */
min-width: var(--min-tab-width);
flex-grow: 1;
flex-basis: 0;
max-width: fit-content;
&:last-child {
border-right: none;
}
&.pinned {
padding-right: var(--padding);
}
&:last-child {
padding-right: var(--padding);
}
&:global(.active) {
background-color: var(--color-active);
background-image: none;
border-bottom: 1px solid var(--color-border-active);
color: var(--color-text-active)!important;
.close {
opacity: 1;
}
&::before {
display: none;
}
}
&::before {
content: " ";
display: block;
position: absolute;
width: 8px;
height: 100%;
right: 0;
background: linear-gradient(90deg, transparent 0%, var(--dockHeadBackground) 65%);
}
&::after {
display: none;
}
&:not(:global(.active)):hover {
background-color: var(--dockTabActiveBackground);
background-image: none;
color: var(--textColorAccent);
.close {
opacity: 1;
background: linear-gradient(90deg, transparent 0%, var(--dockTabActiveBackground) 25%);
}
&::before {
display: none;
}
}
}
.close {
position: absolute;
right: 0px;
width: 5ch;
opacity: 0;
text-align: center;
background: linear-gradient(90deg, transparent 0%, var(--color-active) 25%);
}
.tabIcon {
opacity: 0;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
margin-right: 2.5rem;
}

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.DockTab {
padding: $padding;
padding-right: 0;
.Icon {
&.material {
--size: var(--small-size);
}
&.svg {
--size: 15px;
}
}
.label {
.title {
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
}
}
&.pinned {
padding-right: $padding;
}
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: $padding;
}
}

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./dock-tab.scss";
import styles from "./dock-tab.module.scss";
import React from "react";
import { observer } from "mobx-react";
@ -16,6 +16,7 @@ import { observable, makeObservable } from "mobx";
import { isMac } from "../../../common/vars";
import { withInjectables } from "@ogre-tools/injectable-react";
import dockStoreInjectable from "./dock/store.injectable";
import { Tooltip, TooltipPosition } from "../tooltip";
export interface DockTabProps extends TabProps<DockTabModel> {
moreActions?: React.ReactNode;
@ -76,19 +77,26 @@ class NonInjectedDockTab extends React.Component<DockTabProps & Dependencies> {
}
render() {
const { className, moreActions, dockStore, ...tabProps } = this.props;
const { className, moreActions, dockStore, active, ...tabProps } = this.props;
const { title, pinned } = tabProps.value;
const label = (
<div className="flex gaps align-center" onAuxClick={isMiddleClick(prevDefault(this.close))}>
<span className="title" title={title}>{title}</span>
<div className="flex align-center" onAuxClick={isMiddleClick(prevDefault(this.close))}>
<span className={styles.title}>{title}</span>
{moreActions}
{!pinned && (
<Icon
small material="close"
tooltip={`Close ${isMac ? "⌘+W" : "Ctrl+W"}`}
onClick={prevDefault(this.close)}
/>
<div className={styles.close}>
<Icon
small material="close"
tooltip={`Close ${isMac ? "⌘+W" : "Ctrl+W"}`}
onClick={prevDefault(this.close)}
/>
</div>
)}
<Tooltip
targetId={`tab-${this.tabId}`}
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
style={{ transitionDelay: "700ms" }}
>{title}</Tooltip>
</div>
);
@ -97,7 +105,9 @@ class NonInjectedDockTab extends React.Component<DockTabProps & Dependencies> {
<Tab
{...tabProps}
id={`tab-${this.tabId}`}
className={cssNames("DockTab", className, { pinned })}
className={cssNames(styles.DockTab, className, {
[styles.pinned]: pinned,
})}
onContextMenu={() => this.menuVisible = true}
label={label}
/>

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.dockTabs {
--min-tab-width: 120px;
overflow: hidden;
}
.tabs {
width: 100%;
display: flex;
overflow: hidden;
&:empty {
display: none;
}
&:global(.scrollable) {
overflow: auto;
overflow-x: overlay; /* Set scrollbar inside content area */
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar {
width: 100%;
height: 3px;
}
&::-webkit-scrollbar-thumb {
border-radius: 0;
height: 3px;
background-color: rgba(106, 115, 125, 0.2);
}
}
&::before, &::after {
content: "\00A0";
position: sticky;
min-width: 8px;
z-index: 1;
}
&::before {
left: 0;
background: linear-gradient(270deg, transparent 0%, var(--dockHeadBackground) 65%);
}
&::after {
right: 0;
background: linear-gradient(90deg, transparent 0%, var(--dockHeadBackground) 65%);
}
}
}

View File

@ -3,14 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React, { Fragment } from "react";
import styles from "./dock-tabs.module.scss";
import { Icon } from "../icon";
import React, { Fragment, useEffect, useRef, useState } from "react";
import { Tabs } from "../tabs/tabs";
import { DockTab } from "./dock-tab";
import type { DockTab as DockTabModel } from "./dock/store";
import { TabKind } from "./dock/store";
import { TerminalTab } from "./terminal/dock-tab";
import { useResizeObserver } from "../../hooks";
import { cssVar } from "../../utils";
export interface DockTabsProps {
tabs: DockTabModel[];
@ -20,6 +22,14 @@ export interface DockTabsProps {
}
export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabsProps) => {
const elem = useRef<HTMLDivElement>();
const minTabSize = useRef<number>(0);
const [showScrollbar, setShowScrollbar] = useState(false);
const getTabElements = (): HTMLDivElement[] => {
return Array.from(elem.current?.querySelectorAll(".Tabs .Tab"));
};
const renderTab = (tab?: DockTabModel) => {
if (!tab) {
return null;
@ -31,7 +41,7 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabs
return <DockTab value={tab} icon="edit" />;
case TabKind.INSTALL_CHART:
case TabKind.UPGRADE_CHART:
return <DockTab value={tab} icon={<Icon svg="install" />} />;
return <DockTab value={tab} icon="install_desktop" />;
case TabKind.POD_LOGS:
return <DockTab value={tab} icon="subject" />;
case TabKind.TERMINAL:
@ -39,14 +49,49 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabs
}
};
const scrollActiveTabIntoView = () => {
const tab = elem.current?.querySelector(".Tab.active");
tab?.scrollIntoView();
};
const updateScrollbarVisibility = () => {
const allTabsShrunk = getTabElements().every(tab => tab.offsetWidth == minTabSize.current);
setShowScrollbar(allTabsShrunk);
};
const scrollTabsWithMouseWheel = (left: number) => {
elem.current?.children[0]?.scrollBy({ left });
};
const onMouseWheel = (event: React.WheelEvent) => {
scrollTabsWithMouseWheel(event.deltaY);
};
useEffect(() => {
const cssVars = cssVar(elem.current);
minTabSize.current = cssVars.get("--min-tab-width").valueOf();
});
useResizeObserver(elem.current, () => {
scrollActiveTabIntoView();
updateScrollbarVisibility();
});
return (
<Tabs
className="DockTabs"
autoFocus={autoFocus}
value={selectedTab}
onChange={onChangeTab}
>
{tabs.map(tab => <Fragment key={tab.id}>{renderTab(tab)}</Fragment>)}
</Tabs>
<div className={styles.dockTabs} ref={elem} role="tablist">
<Tabs
autoFocus={autoFocus}
value={selectedTab}
onChange={onChangeTab}
onWheel={onMouseWheel}
scrollable={showScrollbar}
className={styles.tabs}
>
{tabs.map(tab => <Fragment key={tab.id}>{renderTab(tab)}</Fragment>)}
</Tabs>
</div>
);
};

View File

@ -34,7 +34,9 @@
height: auto !important;
.Tab {
--color-active: inherit;
--color-active: var(--colorVague);
--color-text-active: inherit;
--color-border-active: transparent;
&:not(:focus):after {
display: none;
@ -55,6 +57,10 @@
min-height: $unit * 4;
padding-left: $padding;
user-select: none;
&.pl-0 {
padding-left: 0;
}
}
}

View File

@ -4,11 +4,9 @@
*/
import "./dock.scss";
import React from "react";
import { observer } from "mobx-react";
import { cssNames, prevDefault } from "../../utils";
import { cssNames } from "../../utils";
import { Icon } from "../icon";
import { MenuItem } from "../menu";
import { MenuActions } from "../menu/menu-actions";
@ -37,6 +35,11 @@ interface Dependencies {
dockStore: DockStore;
}
enum Direction {
NEXT = 1,
PREV = -1,
}
@observer
class NonInjectedDock extends React.Component<DockProps & Dependencies> {
private element = React.createRef<HTMLDivElement>();
@ -52,6 +55,7 @@ class NonInjectedDock extends React.Component<DockProps & Dependencies> {
onKeyDown = (evt: KeyboardEvent) => {
const { close, selectedTab, closeTab } = this.props.dockStore;
const { code, ctrlKey, metaKey, shiftKey } = evt;
// Determine if user working inside <Dock/> or using any other areas in app
const dockIsFocused = this.element?.current.contains(document.activeElement);
@ -65,6 +69,14 @@ class NonInjectedDock extends React.Component<DockProps & Dependencies> {
closeTab(selectedTab.id);
this.element?.current.focus(); // Avoid loosing focus when closing tab
}
if(ctrlKey && code === "Period") {
this.switchToNextTab();
}
if(ctrlKey && code === "Comma") {
this.switchToNextTab(Direction.PREV);
}
};
onChangeTab = (tab: DockTab) => {
@ -75,6 +87,19 @@ class NonInjectedDock extends React.Component<DockProps & Dependencies> {
this.element?.current.focus();
};
switchToNextTab = (direction: Direction = Direction.NEXT) => {
const { tabs, selectedTab } = this.props.dockStore;
const currentIndex = tabs.indexOf(selectedTab);
const nextIndex = currentIndex + direction;
// check if moving to the next or previous tab is possible.
if (nextIndex >= tabs.length || nextIndex < 0) return;
const nextElement = tabs[nextIndex];
this.onChangeTab(nextElement);
};
renderTab(tab: DockTab) {
switch (tab.kind) {
case TabKind.CREATE_RESOURCE:
@ -125,14 +150,14 @@ class NonInjectedDock extends React.Component<DockProps & Dependencies> {
onMinExtentExceed={dockStore.open}
onDrag={extent => dockStore.height = extent}
/>
<div className="tabs-container flex align-center" onDoubleClick={prevDefault(toggle)}>
<div className="tabs-container flex align-center">
<DockTabs
tabs={tabs}
selectedTab={selectedTab}
autoFocus={isOpen}
onChangeTab={this.onChangeTab}
/>
<div className="toolbar flex gaps align-center box grow">
<div className={cssNames("toolbar flex gaps align-center box grow", { "pl-0": tabs.length == 0 })}>
<div className="dock-menu box grow">
<MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: "New tab" }} closeOnScroll={false}>
<MenuItem className="create-terminal-tab" onClick={() => this.props.createTerminalTab()}>

View File

@ -162,6 +162,10 @@ export class DockStore implements DockStorageState {
this.dependencies.storage.merge({ selectedTabId: tabId });
}
@computed get tabsNumber() : number {
return this.tabs.length;
}
@computed get selectedTab() {
return this.tabs.find(tab => tab.id === this.selectedTabId);
}
@ -323,6 +327,7 @@ export class DockStore implements DockStorageState {
@action
closeTab(tabId: TabId) {
const tab = this.getTabById(tabId);
const tabIndex = this.getTabIndex(tabId);
if (!tab || tab.pinned) {
return;
@ -333,7 +338,7 @@ export class DockStore implements DockStorageState {
if (this.selectedTabId === tab.id) {
if (this.tabs.length) {
const newTab = this.tabs.slice(-1)[0]; // last
const newTab = tabIndex < this.tabsNumber ? this.tabs[tabIndex] : this.tabs[tabIndex - 1];
this.selectTab(newTab.id);
} else {

View File

@ -46,7 +46,7 @@ class NonInjectedTerminalTab extends React.Component<TerminalTabProps & Dependen
}
render() {
const tabIcon = <Icon svg="terminal"/>;
const tabIcon = <Icon material="terminal"/>;
const className = cssNames("TerminalTab", this.props.className, {
disconnected: this.isDisconnected,
});

View File

@ -13,7 +13,6 @@
font-style: normal;
font-weight: 400;
font-display: block;
src: url("./fonts/MaterialIcons-Regular.woff2") format("woff");
src: url("./fonts/MaterialIcons-Regular.ttf") format("truetype");
}

View File

@ -81,10 +81,7 @@ export class Tab extends React.PureComponent<TabProps> {
}
scrollIntoView() {
this.ref.current?.scrollIntoView?.({
behavior: "smooth",
inline: "center",
});
this.ref.current?.scrollIntoViewIfNeeded();
}
@boundMethod
@ -137,6 +134,7 @@ export class Tab extends React.PureComponent<TabProps> {
onClick={this.onClick}
onFocus={this.onFocus}
onKeyDown={this.onKeyDown}
role="tab"
ref={this.ref}
>
{typeof icon === "string" ? <Icon small material={icon}/> : icon}

View File

@ -8,4 +8,5 @@
export * from "./useOnUnmount";
export * from "./useInterval";
export * from "./useMutationObserver";
export * from "./useResizeObserver";
export * from "./use-toggle";

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { useEffect } from "react";
export function useResizeObserver(
element: Element,
callback: ResizeObserverCallback,
) {
useEffect(() => {
if (element) {
const observer = new ResizeObserver(callback);
observer.observe(element);
return () => {
observer.disconnect();
};
}
return undefined;
}, [element, callback]);
}

View File

@ -73,6 +73,8 @@
"dockEditorComment": "#808080",
"dockEditorActiveLineBackground": "#3a3d41",
"dockBadgeBackground": "#36393e",
"dockTabBorderColor": "#43424d",
"dockTabActiveBackground": "#3a3e45",
"logsBackground": "#000000",
"logsForeground": "#ffffff",
"logRowHoverBackground": "#35373a",

View File

@ -73,6 +73,8 @@
"dockEditorComment": "#808080",
"dockEditorActiveLineBackground": "#3a3d41",
"dockBadgeBackground": "#dedede",
"dockTabBorderColor": "#d5d4de",
"dockTabActiveBackground": "#ffffff",
"logsBackground": "#24292e",
"logsForeground": "#ffffff",
"logRowHoverBackground": "#35373a",