Merged PR 203: Improve responsive design and refactor layout

This commit is contained in:
Eric Nguyen 2022-10-03 12:05:16 +00:00
parent 50626218ba
commit 0d05f0959c
27 changed files with 968 additions and 485 deletions

View file

@ -1,55 +0,0 @@
import * as React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { fireEvent, render, screen } from '../../utils/test-utils';
import { Sidebar } from './Sidebar';
describe.concurrent('Sidebar', () => {
it('Start default', () => {
render(
<Sidebar
selectedContainer={undefined}
componentOptions={[]}
isOpen={true}
buttonOnClick={() => { } } categories={[]}
/>
);
const stuff = screen.queryByText(/stuff/i);
expect(screen.getByText(/Components/i).classList.contains('left-0')).toBeDefined();
expect(stuff).toBeNull();
});
it('Start close', () => {
render(<Sidebar
componentOptions={[]}
isOpen={false}
buttonOnClick={() => { } } selectedContainer={undefined} categories={[]} />);
const stuff = screen.queryByText(/stuff/i);
expect(screen.getByText(/Components/i).classList.contains('-left-64')).toBeDefined();
expect(stuff).toBeNull();
});
it('With stuff', () => {
const type = 'stuff';
const handleButtonClick = vi.fn();
render(<Sidebar
componentOptions={[
{
/* eslint-disable @typescript-eslint/naming-convention */
Type: type,
Width: 30,
Height: 30,
Style: {}
/* eslint-enable */
}
]}
isOpen={true}
buttonOnClick={handleButtonClick} selectedContainer={undefined} categories={[]} />);
const stuff = screen.getByText(/stuff/i);
expect(stuff).toBeDefined();
fireEvent.click(stuff);
expect(handleButtonClick).toHaveBeenCalledTimes(1);
});
});

View file

@ -1,121 +1,25 @@
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { ICategory } from '../../Interfaces/ICategory';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { TruncateString } from '../../utils/stringtools';
import { Category } from '../Category/Category';
interface ISidebarProps {
selectedContainer: IContainerModel | undefined
componentOptions: IAvailableContainer[]
categories: ICategory[]
isOpen: boolean
buttonOnClick: (type: string) => void
className?: string
title: string
titleButtons?: JSX.Element | JSX.Element[]
children?: JSX.Element | JSX.Element[]
}
function HandleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id);
}
interface SidebarCategory {
category: ICategory
children: JSX.Element[]
}
export const TITLE_BAR_HEIGHT = 64;
export function Sidebar(props: ISidebarProps): JSX.Element {
const [hideDisabled, setHideDisabled] = React.useState<boolean>(false);
const rootElements: Array<JSX.Element | undefined> = [];
const categories = new Map<string, SidebarCategory>(props.categories.map(category => [
category.Type,
{
category,
children: []
}
]));
// build the categories (sorted with categories first)
categories.forEach((categoryWrapper, categoryName) => {
rootElements.push(
<Category
key={categoryName}
category={categoryWrapper.category}
>
{ categoryWrapper.children }
</Category>);
});
const selectedContainer = props.selectedContainer;
const config = props.componentOptions.find(option => option.Type === selectedContainer?.properties.type);
// build the components
props.componentOptions.forEach(componentOption => {
if (componentOption.IsHidden === true) {
return;
}
let disabled = false;
if (config?.Whitelist !== undefined) {
disabled = config.Whitelist?.find(type => type === componentOption.Type) === undefined;
} else if (config?.Blacklist !== undefined) {
disabled = config.Blacklist?.find(type => type === componentOption.Type) !== undefined ?? false;
}
if (disabled && hideDisabled) {
return;
}
const componentButton = (<button
key={componentOption.Type}
type="button"
className='w-full justify-center h-16 transition-all sidebar-component'
id={componentOption.Type}
title={componentOption.Type}
onClick={() => props.buttonOnClick(componentOption.Type)}
draggable={true}
onDragStart={(event) => HandleDragStart(event)}
disabled={disabled}
>
{TruncateString(componentOption.DisplayedText ?? componentOption.Type, 25)}
</button>);
if (componentOption.Category === null || componentOption.Category === undefined) {
rootElements.push(componentButton);
return;
}
const category = categories.get(componentOption.Category);
if (category === undefined) {
console.error(`[Category] Category does not exists in configuration.Categories: ${componentOption.Category}`);
return;
}
category.children.push(componentButton);
});
const isOpenClasses = props.isOpen ? 'left-16' : '-left-64';
return (
<div className={`fixed z-10 bg-slate-200
text-gray-700 transition-all h-full w-64
overflow-y-auto ${isOpenClasses}`}>
<div className={`transition-all bg-slate-200
text-gray-700 flex flex-col
${props.className ?? ''}`}>
<div className='bg-slate-100 sidebar-title flex place-content-between'>
Components
<button
onClick={() => { setHideDisabled(!hideDisabled); }}
className='h-6'
aria-label='Hide disabled component'
title='Hide disabled component'
>
{
hideDisabled
? <EyeSlashIcon className='heroicon' />
: <EyeIcon className='heroicon' />
}
</button>
{ props.title }
{ props.titleButtons }
</div>
<div className='transition-all grid grid-cols-1 md:grid-cols-1 gap-2
m-2 md:text-xs font-bold'>
{rootElements}
<div className='overflow-y-hidden grow'>
{ props.children }
</div>
</div>
);