Skip to content

Commit

Permalink
feat: editorTree & editorTabs support scroll into view (#275)
Browse files Browse the repository at this point in the history
* fix: collapse content no longer contains Scrollable in automatically

* fix: improve collapse content calculation

* feat: scrollable component support to get ref

* feat: editorTree & editorTab support to scroll into view

* fix: add data-* for entry in folderTree
  • Loading branch information
mortalYoung authored Jul 27, 2021
1 parent 58888d5 commit 07b9a0f
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 146 deletions.
87 changes: 49 additions & 38 deletions src/components/collapse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import {
collapseExtraClassName,
collapseContentClassName,
} from './base';
import { Scrollable } from '../scrollable';
import { select } from 'mo/common/dom';

type RenderFunctionProps = (data: DataBaseProps) => React.ReactNode;
interface DataBaseProps {
export interface DataBaseProps {
id: React.Key;
name: string;
className?: string;
Expand Down Expand Up @@ -50,15 +49,16 @@ export interface ICollapseProps {
}

// default collapse height, only contains header
const HEADER_HEIGTH = 26;
export const HEADER_HEIGTH = 26;
/**
* It's the max height for the item which set the grow to 0
*/
const MAX_GROW_HEIGHT = 220;
export const MAX_GROW_HEIGHT = 220;

export function Collapse(props: ICollapseProps) {
const [activePanelKeys, setActivePanelKeys] = useState<React.Key[]>([]);
const wrapper = React.useRef<HTMLDivElement>(null);
const requestAF = React.useRef<number>();

const {
className,
Expand Down Expand Up @@ -98,9 +98,14 @@ export function Collapse(props: ICollapseProps) {
const isActive = activePanelKeys.includes(panel.id);
let isEmpty = true;
if (isActive) {
const contentDom = select(
`.${collapseContentClassName}[data-content='${panel.id}']`
);
const contentDom =
select(
`.${collapseContentClassName}[data-content='${panel.id}']`
)?.querySelector(`[data-content='${panel.id}']`) ||
select(
`.${collapseContentClassName}[data-content='${panel.id}']`
);

isEmpty = !contentDom?.hasChildNodes();
}
panel._isEmpty = isEmpty;
Expand All @@ -117,21 +122,20 @@ export function Collapse(props: ICollapseProps) {
`.${collapseItemClassName}[data-content='${panel.id}']`
);

// Only set content height for non-grow-zero panel
// 'Cause when you set height for grow-zero panel, you'll get wrong height next render time
if (panel.config?.grow !== 0) {
const contentDom = select<HTMLElement>(
`.${collapseContentClassName}[data-content='${panel.id}']`
);
if (contentDom) {
contentDom.style.height = `${height - HEADER_HEIGTH - 2}px`;
}
}
if (dom) {
dom.style.height = `${height}px`;
dom.style.top = `${top}px`;
requestAF.current = requestAnimationFrame(() => {
dom.style.height = `${height}px`;
dom.style.top = `${top}px`;
});
}
});

return () => {
if (requestAF.current) {
cancelAnimationFrame(requestAF.current);
requestAF.current = undefined;
}
};
}, [filterData]);

const handleChangeCallback = (key: React.Key) => {
Expand Down Expand Up @@ -199,15 +203,22 @@ export function Collapse(props: ICollapseProps) {
const contentDom = select(
`.${collapseContentClassName}[data-content='${key}']`
);
if (contentDom) {
// border-top-width + border-bottom-width = 2
const basisHeight =
contentDom.getBoundingClientRect().height -
2 +
HEADER_HEIGTH;
return basisHeight > 220 ? 220 : basisHeight;

const childrenDom = contentDom?.querySelector(
`[data-content='${key}']`
);

let contentHeight = contentDom?.getBoundingClientRect().height || 0;

if (childrenDom) {
contentHeight = childrenDom.getBoundingClientRect().height;
}
return 0;

// border-top-width + border-bottom-width = 2
const height =
parseInt(contentHeight.toFixed(0)) - 2 + HEADER_HEIGTH;

return height > MAX_GROW_HEIGHT ? MAX_GROW_HEIGHT : height;
});
};

Expand Down Expand Up @@ -235,10 +246,12 @@ export function Collapse(props: ICollapseProps) {
// to get current panel content
const contentDom = select(
`.${collapseContentClassName}[data-content='${panel.id}']`
);
)?.querySelector(`[data-content='${panel.id}']`);

if (contentDom) {
const height =
contentDom.getBoundingClientRect().height +
2 +
HEADER_HEIGTH;
res[0] =
height > MAX_GROW_HEIGHT ? MAX_GROW_HEIGHT : height;
Expand Down Expand Up @@ -282,7 +295,7 @@ export function Collapse(props: ICollapseProps) {
// In general, the following code will not be excuted
const contentDom = select(
`.${collapseContentClassName}[data-content='${panel.id}']`
);
)?.querySelector(`[data-content='${panel.id}']`);
return contentDom?.hasChildNodes();
}
return false;
Expand Down Expand Up @@ -375,15 +388,13 @@ export function Collapse(props: ICollapseProps) {
)}
</div>
</div>
<Scrollable noScrollX isShowShadow>
<div
className={collapseContentClassName}
data-content={panel.id}
tabIndex={0}
>
{renderPanels(panel, panel.renderPanel)}
</div>
</Scrollable>
<div
className={collapseContentClassName}
data-content={panel.id}
tabIndex={0}
>
{renderPanels(panel, panel.renderPanel)}
</div>
</div>
);
})}
Expand Down
8 changes: 4 additions & 4 deletions src/components/collapse/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ $collapse__extra: #{$collapse}__extra;
}
}

