Container queries!

This commit is contained in:
Gregory Schier
2023-03-20 01:08:41 -07:00
parent f4600f3e90
commit 5923399359
14 changed files with 212 additions and 245 deletions

View File

@@ -42,10 +42,6 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
[activeResponse],
);
if (activeResponse === null) {
return null;
}
return (
<div className={classnames(className, 'p-3')}>
<div
@@ -61,57 +57,59 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
alignItems="center"
className="italic text-gray-700 text-sm w-full mb-1 flex-shrink-0 pl-2"
>
{activeResponse && activeResponse.status > 0 && (
<div className="whitespace-nowrap">
<StatusColor statusCode={activeResponse.status}>
{activeResponse.status}
{activeResponse.statusReason && ` ${activeResponse.statusReason}`}
</StatusColor>
&nbsp;&bull;&nbsp;
{activeResponse.elapsed}ms &nbsp;&bull;&nbsp;
{Math.round(activeResponse.body.length / 1000)} KB
</div>
)}
{activeResponse && (
<>
<div className="whitespace-nowrap">
<StatusColor statusCode={activeResponse.status}>
{activeResponse.status}
{activeResponse.statusReason && ` ${activeResponse.statusReason}`}
</StatusColor>
&nbsp;&bull;&nbsp;
{activeResponse.elapsed}ms &nbsp;&bull;&nbsp;
{Math.round(activeResponse.body.length / 1000)} KB
</div>
<HStack alignItems="center" className="ml-auto h-8">
<Dropdown
items={[
{
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
onSelect: toggleViewMode,
},
'-----',
{
label: 'Clear Response',
onSelect: deleteResponse.mutate,
disabled: responses.length === 0,
},
{
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
onSelect: deleteAllResponses.mutate,
disabled: responses.length === 0,
},
'-----',
...responses.slice(0, 10).map((r) => ({
label: r.status + ' - ' + r.elapsed + ' ms',
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
onSelect: () => setActiveResponseId(r.id),
})),
]}
>
<DropdownMenuTrigger>
<IconButton
title="Show response history"
icon="triangleDown"
className="ml-auto"
size="sm"
/>
</DropdownMenuTrigger>
</Dropdown>
</HStack>
<HStack alignItems="center" className="ml-auto h-8">
<Dropdown
items={[
{
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
onSelect: toggleViewMode,
},
'-----',
{
label: 'Clear Response',
onSelect: deleteResponse.mutate,
disabled: responses.length === 0,
},
{
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
onSelect: deleteAllResponses.mutate,
disabled: responses.length === 0,
},
'-----',
...responses.slice(0, 10).map((r) => ({
label: r.status + ' - ' + r.elapsed + ' ms',
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
onSelect: () => setActiveResponseId(r.id),
})),
]}
>
<DropdownMenuTrigger>
<IconButton
title="Show response history"
icon="triangleDown"
className="ml-auto"
size="sm"
/>
</DropdownMenuTrigger>
</Dropdown>
</HStack>
</>
)}
</HStack>
{activeResponse?.error ? (
{!activeResponse ? null : activeResponse?.error ? (
<div className="p-1">
<div className="text-white bg-red-500 px-3 py-3 rounded">{activeResponse.error}</div>
</div>

View File

@@ -12,11 +12,10 @@ import { getEmptyImage } from 'react-dnd-html5-backend';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRequests } from '../hooks/useRequests';
import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { clamp } from '../lib/clamp';
import type { HttpRequest } from '../lib/models';
import { Button } from './core/Button';
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
@@ -32,21 +31,17 @@ interface Props {
className?: string;
}
const MIN_WIDTH = 110;
const INITIAL_WIDTH = 200;
const MAX_WIDTH = 500;
enum ItemTypes {
REQUEST = 'request',
}
export const Sidebar = memo(function Sidebar({ className }: Props) {
const [isResizing, setIsRisizing] = useState<boolean>(false);
const width = useKeyValue<number>({ key: 'sidebar_width', initialValue: INITIAL_WIDTH });
const [isResizing, setIsResizing] = useState<boolean>(false);
const sidebarRef = useRef<HTMLDivElement>(null);
const requests = useRequests();
const activeRequest = useActiveRequest();
const createRequest = useCreateRequest({ navigateAfter: true });
const width = useSidebarWidth();
const moveState = useRef<{ move: (e: MouseEvent) => void; up: () => void } | null>(null);
const unsub = () => {
@@ -56,28 +51,28 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
}
};
const handleResizeReset = useCallback(() => {
width.set(INITIAL_WIDTH);
}, []);
const handleResizeStart = useCallback(
(e: ReactMouseEvent<HTMLDivElement>) => {
if (width.value === undefined) return;
const handleResizeStart = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {
unsub();
const mouseStartX = e.clientX;
const startWidth = width.value;
moveState.current = {
move: (e: MouseEvent) => {
const newWidth = clamp(startWidth + (e.clientX - mouseStartX), MIN_WIDTH, MAX_WIDTH);
width.set(newWidth);
},
up: () => {
unsub();
setIsRisizing(false);
},
};
document.documentElement.addEventListener('mousemove', moveState.current.move);
document.documentElement.addEventListener('mouseup', moveState.current.up);
setIsRisizing(true);
}, []);
unsub();
const mouseStartX = e.clientX;
const startWidth = width.value;
moveState.current = {
move: (e: MouseEvent) => {
width.set(startWidth + (e.clientX - mouseStartX));
},
up: () => {
unsub();
setIsResizing(false);
},
};
document.documentElement.addEventListener('mousemove', moveState.current.move);
document.documentElement.addEventListener('mouseup', moveState.current.up);
setIsResizing(true);
},
[width.value],
);
const sidebarStyles = useMemo(() => ({ width: width.value }), [width.value]);
const sidebarWidth = width.value - 1; // Minus 1 for the border
@@ -89,7 +84,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
aria-hidden
className="group absolute z-10 right-0 w-1 top-0 bottom-0 cursor-ew-resize flex justify-end"
onMouseDown={handleResizeStart}
onDoubleClick={handleResizeReset}
onDoubleClick={width.reset}
>
<div // drag-divider
className={classnames(

View File

@@ -1,8 +1,10 @@
import classnames from 'classnames';
import { useMemo, useRef } from 'react';
import { useWindowSize } from 'react-use';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
@@ -17,8 +19,18 @@ export default function Workspace() {
const activeRequest = useActiveRequest();
const activeWorkspace = useActiveWorkspace();
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
const { width } = useWindowSize();
const isSideBySide = width > 900;
const mainContentRef = useRef<HTMLDivElement>(null);
const windowSize = useWindowSize();
const sidebarWidth = useSidebarWidth();
const mainContentWidth = useMemo(() => {
return mainContentRef.current?.getBoundingClientRect().width ?? 0;
// TODO: Use container query subscription instead of minitoring everything
}, [mainContentRef.current, windowSize, sidebarWidth.value]);
const isSideBySide = mainContentWidth > 700;
if (activeWorkspace == null) {
return null;
}
@@ -26,7 +38,11 @@ export default function Workspace() {
return (
<div className="grid grid-cols-[auto_1fr] grid-rows-1 h-full text-gray-900">
<Sidebar />
<div data-tauri-drag-region className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
<div
ref={mainContentRef}
data-tauri-drag-region
className="grid grid-rows-[auto_minmax(0,1fr)] h-full"
>
<HStack
as={WindowDragRegion}
justifyContent="center"

View File

@@ -43,14 +43,14 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
() =>
classnames(
className,
'outline-none',
'outline-none whitespace-nowrap',
'border border-transparent focus-visible:border-blue-300',
'rounded-md flex items-center',
colorStyles[color || 'default'],
justify === 'start' && 'justify-start',
justify === 'center' && 'justify-center',
size === 'md' && 'h-9 px-3',
size === 'sm' && 'h-7 px-2.5 text-sm',
size === 'md' && 'h-md px-3',
size === 'sm' && 'h-sm px-2.5 text-sm',
),
[color, size, justify, className],
);

View File

@@ -18,7 +18,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
onCheckedChange={onChange}
className={classnames(
className,
'flex-shrink-0 w-5 h-5 border border-gray-200 rounded',
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
'focus:border-focus',
'disabled:opacity-disabled',
'outline-none',
@@ -28,7 +28,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
>
<CB.Indicator className="flex items-center justify-center">
{checked === 'indeterminate' && <Icon icon="dividerH" />}
{checked === true && <Icon icon="check" />}
{checked === true && <Icon size="sm" icon="check" />}
</CB.Indicator>
</CB.Root>
);

View File

@@ -81,8 +81,8 @@ export function Input({
'relative w-full rounded-md text-gray-900',
'border border-gray-200 focus-within:border-focus',
!isValid && 'border-invalid',
size === 'md' && 'h-9',
size === 'sm' && 'h-7',
size === 'md' && 'h-md',
size === 'sm' && 'h-sm',
)}
>
{leftSlot}

View File

@@ -1,4 +1,5 @@
import type { CheckedState } from '@radix-ui/react-checkbox';
import classNames from 'classnames';
import classnames from 'classnames';
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { XYCoord } from 'react-dnd';
@@ -121,6 +122,7 @@ export const PairEditor = memo(function PairEditor({
<div
className={classnames(
className,
'@container',
'pb-6 grid',
// NOTE: Add padding to top so overflow doesn't hide drop marker
'py-1',
@@ -236,7 +238,7 @@ const FormRow = memo(function FormRow({
<div
ref={ref}
className={classnames(
'pb-2 group grid grid-cols-[auto_minmax(0,1fr)_auto]',
'pb-2 group grid grid-cols-[auto_auto_minmax(0,1fr)_auto]',
'grid-rows-1 items-center',
!pairContainer.pair.enabled && 'opacity-60',
)}
@@ -244,7 +246,7 @@ const FormRow = memo(function FormRow({
{!isLast ? (
<div
className={classnames(
'py-2 h-9 w-3 flex items-center',
'py-2 h-7 w-3 flex items-center',
'justify-center opacity-0 hover:opacity-100',
)}
>
@@ -253,15 +255,16 @@ const FormRow = memo(function FormRow({
) : (
<span className="w-3" />
)}
<HStack space={2} alignItems="center">
<Checkbox
disabled={isLast}
checked={isLast ? false : !!pairContainer.pair.enabled}
className={classnames(isLast && '!opacity-disabled')}
onChange={handleChangeEnabled}
/>
<Checkbox
disabled={isLast}
checked={isLast ? false : !!pairContainer.pair.enabled}
className={classnames('mr-2', isLast && '!opacity-disabled')}
onChange={handleChangeEnabled}
/>
<div className={classNames('flex flex-col @xs:flex-row gap-2 items-center')}>
<Input
hideLabel
size="sm"
require={!isLast && !!pairContainer.pair.enabled && !!pairContainer.pair.value}
useTemplating
containerClassName={classnames(isLast && 'border-dashed')}
@@ -275,6 +278,7 @@ const FormRow = memo(function FormRow({
/>
<Input
hideLabel
size="sm"
containerClassName={classnames(isLast && 'border-dashed')}
defaultValue={pairContainer.pair.value}
label="Value"
@@ -285,7 +289,7 @@ const FormRow = memo(function FormRow({
useTemplating
autocomplete={valueAutocomplete?.(pairContainer.pair.name)}
/>
</HStack>
</div>
<IconButton
aria-hidden={!onDelete}
disabled={!onDelete}

View File

@@ -17,7 +17,11 @@ interface HStackProps extends BaseStackProps {
export function HStack({ className, space, children, ...props }: HStackProps) {
return (
<BaseStack className={classnames(className, 'flex-row', space && gapClasses[space])} {...props}>
<BaseStack
direction="row"
className={classnames(className, 'flex-row', space && gapClasses[space])}
{...props}
>
{children}
</BaseStack>
);
@@ -30,7 +34,8 @@ export type VStackProps = BaseStackProps & {
export function VStack({ className, space, children, ...props }: VStackProps) {
return (
<BaseStack
className={classnames(className, 'w-full h-full flex-col', space && gapClasses[space])}
direction="col"
className={classnames(className, 'w-full h-full', space && gapClasses[space])}
{...props}
>
{children}
@@ -43,15 +48,25 @@ type BaseStackProps = HTMLAttributes<HTMLElement> & {
space?: keyof typeof gapClasses;
alignItems?: 'start' | 'center';
justifyContent?: 'start' | 'center' | 'end';
direction?: 'row' | 'col';
};
function BaseStack({ className, alignItems, justifyContent, children, as }: BaseStackProps) {
function BaseStack({
className,
direction,
alignItems,
justifyContent,
children,
as,
}: BaseStackProps) {
const Component = as ?? 'div';
return (
<Component
className={classnames(
className,
'flex',
direction === 'row' && 'flex-row',
direction === 'col' && 'flex-col',
alignItems === 'center' && 'items-center',
alignItems === 'start' && 'items-start',
justifyContent === 'start' && 'justify-start',

View File

@@ -128,6 +128,7 @@ export const TabContent = memo(function TabContent({
}: TabContentProps) {
return (
<T.Content
tabIndex={-1}
forceMount
value={value}
className={classnames(className, 'tab-content', 'w-full h-full overflow-auto')}