Skip to content

Commit

Permalink
Added StickyButtons to onRenderTabSet render values to allow for impl…
Browse files Browse the repository at this point in the history
…ementation of Chrome style + button
  • Loading branch information
nealus committed Jun 12, 2021
1 parent bb74567 commit 3196ded
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 25 deletions.
4 changes: 4 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
0.5.11
Added StickyButtons to onRenderTabSet render values to allow for implementation of Chrome style + button
Added example of + button to default layout in demo app

0.5.10
Adjust selected tab when tabs popped out to an external window

Expand Down
42 changes: 31 additions & 11 deletions examples/demo/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ class App extends React.Component<any, { layoutFile: string | null, model: FlexL
}
}

onAddFromTabSetButton = (node: TabSetNode | BorderNode) => {
// if (this.state.model!.getMaximizedTabset() == null) {
(this.refs.layout as FlexLayout.Layout).addTabToTabSet(node.getId(), {
component: "grid",
name: "Grid " + this.nextGridIndex++
});
// }
}

onAddIndirectClick = (event: React.MouseEvent) => {
if (this.state.model!.getMaximizedTabset() == null) {
Expand Down Expand Up @@ -235,7 +243,7 @@ class App extends React.Component<any, { layoutFile: string | null, model: FlexL
// return "(Added by titleFactory) " + node.getName();
return {
titleContent: <div>(Added by titleFactory) {node.getName()}</div>,
name: "the name for custom tab"
name: "the name for custom tab"
};
}
return;
Expand Down Expand Up @@ -273,17 +281,29 @@ class App extends React.Component<any, { layoutFile: string | null, model: FlexL
this.setState({ fontSize: target.value });
}

render() {
var onRenderTab = function (node: TabNode, renderValues: any) {
// renderValues.content += " *";
// renderValues.name = "tab " + node.getId(); // name used in overflow menu
// renderValues.buttons.push(<img src="images/grey_ball.png"/>);
};
onRenderTab = (node: TabNode, renderValues: any) => {
// renderValues.content += " *";
// renderValues.name = "tab " + node.getId(); // name used in overflow menu
// renderValues.buttons.push(<img src="images/grey_ball.png"/>);
}

var onRenderTabSet = function (node: (TabSetNode | BorderNode), renderValues: any) {
onRenderTabSet = (node: (TabSetNode | BorderNode), renderValues: any) => {
if (this.state.layoutFile === "default") {
//renderValues.headerContent = "-- " + renderValues.headerContent + " --";
//renderValues.buttons.push(<img src="images/grey_ball.png"/>);
};
renderValues.stickyButtons.push(
<img src="images/add.png"
alt="Add"
key="Add button"
title="Add Tab (using onRenderTabSet callback, see Demo)"
style={{ marginLeft: 5 }}
onClick={() => this.onAddFromTabSetButton(node)}
/>);
}
}

render() {


let contents: React.ReactNode = "loading ...";
let maximized = false;
Expand All @@ -297,8 +317,8 @@ class App extends React.Component<any, { layoutFile: string | null, model: FlexL
onAction={this.onAction}
titleFactory={this.titleFactory}
iconFactory={this.iconFactory}
onRenderTab={onRenderTab}
onRenderTabSet={onRenderTabSet}
onRenderTab={this.onRenderTab}
onRenderTabSet={this.onRenderTabSet}
onExternalDrag={this.onExternalDrag}
// classNameMapper={
// className => {
Expand Down
Binary file added examples/demo/images/add.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flexlayout-react",
"version": "0.5.10",
"version": "0.5.11",
"description": "A multi-tab docking layout manager",
"main": "lib/index.js",
"types": "./declarations/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ export enum CLASSES {
FLEXLAYOUT__TAB_TOOLBAR_BUTTON = "flexlayout__tab_toolbar_button",
FLEXLAYOUT__TAB_TOOLBAR_BUTTON_ = "flexlayout__tab_toolbar_button-",
FLEXLAYOUT__TAB_TOOLBAR_BUTTON_FLOAT = "flexlayout__tab_toolbar_button-float",
FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER = "flexlayout__tab_toolbar_sticky_buttons_container",
}
6 changes: 4 additions & 2 deletions src/view/BorderTabSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {

const toolbarRef = React.useRef<HTMLDivElement | null>(null);
const overflowbuttonRef = React.useRef<HTMLButtonElement | null>(null);
const stickyButtonsRef = React.useRef<HTMLDivElement | null>(null);

const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel } = useTabOverflow(border, Orientation.flip(border.getOrientation()), toolbarRef);
const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel } = useTabOverflow(border, Orientation.flip(border.getOrientation()), toolbarRef, stickyButtonsRef);

const onInterceptMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.MouseEvent<HTMLButtonElement, MouseEvent> | React.TouchEvent<HTMLButtonElement>) => {
event.stopPropagation();
Expand Down Expand Up @@ -84,7 +85,8 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {

// allow customization of tabset right/bottom buttons
let buttons: any[] = [];
const renderState = { headerContent: {}, buttons };
let stickyButtons: any[] = [];
const renderState = { headerContent: {}, buttons, stickyButtons };
layout.customizeTabSet(border, renderState);
buttons = renderState.buttons;

Expand Down
14 changes: 11 additions & 3 deletions src/view/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface ILayoutProps {
tabSetNode: TabSetNode | BorderNode,
renderValues: {
headerContent?: React.ReactNode;
stickyButtons: React.ReactNode[];
buttons: React.ReactNode[];
}
) => void;
Expand Down Expand Up @@ -120,6 +121,7 @@ export interface ILayoutCallbacks {
renderValues: {
headerContent?: React.ReactNode;
buttons: React.ReactNode[];
stickyButtons: React.ReactNode[];
}
): void;
styleFont: (style: Record<string, string>) => Record<string, string>;
Expand Down Expand Up @@ -195,7 +197,10 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
private popoutURL: string;
/** @hidden @internal */
private icons?: IIcons;
/** @hidden @internal */
private firstRender: boolean;
/** @hidden @internal */
private resizeObserver? : ResizeObserver;

constructor(props: ILayoutProps) {
super(props);
Expand Down Expand Up @@ -264,12 +269,14 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
// need to re-render if size changes
this.currentDocument = (this.selfRef.current as HTMLDivElement).ownerDocument;
this.currentWindow = this.currentDocument.defaultView!;
this.currentWindow!.addEventListener("resize", this.updateRect);
this.resizeObserver = new ResizeObserver(entries => {
this.updateRect();
});
this.resizeObserver.observe(this.selfRef.current!);
}

/** @hidden @internal */
componentDidUpdate() {
this.updateRect();
this.updateLayoutMetrics();
if (this.props.model !== this.previousModel) {
if (this.previousModel !== undefined) {
Expand Down Expand Up @@ -348,7 +355,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {

/** @hidden @internal */
componentWillUnmount() {
this.currentWindow!.removeEventListener("resize", this.updateRect);
this.resizeObserver?.unobserve(this.selfRef.current!)
}

/** @hidden @internal */
Expand Down Expand Up @@ -857,6 +864,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
renderValues: {
headerContent?: React.ReactNode;
buttons: React.ReactNode[];
stickyButtons: React.ReactNode[];
}
) {
if (this.props.onRenderTabSet) {
Expand Down
20 changes: 17 additions & 3 deletions src/view/TabOverflowHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import BorderNode from "../model/BorderNode";
import Orientation from "../Orientation";

/** @hidden @internal */
export const useTabOverflow = (node: TabSetNode | BorderNode, orientation: Orientation, toolbarRef: React.MutableRefObject<HTMLDivElement | null>) => {
export const useTabOverflow = (
node: TabSetNode | BorderNode,
orientation: Orientation,
toolbarRef: React.MutableRefObject<HTMLDivElement | null>,
stickyButtonsRef: React.MutableRefObject<HTMLDivElement | null>
) => {
const firstRender = React.useRef<boolean>(true);
const tabsTruncated = React.useRef<boolean>(false);
const lastRect = React.useRef<Rect>(new Rect(0, 0, 0, 0));
const selfRef = React.useRef<HTMLDivElement | null>(null);

Expand Down Expand Up @@ -63,8 +69,12 @@ export const useTabOverflow = (node: TabSetNode | BorderNode, orientation: Orien

const updateVisibleTabs = () => {
const tabMargin = 2;
if (firstRender.current === true ) {
tabsTruncated.current = false;
}
const nodeRect = node instanceof TabSetNode ? node.getRect() : (node as BorderNode).getTabHeaderRect()!;
let lastChild = node.getChildren()[node.getChildren().length - 1] as TabNode;
const stickyButtonsSize = stickyButtonsRef.current === null ? 0 : getSize(stickyButtonsRef.current!.getBoundingClientRect());

if (
firstRender.current === true ||
Expand All @@ -73,7 +83,7 @@ export const useTabOverflow = (node: TabSetNode | BorderNode, orientation: Orien
) {
lastRect.current = nodeRect;
const enabled = node instanceof TabSetNode ? node.isEnableTabStrip() === true : true;
let endPos = getFar(nodeRect) - getSize(toolbarRef.current!.getBoundingClientRect());
let endPos = getFar(nodeRect) - getSize(toolbarRef.current!.getBoundingClientRect()) - stickyButtonsSize;
if (enabled && node.getChildren().length > 0) {
if (hiddenTabs.length === 0 && position === 0 && getFar(lastChild.getTabRect()!) + tabMargin < endPos) {
return; // nothing to do all tabs are shown in available space
Expand Down Expand Up @@ -118,6 +128,10 @@ export const useTabOverflow = (node: TabSetNode | BorderNode, orientation: Orien
}
}

if (hidden.length > 0) {
tabsTruncated.current = true;
}

firstRender.current = false; // need to do a second render
setHiddenTabs(hidden);
setPosition(newPosition);
Expand All @@ -143,5 +157,5 @@ export const useTabOverflow = (node: TabSetNode | BorderNode, orientation: Orien
event.stopPropagation();
};

return { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel };
return { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated: tabsTruncated.current };
};
35 changes: 30 additions & 5 deletions src/view/TabSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export const TabSet = (props: ITabSetProps) => {
const toolbarRef = React.useRef<HTMLDivElement | null>(null);
const overflowbuttonRef = React.useRef<HTMLButtonElement | null>(null);
const tabbarInnerRef = React.useRef<HTMLDivElement | null>(null);
const stickyButtonsRef = React.useRef<HTMLDivElement | null>(null);

const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel } = useTabOverflow(node, Orientation.HORZ, toolbarRef);
const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated } = useTabOverflow(node, Orientation.HORZ, toolbarRef, stickyButtonsRef);

const onOverflowClick = () => {
const element = overflowbuttonRef.current!;
Expand All @@ -51,7 +52,7 @@ export const TabSet = (props: ITabSetProps) => {
layout.dragStart(event, message, node, node.isEnableDrag(), (event2: Event) => undefined, onDoubleClick);
};

const onInterceptMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.MouseEvent<HTMLButtonElement, MouseEvent> | React.TouchEvent<HTMLButtonElement>) => {
const onInterceptMouseDown = (event: React.MouseEvent | React.TouchEvent) => {
event.stopPropagation();
};

Expand Down Expand Up @@ -109,14 +110,33 @@ export const TabSet = (props: ITabSetProps) => {
}
}

let buttons: any[] = [];
let stickyButtons: React.ReactNode[] = [];
let buttons: React.ReactNode[] = [];

// allow customization of header contents and buttons
const renderState = { headerContent: node.getName(), buttons };
const renderState = { headerContent: node.getName(), stickyButtons, buttons };
layout.customizeTabSet(node, renderState);
const headerContent = renderState.headerContent;
stickyButtons = renderState.stickyButtons;
buttons = renderState.buttons;

if (stickyButtons.length > 0) {
if (tabsTruncated) {
buttons = [...stickyButtons, ...buttons];
} else {
tabs.push(<div
ref={stickyButtonsRef}
key="sticky_buttons_container"
onMouseDown={onInterceptMouseDown}
onTouchStart={onInterceptMouseDown}
onDragStart={(e) => { e.preventDefault() }}
className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER)}
>
{stickyButtons}
</div>);
}
}

let toolbar;
if (hiddenTabs.length > 0) {
const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
Expand Down Expand Up @@ -169,7 +189,12 @@ export const TabSet = (props: ITabSetProps) => {
}

toolbar = (
<div key="toolbar" ref={toolbarRef} className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR)} onMouseDown={onInterceptMouseDown}>
<div key="toolbar" ref={toolbarRef}
className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR)}
onMouseDown={onInterceptMouseDown}
onTouchStart={onInterceptMouseDown}
onDragStart={(e) => { e.preventDefault() }}
>
{buttons}
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions style/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@
background: transparent url("../images/popout.png") no-repeat center;
}
}

&_sticky_buttons_container {
display: flex;
align-items: center;
}
}

&_floating {
Expand Down
4 changes: 4 additions & 0 deletions style/dark.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions style/gray.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions style/light.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3196ded

Please sign in to comment.