Skip to content

Commit

Permalink
Refactored dock component.
Browse files Browse the repository at this point in the history
  • Loading branch information
hubuk committed Jul 30, 2022
1 parent b339a4f commit 1e16bf2
Show file tree
Hide file tree
Showing 17 changed files with 185 additions and 193 deletions.
86 changes: 11 additions & 75 deletions src/components/common/Dock/Dock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,22 @@
//
// @jsxImportSource @emotion/react

import React, { PropsWithChildren, ReactNode } from 'react';
import DockContainer from './DockContainer';
import DockItem, { DockItemProps } from './DockItem';
import { dockAttachedDelayProps, dockAttachedDirectionProps } from './types';
import React from 'react';
import DockManager, { DockedReactNode } from './DockManager';
import { DockDirection, dockDirectionPropName } from './types';

export default ({ children }: PropsWithChildren) => {
let topLineCount = 1;
let bottomLineCount = 1;
let leftLineCount = 1;
let rightLineCount = 1;
export default ({ children }: React.PropsWithChildren) => {
const dockedNodes: DockedReactNode[] = React.Children.toArray(children).filter(child => child).map(child => {
let dockDirection: DockDirection = 'Fill';

const mapChild = (child: ReactNode, index: number) => {
if (child && typeof child === 'object' && 'props' in child) {
let props: DockItemProps = {
rowStart: `top-${topLineCount}`,
rowEnd: `bottom-${bottomLineCount}`,
columnStart: `left-${leftLineCount}`,
columnEnd: `right-${rightLineCount}`,
dock: 'Fill',
};

dockAttachedDelayProps.filter(prop => prop in child.props).forEach(prop => {
props = { ...props, ...{ [prop.substring('dock-'.length)]: child.props[prop] } };
});

switch (dockAttachedDirectionProps.find(propName => propName in child.props)) {
case 'dock-top': {
++topLineCount;
props.rowEnd = `top-${topLineCount}`;
props.dock = 'Top';
break;
}

case 'dock-bottom': {
++bottomLineCount;
props.rowStart = `bottom-${bottomLineCount}`;
props.dock = 'Bottom';
break;
}

case 'dock-left': {
++leftLineCount;
props.columnEnd = `left-${leftLineCount}`;
props.dock = 'Left';
break;
}

case 'dock-right': {
++rightLineCount;
props.columnStart = `right-${rightLineCount}`;
props.dock = 'Right';
break;
}

case 'dock-fill': {
break;
}

default: {
return child;
}
}

const key = child.key ?? index.toString(36);
return <DockItem key={key} {...props}>{child}</DockItem>;
if (child && typeof child === 'object' && 'props' in child && dockDirectionPropName in child.props) {
dockDirection = child.props[dockDirectionPropName];
}

return child;
}

const wrappedChildren = React.Children.map(children, mapChild);

const topLines = [...Array(topLineCount + 1).keys()].slice(1).map(num => `[top-${num}]`);
const bottomLines = [...Array(bottomLineCount + 1).keys()].slice(1).reverse().map(num => `[bottom-${num}]`);
const leftLines = [...Array(leftLineCount + 1).keys()].slice(1).map(num => `[left-${num}]`);
const rightLines = [...Array(rightLineCount + 1).keys()].slice(1).reverse().map(num => `[right-${num}]`);
return [dockDirection, child];
});

return (
<DockContainer topLines={topLines} bottomLines={bottomLines} leftLines={leftLines} rightLines={rightLines}>
{wrappedChildren}
</DockContainer>
<DockManager dockedNodes={dockedNodes} />
);
};
4 changes: 2 additions & 2 deletions src/components/common/Dock/DockContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// @jsxImportSource @emotion/react

import { CSSObject } from '@emotion/react'
import { PropsWithChildren } from 'react'
import React from 'react'
import { Fill } from '../../../styles/layout';
import { mergeStyles } from '../../../styles/mergeStyles';

Expand All @@ -16,7 +16,7 @@ export interface DockContainerProps {
rightLines: string[],
}

export default ({ topLines, bottomLines, leftLines, rightLines, children }: PropsWithChildren<DockContainerProps>) => {
export default ({ topLines, bottomLines, leftLines, rightLines, children }: React.PropsWithChildren<DockContainerProps>) => {
const gridTemplateRows = `${topLines.join(' max-content ')} 1fr ${bottomLines.join(' max-content ')}`;
const gridTemplateColumns = `${leftLines.join(' max-content ')} 1fr ${rightLines.join(' max-content ')}`;

Expand Down
36 changes: 4 additions & 32 deletions src/components/common/Dock/DockItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
// @jsxImportSource @emotion/react

import { CSSObject } from '@emotion/react'
import { PropsWithChildren } from 'react'
import useThrottledState from '../../../hooks/useThrottledState'
import { mergeStyles } from '../../../styles/mergeStyles'
import React from 'react'
import { DockDirection } from './types'

export interface DockItemProps {
Expand All @@ -16,45 +14,19 @@ export interface DockItemProps {
rowStart: string,
rowEnd: string,
dock: DockDirection,
showDelay?: number,
hideDelay?: number,
}

export default ({ columnStart, columnEnd, rowStart, rowEnd, dock, showDelay, hideDelay, children }: PropsWithChildren<DockItemProps>) => {
const baseStyle: CSSObject = {
export default ({ columnStart, columnEnd, rowStart, rowEnd, dock, children }: React.PropsWithChildren<DockItemProps>) => {
const style: CSSObject = {
gridColumnStart: columnStart,
gridColumnEnd: columnEnd,
gridRowStart: rowStart,
gridRowEnd: rowEnd,
label: `Dock-${dock}`,
}

const autoHide = showDelay !== undefined || hideDelay !== undefined;
const showDelayWithDefault = showDelay ?? 0;
const hideDelayWithDefault = hideDelay ?? 0;

const calculateTimeout = (isCurrentlyHidden: boolean) => (isCurrentlyHidden ? showDelayWithDefault : hideDelayWithDefault);
const [isHidden, setIsHidden] = useThrottledState(autoHide, calculateTimeout);

let style = baseStyle;
if (autoHide && isHidden) {
if (dock === 'Top' || dock === 'Bottom') {
style = mergeStyles(baseStyle, { height: '10px' });
} else {
style = mergeStyles(baseStyle, { width: '10px' });
}
}

const onMouseEnter = () => {
setIsHidden(false);
};

const onMouseLeave = () => {
setIsHidden(true);
};

return (
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} css={style}>
<div css={style}>
{children}
</div>
);
Expand Down
92 changes: 92 additions & 0 deletions src/components/common/Dock/DockManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Hubert Bukowski. All rights reserved.
// Licensed under the MIT License.
// See LICENSE file in the project root for full license information.
//
// @jsxImportSource @emotion/react

import React from 'react'
import DockContainer from './DockContainer';
import DockItem, { DockItemProps } from './DockItem';
import { DockDirection } from './types';

export type DockedReactNode = [ DockDirection, React.ReactNode ];
export interface RelocationInfo {
index: number,
dock: DockDirection,
}

export interface DockManagerProps {
dockedNodes: DockedReactNode[],
}

export default ({ dockedNodes }: DockManagerProps) => {
let topLineCount = 1;
let bottomLineCount = 1;
let leftLineCount = 1;
let rightLineCount = 1;

const mapChild = (dockedNode: DockedReactNode, index: number) => {
const [dock, child] = dockedNode;

const props: DockItemProps = {
rowStart: `top-${topLineCount}`,
rowEnd: `bottom-${bottomLineCount}`,
columnStart: `left-${leftLineCount}`,
columnEnd: `right-${rightLineCount}`,
dock,
};

let key: React.Key = index.toString(36);

if (child && typeof child === 'object' && 'props' in child) {
switch (dock) {
case 'Top': {
++topLineCount;
props.rowEnd = `top-${topLineCount}`;
break;
}

case 'Bottom': {
++bottomLineCount;
props.rowStart = `bottom-${bottomLineCount}`;
break;
}

case 'Left': {
++leftLineCount;
props.columnEnd = `left-${leftLineCount}`;
break;
}

case 'Right': {
++rightLineCount;
props.columnStart = `right-${rightLineCount}`;
break;
}

default: {
break;
}
}

if (child.key) {
key = child.key;
}
}

return <DockItem key={key} {...props}>{child}</DockItem>
}

const wrappedChildren = dockedNodes.map(mapChild);

const topLines = [...Array(topLineCount + 1).keys()].slice(1).map(num => `[top-${num}]`);
const bottomLines = [...Array(bottomLineCount + 1).keys()].slice(1).reverse().map(num => `[bottom-${num}]`);
const leftLines = [...Array(leftLineCount + 1).keys()].slice(1).map(num => `[left-${num}]`);
const rightLines = [...Array(rightLineCount + 1).keys()].slice(1).reverse().map(num => `[right-${num}]`);

return (
<DockContainer topLines={topLines} bottomLines={bottomLines} leftLines={leftLines} rightLines={rightLines}>
{wrappedChildren}
</DockContainer>
);
}
4 changes: 2 additions & 2 deletions src/components/common/Dock/DockWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//
// @jsxImportSource @emotion/react

import { PropsWithChildren } from 'react';
import React from 'react';

export default ({ children }: PropsWithChildren) => (
export default ({ children }: React.PropsWithChildren) => (
<>{children}</>
);
2 changes: 1 addition & 1 deletion src/components/common/Dock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

export { default } from './Dock';
export { default as DockWrapper } from './DockWrapper';
export type { DockAttachedDirectionProps, DockAttachedDelayProps, DockAttachedProps } from './types';
export type { DockAttachedProps } from './types';
35 changes: 6 additions & 29 deletions src/components/common/Dock/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,16 @@

export type DockDirection = 'Top' | 'Bottom' | 'Left' | 'Right' | 'Fill';

export interface DockAttachedDirectionProps {
'dock-top'?: boolean,
'dock-bottom'?: boolean,
'dock-left'?: boolean,
'dock-right'?: boolean,
'dock-fill'?: boolean,
}

export interface DockAttachedDelayProps {
'dock-showDelay'?: number,
'dock-hideDelay'?: number,
}
export const dockDirectionPropName = 'dock-direction';

export interface DockAttachedProps extends DockAttachedDirectionProps, DockAttachedDelayProps {
export interface DockAttachedProps {
'dock-direction'?: DockDirection,
}

export type DockAttachedDirectionProp = keyof DockAttachedDirectionProps;
export type DockAttachedDelayProp = keyof DockAttachedDelayProps;
export type DockAttachedProp = keyof DockAttachedProps;

export const defaultDockAttachedDirectionProps: Required<DockAttachedDirectionProps> = {
'dock-top': false,
'dock-bottom': false,
'dock-left': false,
'dock-right': false,
'dock-fill': false,
}

export const dockAttachedDirectionProps = Object.getOwnPropertyNames(defaultDockAttachedDirectionProps) as DockAttachedDirectionProp[];

export const defaultDockAttachedDelayProps: Required<DockAttachedDelayProps> = {
'dock-showDelay': 0,
'dock-hideDelay': 0,
const fullDockAttachedProps: DockAttachedProps = {
'dock-direction': undefined,
}

export const dockAttachedDelayProps = Object.getOwnPropertyNames(defaultDockAttachedDelayProps) as DockAttachedDelayProp[];
export const dockAttachedProps = Object.getOwnPropertyNames(fullDockAttachedProps) as DockAttachedProps[];
4 changes: 2 additions & 2 deletions src/components/common/FullscreenViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// @jsxImportSource @emotion/react

import { PropsWithChildren } from 'react';
import React from 'react';
import { CSSObject } from '@emotion/react';
import { Fill, FullScreen } from '../../styles/layout';
import { mergeStyles } from '../../styles/mergeStyles';
Expand All @@ -20,7 +20,7 @@ const fitContentStyle: CSSObject = mergeStyles(Fill, {
minHeight: 'fit-content',
});

export default ({ children }: PropsWithChildren) => (
export default ({ children }: React.PropsWithChildren) => (
<div css={style}>
<div css={fitContentStyle}>
{children}
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/ModalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { CSSObject } from '@emotion/react';
import FocusTrap from 'focus-trap-react';
import { PropsWithChildren, useEffect } from 'react';
import React from 'react';
import { Fill } from '../../styles/layout';
import { mergeStyles } from '../../styles/mergeStyles';
import { BackgroundColor, DimerColor } from '../../styles/themes';
Expand Down Expand Up @@ -48,7 +48,7 @@ const contentStyle: CSSObject = {
margin: '0.5rem',
}

export default ({ onClose, children }: PropsWithChildren<ModalDialogProps>) => {
export default ({ onClose, children }: React.PropsWithChildren<ModalDialogProps>) => {
const closeOnEscapeKey = (e: KeyboardEvent) => {
if (e.key === 'Escape' && onClose) {
onClose();
Expand All @@ -64,7 +64,7 @@ export default ({ onClose, children }: PropsWithChildren<ModalDialogProps>) => {
window.removeEventListener('keydown', closeOnEscapeKey);
};

useEffect(() => {
React.useEffect(() => {
addListener();
return removeListener;
}, []);
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/ModalDialogLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
//
// @jsxImportSource @emotion/react

import { PropsWithChildren, ReactNode } from 'react';
import React from 'react';
import { Link, Outlet, Route, Routes } from 'react-router-dom';
import RoutedModalDialog from './RoutedModalDialog';

export interface ModalDialogLinkProps {
content: ReactNode,
content: React.ReactNode,
to: string,
}

export default ({ content, to, children }: PropsWithChildren<ModalDialogLinkProps>) => {
export default ({ content, to, children }: React.PropsWithChildren<ModalDialogLinkProps>) => {
const dialog = <RoutedModalDialog>{content}</RoutedModalDialog>;

return (
Expand Down
Loading

0 comments on commit 1e16bf2

Please sign in to comment.