&--active {
overflow: auto;
}

&__header {
align-items: center;
border: 1px solid transparent;
Expand Down Expand Up @@ -74,6 +70,10 @@ $collapse__extra: #{$collapse}__extra;
border: 1px solid transparent;
width: calc(100% - 3px);

&:not(:empty) {
flex: 1;
}

&:focus {
border-color: var(--list-focusOutline);
}
Expand Down
11 changes: 9 additions & 2 deletions src/components/scrollable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ const defaultSrollableClassName = prefixClaName('scrollbar');
* the below implementation from this issue:
* https://github.com/xobotyi/react-scrollbars-custom/issues/46
*/
export function Scrollable(props: IScrollbarProps) {
const Scrollable = React.forwardRef<Scrollbar, IScrollbarProps>(function (
props,
ref
) {
const {
className,
children,
Expand All @@ -26,6 +29,8 @@ export function Scrollable(props: IScrollbarProps) {
} = props;
const scroller = React.useRef<Scrollbar>(null);

React.useImperativeHandle(ref, () => scroller.current!);

const [isScrolling, setIsScrolling] = useState(false);
const [isMouseOver, setIsMouseOver] = useState(false);
const isShow = isScrolling || isMouseOver;
Expand Down Expand Up @@ -105,4 +110,6 @@ export function Scrollable(props: IScrollbarProps) {
{children}
</Scrollbar>
);
}
});

export { Scrollable };
6 changes: 5 additions & 1 deletion src/components/tabs/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export interface ITabProps<T = any, P = any> extends ITabEvent {

export const tabClassName = prefixClaName('tab');
export const tabItemClassName = getBEMElement(tabClassName, 'item');
export const tabItemActiveClassName = getBEMModifier(
tabItemClassName,
'active'
);

export function Tab<T>(props: ITabProps) {
const {
Expand Down Expand Up @@ -115,7 +119,7 @@ export function Tab<T>(props: ITabProps) {
<div
ref={ref}
className={classNames(tabItemClassName, {
[getBEMModifier(tabItemClassName, 'active')]: active,
[tabItemActiveClassName]: active,
})}
onClick={(event: React.MouseEvent) => onSelectTab?.(id)}
onMouseOver={handleMouseOver}
Expand Down
3 changes: 2 additions & 1 deletion src/controller/explorer/editorTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ export class EditorTreeController

this.explorerService.addPanel({
...restEditor,
renderPanel: () => (
renderPanel: (panel) => (
<EditorTreeView
panel={panel}
contextMenu={contextMenu}
headerContextMenu={headerContextMenu}
groupToolbar={groupToolbar}
Expand Down
4 changes: 2 additions & 2 deletions src/controller/explorer/explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ export class ExplorerController
}
};

public renderFolderTree = () => {
return <FolderTreeView />;
public renderFolderTree = (panel) => {
return <FolderTreeView panel={panel} />;
};
}

Expand Down
28 changes: 26 additions & 2 deletions src/workbench/editor/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { IEditorController } from 'mo/controller/editor';
import { Menu } from 'mo/components/menu';
import { useContextView } from 'mo/components/contextView';
import { getEventPosition } from 'mo/common/dom';
import Scrollbar from 'react-scrollbars-custom';
import { tabItemActiveClassName } from 'mo/components/tabs/tab';

export interface IEditorGroupProps extends IEditorGroup {
currentGroup?: IEditorGroup;
Expand All @@ -38,6 +40,9 @@ export function EditorGroup(props: IEditorGroupProps & IEditorController) {
onUpdateEditorIns,
} = props;

const scrollable = React.useRef<Scrollbar>(null);
const groupTabs = React.useRef<HTMLDivElement>(null);

const isActiveGroup = id === currentGroup?.id;

const contextView = useContextView();
Expand All @@ -58,11 +63,30 @@ export function EditorGroup(props: IEditorGroupProps & IEditorController) {
contextView?.dispose();
};
});

// scoller into view
React.useLayoutEffect(() => {
const activeItem = groupTabs.current?.querySelector<HTMLDivElement>(
`.${tabItemActiveClassName}`
);
if (activeItem) {
const width = groupTabs.current?.clientWidth || 0;
const left = activeItem.offsetLeft;
if (left > width) {
scrollable.current?.scrollTo(left, 0);
}
}
}, [currentGroup?.id && currentGroup.tab?.id]);

return (
<div className={groupClassName}>
<div className={groupHeaderClassName}>
<div className={groupTabsClassName}>
<Scrollable noScrollY trackStyle={{ height: 3 }}>
<div className={groupTabsClassName} ref={groupTabs}>
<Scrollable
noScrollY
trackStyle={{ height: 3 }}
ref={scrollable}
>
<Tabs
editable={true}
type="card"
Expand Down
Loading

0 comments on commit 07b9a0f

Please sign in to comment.