Even better layouts

This commit is contained in:
Gregory Schier
2023-03-25 18:33:01 -07:00
parent 402b2a551f
commit 0d82cc7574
6 changed files with 47 additions and 31 deletions

View File

@@ -2,16 +2,25 @@ import classnames from 'classnames';
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useKeyValue } from '../hooks/useKeyValue'; import { useKeyValue } from '../hooks/useKeyValue';
import { clamp } from '../lib/clamp';
import { RequestPane } from './RequestPane'; import { RequestPane } from './RequestPane';
import { ResponsePane } from './ResponsePane'; import { ResponsePane } from './ResponsePane';
import { ResizeBar } from './Workspace'; import { ResizeBar } from './Workspace';
interface Props {
style: CSSProperties;
vertical?: boolean;
}
const rqst = { gridArea: 'rqst' }; const rqst = { gridArea: 'rqst' };
const resp = { gridArea: 'resp' }; const resp = { gridArea: 'resp' };
const drag = { gridArea: 'drag' }; const drag = { gridArea: 'drag' };
export default function RequestResponse() { const DEFAULT = 0.5;
const DEFAULT = 0.5; const MIN_WIDTH_PX = 10;
const MIN_HEIGHT_PX = 100;
export default function RequestResponse({ style, vertical }: Props) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const widthKv = useKeyValue<number>({ key: 'body_width', defaultValue: DEFAULT }); const widthKv = useKeyValue<number>({ key: 'body_width', defaultValue: DEFAULT });
const heightKv = useKeyValue<number>({ key: 'body_height', defaultValue: DEFAULT }); const heightKv = useKeyValue<number>({ key: 'body_height', defaultValue: DEFAULT });
@@ -22,14 +31,14 @@ export default function RequestResponse() {
null, null,
); );
const vertical = false;
const styles = useMemo<CSSProperties>( const styles = useMemo<CSSProperties>(
() => ({ () => ({
...style,
gridTemplate: vertical gridTemplate: vertical
? ` ? `
' ${rqst.gridArea}' ${1 - height}fr ' ${rqst.gridArea}' minmax(0,${1 - height}fr)
' ${drag.gridArea}' auto ' ${drag.gridArea}' auto
' ${resp.gridArea}' ${height}fr ' ${resp.gridArea}' minmax(0,${height}fr)
/ 1fr / 1fr
` `
: ` : `
@@ -37,7 +46,7 @@ export default function RequestResponse() {
/ ${1 - width}fr auto ${width}fr / ${1 - width}fr auto ${width}fr
`, `,
}), }),
[vertical, width, height], [vertical, width, height, style],
); );
const unsub = () => { const unsub = () => {
@@ -68,13 +77,21 @@ export default function RequestResponse() {
move: (e: MouseEvent) => { move: (e: MouseEvent) => {
e.preventDefault(); // Prevent text selection and things e.preventDefault(); // Prevent text selection and things
if (vertical) { if (vertical) {
const newHeightPx = startHeight - (e.clientY - mouseStartY); const maxHeightPx = containerRect.height - MIN_HEIGHT_PX;
const newHeight = newHeightPx / containerRect.height; const newHeightPx = clamp(
heightKv.set(newHeight); startHeight - (e.clientY - mouseStartY),
MIN_HEIGHT_PX,
maxHeightPx,
);
heightKv.set(newHeightPx / containerRect.height);
} else { } else {
const newWidthPx = startWidth - (e.clientX - mouseStartX); const maxWidthPx = containerRect.width - MIN_WIDTH_PX;
const newWidth = newWidthPx / containerRect.width; const newWidthPx = clamp(
widthKv.set(newWidth); startWidth - (e.clientX - mouseStartX),
MIN_WIDTH_PX,
maxWidthPx,
);
widthKv.set(newWidthPx / containerRect.width);
} }
}, },
up: (e: MouseEvent) => { up: (e: MouseEvent) => {
@@ -93,9 +110,9 @@ export default function RequestResponse() {
return ( return (
<div ref={containerRef} className="grid w-full h-full p-3" style={styles}> <div ref={containerRef} className="grid w-full h-full p-3" style={styles}>
<div style={rqst}> <div style={rqst}>
<RequestPane fullHeight /> <RequestPane fullHeight={!vertical} />
</div> </div>
<div style={drag} className={classnames('relative', vertical ? 'h-3' : 'w-3')}> <div style={drag} className={classnames('relative flex-grow-0', vertical ? 'h-3' : 'w-3')}>
<ResizeBar <ResizeBar
isResizing={isResizing} isResizing={isResizing}
onResizeStart={handleResizeStart} onResizeStart={handleResizeStart}

View File

@@ -11,6 +11,8 @@ const side = { gridArea: 'side' };
const head = { gridArea: 'head' }; const head = { gridArea: 'head' };
const body = { gridArea: 'body' }; const body = { gridArea: 'body' };
const FLOATING_SIDEBAR_BREAKPOINT = 960;
export default function Workspace() { export default function Workspace() {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const styles = useMemo<CSSProperties>( const styles = useMemo<CSSProperties>(
@@ -26,23 +28,17 @@ export default function Workspace() {
return ( return (
<div className="grid w-full h-full" style={styles}> <div className="grid w-full h-full" style={styles}>
<SidebarContainer style={side} floating={windowSize.width < 800}> <SidebarContainer style={side} floating={windowSize.width < FLOATING_SIDEBAR_BREAKPOINT}>
<Sidebar /> <Sidebar />
</SidebarContainer> </SidebarContainer>
<HeaderContainer> <HeaderContainer>
<WorkspaceHeader className="pointer-events-none" /> <WorkspaceHeader className="pointer-events-none" />
</HeaderContainer> </HeaderContainer>
<BodyContainer> <RequestResponse style={body} vertical={windowSize.width < FLOATING_SIDEBAR_BREAKPOINT} />
<RequestResponse />
</BodyContainer>
</div> </div>
); );
} }
const BodyContainer = memo(function BodyContainer({ children }: { children: ReactNode }) {
return <div style={body}>{children}</div>;
});
const HeaderContainer = memo(function HeaderContainer({ children }: { children: ReactNode }) { const HeaderContainer = memo(function HeaderContainer({ children }: { children: ReactNode }) {
return ( return (
<div <div

View File

@@ -57,7 +57,11 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
return ( return (
<Dropdown items={items}> <Dropdown items={items}>
<Button size="sm" className={classnames(className, '!px-2 truncate')} forDropdown> <Button
size="sm"
className={classnames(className, 'text-gray-800 !px-2 truncate')}
forDropdown
>
{activeWorkspace?.name} {activeWorkspace?.name}
</Button> </Button>
</Dropdown> </Dropdown>

View File

@@ -25,7 +25,7 @@ export function WorkspaceHeader({ className }: Props) {
/> />
<WorkspaceDropdown className="pointer-events-auto" /> <WorkspaceDropdown className="pointer-events-auto" />
</HStack> </HStack>
<div className="flex-[2] text-center text-gray-700 text-sm truncate pointer-events-none"> <div className="flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none">
{activeRequest?.name} {activeRequest?.name}
</div> </div>
<div className="flex-1 flex justify-end -mr-2 pointer-events-none"> <div className="flex-1 flex justify-end -mr-2 pointer-events-none">

View File

@@ -17,7 +17,7 @@
} }
.cm-line { .cm-line {
@apply text-gray-900 pl-1 pr-1.5; @apply text-gray-800 pl-1 pr-1.5;
} }
.cm-placeholder { .cm-placeholder {

View File

@@ -69,6 +69,9 @@ export function Tabs<T>({
<HStack space={1}> <HStack space={1}>
{tabs.map((t) => { {tabs.map((t) => {
const isActive = t.value === value; const isActive = t.value === value;
const btnClassName = classnames(
isActive ? 'bg-gray-100 text-gray-800' : 'text-gray-600 hover:text-gray-900',
);
if (t.options) { if (t.options) {
return ( return (
<RadioDropdown <RadioDropdown
@@ -81,9 +84,7 @@ export function Tabs<T>({
color="custom" color="custom"
size="sm" size="sm"
onClick={isActive ? undefined : () => handleTabChange(t.value)} onClick={isActive ? undefined : () => handleTabChange(t.value)}
className={classnames( className={btnClassName}
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
)}
> >
{t.options.items.find((i) => i.value === t.options?.value)?.label ?? ''} {t.options.items.find((i) => i.value === t.options?.value)?.label ?? ''}
<Icon icon="triangleDown" className="-mr-1.5" /> <Icon icon="triangleDown" className="-mr-1.5" />
@@ -97,9 +98,7 @@ export function Tabs<T>({
color="custom" color="custom"
size="sm" size="sm"
onClick={() => handleTabChange(t.value)} onClick={() => handleTabChange(t.value)}
className={classnames( className={btnClassName}
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
)}
> >
{t.label} {t.label}
</Button> </Button>