mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
tabs new design (#1352)
Co-authored-by: sawka <mike@commandline.dev> Co-authored-by: Evan Simkowitz <esimkowitz@users.noreply.github.com>
This commit is contained in:
parent
90e31dfa48
commit
42cbbcdc2a
@ -51,63 +51,71 @@ const isPopoverContent = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Popover = memo(
|
const Popover = memo(
|
||||||
({ children, className, placement = "bottom-start", offset = 3, onDismiss, middleware }: PopoverProps) => {
|
forwardRef<HTMLDivElement, PopoverProps>(
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
({ children, className, placement = "bottom-start", offset = 3, onDismiss, middleware }, ref) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
setIsOpen(open);
|
setIsOpen(open);
|
||||||
if (!open && onDismiss) {
|
if (!open && onDismiss) {
|
||||||
onDismiss();
|
onDismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (offset === undefined) {
|
||||||
|
offset = 3;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (offset === undefined) {
|
middleware ??= [];
|
||||||
offset = 3;
|
middleware.push(offsetMiddleware(offset));
|
||||||
|
|
||||||
|
const { refs, floatingStyles, context } = useFloating({
|
||||||
|
placement,
|
||||||
|
open: isOpen,
|
||||||
|
onOpenChange: handleOpenChange,
|
||||||
|
middleware: middleware,
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const click = useClick(context);
|
||||||
|
const dismiss = useDismiss(context);
|
||||||
|
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);
|
||||||
|
|
||||||
|
const renderChildren = Children.map(children, (child) => {
|
||||||
|
if (isValidElement(child)) {
|
||||||
|
if (isPopoverButton(child)) {
|
||||||
|
return cloneElement(child as any, {
|
||||||
|
isActive: isOpen,
|
||||||
|
ref: refs.setReference,
|
||||||
|
getReferenceProps,
|
||||||
|
// Do not overwrite onClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPopoverContent(child)) {
|
||||||
|
return isOpen
|
||||||
|
? cloneElement(child as any, {
|
||||||
|
ref: refs.setFloating,
|
||||||
|
style: floatingStyles,
|
||||||
|
getFloatingProps,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={clsx("popover", className)}>
|
||||||
|
{renderChildren}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
)
|
||||||
middleware ??= [];
|
|
||||||
middleware.push(offsetMiddleware(offset));
|
|
||||||
|
|
||||||
const { refs, floatingStyles, context } = useFloating({
|
|
||||||
placement,
|
|
||||||
open: isOpen,
|
|
||||||
onOpenChange: handleOpenChange,
|
|
||||||
middleware: middleware,
|
|
||||||
whileElementsMounted: autoUpdate,
|
|
||||||
});
|
|
||||||
|
|
||||||
const click = useClick(context);
|
|
||||||
const dismiss = useDismiss(context);
|
|
||||||
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);
|
|
||||||
|
|
||||||
const renderChildren = Children.map(children, (child) => {
|
|
||||||
if (isValidElement(child)) {
|
|
||||||
if (isPopoverButton(child)) {
|
|
||||||
return cloneElement(child as any, {
|
|
||||||
isActive: isOpen,
|
|
||||||
ref: refs.setReference,
|
|
||||||
getReferenceProps,
|
|
||||||
// Do not overwrite onClick
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPopoverContent(child)) {
|
|
||||||
return isOpen
|
|
||||||
? cloneElement(child as any, {
|
|
||||||
ref: refs.setFloating,
|
|
||||||
style: floatingStyles,
|
|
||||||
getFloatingProps,
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div className={clsx("popover", className)}>{renderChildren}</div>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Popover.displayName = "Popover";
|
||||||
|
|
||||||
interface PopoverButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface PopoverButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -8,12 +8,13 @@ import "./windowdrag.scss";
|
|||||||
|
|
||||||
interface WindowDragProps {
|
interface WindowDragProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WindowDrag = forwardRef<HTMLDivElement, WindowDragProps>(({ children, className }, ref) => {
|
const WindowDrag = forwardRef<HTMLDivElement, WindowDragProps>(({ children, className, style }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={clsx(`window-drag`, className)}>
|
<div ref={ref} className={clsx(`window-drag`, className)} style={style}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -5,22 +5,44 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 130px;
|
width: 130px;
|
||||||
height: calc(100% - 1px);
|
height: calc(100% - 1px);
|
||||||
padding: 6px 3px 0px;
|
padding: 6px 0px 0px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
border-right: 1px solid rgb(from var(--main-text-color) r g b / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.tab-inner {
|
.tab-inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: calc(100% - 6px);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 0.5px solid rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
& + .tab::after,
|
||||||
|
&::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-inner {
|
||||||
|
border-color: transparent;
|
||||||
|
background: rgb(from var(--main-text-color) r g b / 0.07);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.animate {
|
&.animate {
|
||||||
transition:
|
transition:
|
||||||
transform 0.3s ease,
|
transform 0.3s ease,
|
||||||
@ -29,16 +51,25 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
.tab-inner {
|
.tab-inner {
|
||||||
border: 0.5px solid rgba(255, 255, 255, 0.2);
|
border-color: transparent;
|
||||||
background: radial-gradient(ellipse 92px 32px at bottom, rgba(118, 255, 53, 0.3) 0%, transparent 100%),
|
border-radius: 6px;
|
||||||
rgba(255, 255, 255, 0.2);
|
background: rgb(from var(--main-text-color) r g b / 0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&+.tab::after,
|
||||||
|
&::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:first-child::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -48,7 +79,7 @@
|
|||||||
z-index: var(--zindex-tab-name);
|
z-index: var(--zindex-tab-name);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
|
text-shadow: 0px 0px 4px rgb(from var(--main-bg-color) r g b / 0.25);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -56,7 +87,7 @@
|
|||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.179);
|
border: 1px solid rgb(from var(--main-text-color) r g b / 0.179);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
@ -84,7 +115,6 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
// background-color: var(--highlight-bg-color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,12 @@
|
|||||||
|
|
||||||
.dev-label,
|
.dev-label,
|
||||||
.app-menu-button {
|
.app-menu-button {
|
||||||
height: 100%;
|
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 2px 2px 0 0;
|
margin: 6px 6px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-menu-button {
|
.app-menu-button {
|
||||||
|
@ -104,8 +104,6 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
|
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
|
||||||
const [draggingTab, setDraggingTab] = useState<string>();
|
const [draggingTab, setDraggingTab] = useState<string>();
|
||||||
const [tabsLoaded, setTabsLoaded] = useState({});
|
const [tabsLoaded, setTabsLoaded] = useState({});
|
||||||
// const [scrollable, setScrollable] = useState(false);
|
|
||||||
// const [tabWidth, setTabWidth] = useState(TAB_DEFAULT_WIDTH);
|
|
||||||
const [newTabId, setNewTabId] = useState<string | null>(null);
|
const [newTabId, setNewTabId] = useState<string | null>(null);
|
||||||
|
|
||||||
const tabbarWrapperRef = useRef<HTMLDivElement>(null);
|
const tabbarWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
@ -126,6 +124,9 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
const osInstanceRef = useRef<OverlayScrollbars>(null);
|
const osInstanceRef = useRef<OverlayScrollbars>(null);
|
||||||
const draggerRightRef = useRef<HTMLDivElement>(null);
|
const draggerRightRef = useRef<HTMLDivElement>(null);
|
||||||
const draggerLeftRef = useRef<HTMLDivElement>(null);
|
const draggerLeftRef = useRef<HTMLDivElement>(null);
|
||||||
|
const workspaceSwitcherRef = useRef<HTMLDivElement>(null);
|
||||||
|
const devLabelRef = useRef<HTMLDivElement>(null);
|
||||||
|
const appMenuButtonRef = useRef<HTMLDivElement>(null);
|
||||||
const tabWidthRef = useRef<number>(TAB_DEFAULT_WIDTH);
|
const tabWidthRef = useRef<number>(TAB_DEFAULT_WIDTH);
|
||||||
const scrollableRef = useRef<boolean>(false);
|
const scrollableRef = useRef<boolean>(false);
|
||||||
const updateStatusButtonRef = useRef<HTMLButtonElement>(null);
|
const updateStatusButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
@ -185,33 +186,31 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
const addBtnWidth = addBtnRef.current.getBoundingClientRect().width;
|
const addBtnWidth = addBtnRef.current.getBoundingClientRect().width;
|
||||||
const updateStatusLabelWidth = updateStatusButtonRef.current?.getBoundingClientRect().width ?? 0;
|
const updateStatusLabelWidth = updateStatusButtonRef.current?.getBoundingClientRect().width ?? 0;
|
||||||
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
|
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
|
||||||
const spaceForTabs =
|
const appMenuButtonWidth = appMenuButtonRef.current?.getBoundingClientRect().width ?? 0;
|
||||||
tabbarWrapperWidth -
|
const workspaceSwitcherWidth = workspaceSwitcherRef.current?.getBoundingClientRect().width ?? 0;
|
||||||
(windowDragLeftWidth + DRAGGER_RIGHT_MIN_WIDTH + addBtnWidth + updateStatusLabelWidth + configErrorWidth);
|
const devLabelWidth = devLabelRef.current?.getBoundingClientRect().width ?? 0;
|
||||||
|
|
||||||
|
const nonTabElementsWidth =
|
||||||
|
windowDragLeftWidth +
|
||||||
|
DRAGGER_RIGHT_MIN_WIDTH +
|
||||||
|
addBtnWidth +
|
||||||
|
updateStatusLabelWidth +
|
||||||
|
configErrorWidth +
|
||||||
|
appMenuButtonWidth +
|
||||||
|
workspaceSwitcherWidth +
|
||||||
|
devLabelWidth;
|
||||||
|
const spaceForTabs = tabbarWrapperWidth - nonTabElementsWidth;
|
||||||
|
|
||||||
const numberOfTabs = tabIds.length;
|
const numberOfTabs = tabIds.length;
|
||||||
const totalDefaultTabWidth = numberOfTabs * TAB_DEFAULT_WIDTH;
|
|
||||||
const minTotalTabWidth = numberOfTabs * TAB_MIN_WIDTH;
|
|
||||||
const tabWidth = tabWidthRef.current;
|
|
||||||
const scrollable = scrollableRef.current;
|
|
||||||
let newTabWidth = tabWidth;
|
|
||||||
let newScrollable = scrollable;
|
|
||||||
|
|
||||||
if (spaceForTabs < totalDefaultTabWidth && spaceForTabs > minTotalTabWidth) {
|
// Compute the ideal width per tab by dividing the available space by the number of tabs
|
||||||
newTabWidth = TAB_MIN_WIDTH;
|
let idealTabWidth = spaceForTabs / numberOfTabs;
|
||||||
} else if (minTotalTabWidth > spaceForTabs) {
|
|
||||||
// Case where tabs cannot shrink further, make the tab bar scrollable
|
// Apply min/max constraints
|
||||||
newTabWidth = TAB_MIN_WIDTH;
|
idealTabWidth = Math.max(TAB_MIN_WIDTH, Math.min(idealTabWidth, TAB_DEFAULT_WIDTH));
|
||||||
newScrollable = true;
|
|
||||||
} else if (totalDefaultTabWidth > spaceForTabs) {
|
// Determine if the tab bar needs to be scrollable
|
||||||
// Case where resizing is needed due to limited container width
|
const newScrollable = idealTabWidth * numberOfTabs > spaceForTabs;
|
||||||
newTabWidth = spaceForTabs / numberOfTabs;
|
|
||||||
newScrollable = false;
|
|
||||||
} else {
|
|
||||||
// Case where tabs were previously shrunk or there is enough space for default width tabs
|
|
||||||
newTabWidth = TAB_DEFAULT_WIDTH;
|
|
||||||
newScrollable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the calculated width and position to all tabs
|
// Apply the calculated width and position to all tabs
|
||||||
tabRefs.current.forEach((ref, index) => {
|
tabRefs.current.forEach((ref, index) => {
|
||||||
@ -221,20 +220,22 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
} else {
|
} else {
|
||||||
ref.current.classList.remove("animate");
|
ref.current.classList.remove("animate");
|
||||||
}
|
}
|
||||||
ref.current.style.width = `${newTabWidth}px`;
|
ref.current.style.width = `${idealTabWidth}px`;
|
||||||
ref.current.style.transform = `translate3d(${index * newTabWidth}px,0,0)`;
|
ref.current.style.transform = `translate3d(${index * idealTabWidth}px,0,0)`;
|
||||||
ref.current.style.opacity = "1";
|
ref.current.style.opacity = "1";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the state with the new tab width if it has changed
|
// Update the state with the new tab width if it has changed
|
||||||
if (newTabWidth !== tabWidth) {
|
if (idealTabWidth !== tabWidthRef.current) {
|
||||||
tabWidthRef.current = newTabWidth;
|
tabWidthRef.current = idealTabWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the state with the new scrollable state if it has changed
|
// Update the state with the new scrollable state if it has changed
|
||||||
if (newScrollable !== scrollable) {
|
if (newScrollable !== scrollableRef.current) {
|
||||||
scrollableRef.current = newScrollable;
|
scrollableRef.current = newScrollable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize/destroy overlay scrollbars
|
// Initialize/destroy overlay scrollbars
|
||||||
if (newScrollable) {
|
if (newScrollable) {
|
||||||
osInstanceRef.current = OverlayScrollbars(tabBarRef.current, { ...(OS_OPTIONS as any) });
|
osInstanceRef.current = OverlayScrollbars(tabBarRef.current, { ...(OS_OPTIONS as any) });
|
||||||
@ -530,13 +531,13 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
|
|
||||||
const tabsWrapperWidth = tabIds.length * tabWidthRef.current;
|
const tabsWrapperWidth = tabIds.length * tabWidthRef.current;
|
||||||
const devLabel = isDev() ? (
|
const devLabel = isDev() ? (
|
||||||
<div className="dev-label">
|
<div ref={devLabelRef} className="dev-label">
|
||||||
<i className="fa fa-brands fa-dev fa-fw" />
|
<i className="fa fa-brands fa-dev fa-fw" />
|
||||||
</div>
|
</div>
|
||||||
) : undefined;
|
) : undefined;
|
||||||
const appMenuButton =
|
const appMenuButton =
|
||||||
PLATFORM !== "darwin" && !settings["window:showmenubar"] ? (
|
PLATFORM !== "darwin" && !settings["window:showmenubar"] ? (
|
||||||
<div className="app-menu-button" onClick={onEllipsisClick}>
|
<div ref={appMenuButtonRef} className="app-menu-button" onClick={onEllipsisClick}>
|
||||||
<i className="fa fa-ellipsis" />
|
<i className="fa fa-ellipsis" />
|
||||||
</div>
|
</div>
|
||||||
) : undefined;
|
) : undefined;
|
||||||
@ -545,7 +546,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
<WindowDrag ref={draggerLeftRef} className="left" />
|
<WindowDrag ref={draggerLeftRef} className="left" />
|
||||||
{appMenuButton}
|
{appMenuButton}
|
||||||
{devLabel}
|
{devLabel}
|
||||||
<WorkspaceSwitcher></WorkspaceSwitcher>
|
<WorkspaceSwitcher />
|
||||||
<div className="tab-bar" ref={tabBarRef} data-overlayscrollbars-initialize>
|
<div className="tab-bar" ref={tabBarRef} data-overlayscrollbars-initialize>
|
||||||
<div className="tabs-wrapper" ref={tabsWrapperRef} style={{ width: `${tabsWrapperWidth}px` }}>
|
<div className="tabs-wrapper" ref={tabsWrapperRef} style={{ width: `${tabsWrapperWidth}px` }}>
|
||||||
{tabIds.map((tabId, index) => {
|
{tabIds.map((tabId, index) => {
|
||||||
@ -572,7 +573,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
|
|||||||
<div ref={addBtnRef} className="add-tab-btn" onClick={handleAddTab}>
|
<div ref={addBtnRef} className="add-tab-btn" onClick={handleAddTab}>
|
||||||
<i className="fa fa-solid fa-plus fa-fw" />
|
<i className="fa fa-solid fa-plus fa-fw" />
|
||||||
</div>
|
</div>
|
||||||
<WindowDrag ref={draggerRightRef} className="right" />
|
<WindowDrag ref={draggerRightRef} className="right" style={{ minWidth: DRAGGER_RIGHT_MIN_WIDTH }} />
|
||||||
<UpdateStatusBanner buttonRef={updateStatusButtonRef} />
|
<UpdateStatusBanner buttonRef={updateStatusButtonRef} />
|
||||||
<ConfigErrorIcon buttonRef={configErrorButtonRef} />
|
<ConfigErrorIcon buttonRef={configErrorButtonRef} />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user