Merged PR 203: Improve responsive design and refactor layout
This commit is contained in:
parent
50626218ba
commit
0d05f0959c
27 changed files with 968 additions and 485 deletions
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue