Start of themes

This commit is contained in:
Gregory Schier
2023-03-07 11:24:38 -08:00
parent b48a41aaec
commit 5aa80d8ea8
24 changed files with 1490 additions and 156 deletions

View File

@@ -10,7 +10,8 @@ import { Icon } from './Icon';
const colorStyles = {
default: 'hover:bg-gray-500/10 text-gray-600',
gray: 'text-gray-800 bg-gray-50 hover:bg-gray-500/20',
gray: 'text-gray-800 bg-gray-100 hover:bg-gray-500/20',
tint: 'text-white/90 hover:text-white hover:bg-white/20',
primary: 'bg-blue-400',
secondary: 'bg-violet-400',
warning: 'bg-orange-400',
@@ -45,7 +46,9 @@ export const Button = forwardRef(function Button<T extends ElementType>(
type="button"
className={classnames(
className,
'transition-all rounded-md flex items-center bg-opacity-80 hover:bg-opacity-100 hover:text-white',
'outline-none', // TODO: Add focus styles
'border border-transparent focus-visible:border-blue-300',
'transition-all rounded-md flex items-center hover:text-white',
// 'active:translate-y-[0.5px] active:scale-[0.99]',
colorStyles[color || 'default'],
justify === 'start' && 'justify-start',
@@ -57,7 +60,7 @@ export const Button = forwardRef(function Button<T extends ElementType>(
{...props}
>
{children}
{forDropdown && <Icon icon="triangle-down" className="ml-1 -mr-1" />}
{forDropdown && <Icon icon="triangleDown" className="ml-1 -mr-1" />}
</Component>
);
});

View File

@@ -264,11 +264,7 @@ function DropdownMenuSeparator({ className, ...props }: D.DropdownMenuSeparatorP
function DropdownMenuTrigger({ children, className, ...props }: D.DropdownMenuTriggerProps) {
return (
<D.Trigger
asChild
className={classnames(className, 'focus:outline-none focus:border-0 focus:shadow-none')}
{...props}
>
<D.Trigger asChild className={classnames(className)} {...props}>
{children}
</D.Trigger>
);
@@ -290,7 +286,7 @@ const ItemInner = forwardRef<HTMLDivElement, ItemInnerProps>(function ItemInner(
ref={ref}
className={classnames(
className,
'outline-none px-2 py-1.5 flex items-center text-sm text-gray-700',
'outline-none px-2 py-1.5 flex items-center text-sm text-gray-700 whitespace-nowrap pr-4',
!noHover && 'focus:bg-gray-50 focus:text-gray-900 rounded',
)}
{...props}

View File

@@ -12,7 +12,7 @@
}
.cm-editor {
@apply bg-background w-full block text-[0.85rem];
@apply w-full block text-[0.85rem];
&.cm-focused {
outline: none !important;
@@ -26,6 +26,18 @@
@apply text-placeholder;
}
.cm-gutters {
@apply border-0 text-gray-500 text-opacity-30;
.cm-gutterElement {
@apply cursor-default;
}
}
&.cm-focused .cm-gutters,
.cm-gutters:hover {
@apply text-opacity-60;
}
.placeholder-widget {
@apply text-xs text-white/90 bg-blue-400/80 py-[0.5px] px-1 mx-[1px] rounded cursor-default hover:bg-blue-400 hover:text-white;
text-shadow: 0 0 1px rgba(0, 0, 0, 0.9);
@@ -69,10 +81,6 @@
align-items: center !important;
}
.cm-editor .cm-gutters {
@apply bg-background border-0 text-gray-200;
}
.cm-editor .cm-gutterElement {
transition: color var(--transition-duration);
}
@@ -98,10 +106,6 @@
@apply text-gray-400 bg-gray-100/20;
}
.cm-editor.cm-focused .cm-gutters {
@apply text-gray-300;
}
.cm-editor .cm-foldPlaceholder {
@apply px-2 border border-gray-200 bg-gray-100;
}

View File

@@ -1,19 +1,17 @@
import { defaultKeymap } from '@codemirror/commands';
import type { Extension } from '@codemirror/state';
import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import classnames from 'classnames';
import { EditorView } from 'codemirror';
import type { CSSProperties, HTMLAttributes } from 'react';
import type { HTMLAttributes } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import './Editor.css';
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
import { singleLineExt } from './singleLine';
export interface EditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
height?: 'auto' | 'full';
heightMode?: 'auto' | 'full';
contentType?: string;
backgroundColor?: string;
autoFocus?: boolean;
valueKey?: string | number;
defaultValue?: string;
@@ -25,9 +23,8 @@ export interface EditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCha
}
export default function Editor({
height,
heightMode,
contentType,
backgroundColor,
autoFocus,
placeholder,
valueKey,
@@ -53,6 +50,18 @@ export default function Editor({
[contentType, ref.current],
);
const syncGutterBg = () => {
if (ref.current === null) return;
if (singleLine) return;
const gutterEl = ref.current.querySelector<HTMLDivElement>('.cm-gutters');
const bgClass = className
?.split(/\s+/)
.find((c) => c.startsWith('!bg-') || c.startsWith('bg-'));
if (bgClass && gutterEl) {
gutterEl?.classList.add(`${bgClass}`);
}
};
// Create codemirror instance when ref initializes
useEffect(() => {
if (ref.current === null) return;
@@ -69,6 +78,7 @@ export default function Editor({
parent: ref.current,
});
setCm({ view, langHolder });
syncGutterBg();
if (autoFocus && view) view.focus();
} catch (e) {
console.log('Failed to initialize Codemirror', e);
@@ -76,6 +86,10 @@ export default function Editor({
return () => view?.destroy();
}, [ref.current, valueKey]);
useEffect(() => {
syncGutterBg();
}, [ref.current, className]);
// Update value when valueKey changes
// TODO: This would be more efficient but the onChange handler gets fired on update
// useEffect(() => {
@@ -98,11 +112,10 @@ export default function Editor({
ref={ref}
className={classnames(
className,
'cm-wrapper text-base',
height === 'auto' ? 'cm-auto-height' : 'cm-full-height',
'cm-wrapper text-base bg-background',
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
singleLine ? 'cm-singleline' : 'cm-multiline',
)}
data-color-background="var(--color-gray-50)"
{...props}
/>
);
@@ -158,24 +171,3 @@ function getExtensions({
}),
];
}
const newState = ({
langHolder,
contentType,
useTemplating,
defaultValue,
extensions,
}: {
langHolder: Compartment;
contentType?: string;
useTemplating?: boolean;
defaultValue?: string;
extensions: Extension[];
}) => {
console.log('NEW STATE', defaultValue);
const langExt = getLanguageExtension({ contentType, useTemplating });
return EditorState.create({
doc: `${defaultValue ?? ''}`,
extensions: [...extensions, langHolder.of(langExt)],
});
};

View File

@@ -51,7 +51,7 @@ export const myHighlightStyle = HighlightStyle.define([
{ tag: [t.attributeName], color: 'hsl(var(--color-violet-600))' },
{ tag: [t.attributeValue], color: 'hsl(var(--color-orange-600))' },
{ tag: [t.string], color: 'hsl(var(--color-yellow-600))' },
{ tag: [t.keyword, t.meta, t.operator], color: '#45e8a4' },
{ tag: [t.keyword, t.meta, t.operator], color: 'hsl(var(--color-red-600))' },
]);
// export const defaultHighlightStyle = HighlightStyle.define([

View File

@@ -2,6 +2,7 @@ import {
ArchiveIcon,
CameraIcon,
CheckIcon,
ClockIcon,
CodeIcon,
Cross2Icon,
EyeOpenIcon,
@@ -15,6 +16,8 @@ import {
SunIcon,
TrashIcon,
TriangleDownIcon,
TriangleLeftIcon,
TriangleRightIcon,
UpdateIcon,
} from '@radix-ui/react-icons';
import classnames from 'classnames';
@@ -26,13 +29,16 @@ type IconName =
| 'camera'
| 'gear'
| 'eye'
| 'triangle-down'
| 'paper-plane'
| 'triangleDown'
| 'triangleLeft'
| 'triangleRight'
| 'paperPlane'
| 'update'
| 'question'
| 'check'
| 'plus'
| 'plus-circled'
| 'plusCircle'
| 'clock'
| 'sun'
| 'code'
| 'x'
@@ -40,13 +46,16 @@ type IconName =
| 'moon';
const icons: Record<IconName, NamedExoticComponent<{ className: string }>> = {
'paper-plane': PaperPlaneIcon,
'triangle-down': TriangleDownIcon,
paperPlane: PaperPlaneIcon,
triangleDown: TriangleDownIcon,
plus: PlusIcon,
'plus-circled': PlusCircledIcon,
plusCircle: PlusCircledIcon,
clock: ClockIcon,
archive: ArchiveIcon,
camera: CameraIcon,
check: CheckIcon,
triangleLeft: TriangleLeftIcon,
triangleRight: TriangleRightIcon,
gear: GearIcon,
home: HomeIcon,
update: UpdateIcon,

View File

@@ -5,18 +5,22 @@ import type { ButtonProps } from './Button';
import { Button } from './Button';
import classnames from 'classnames';
type Props = Omit<IconProps, 'size'> & ButtonProps<typeof Button>;
type Props = Omit<IconProps, 'size'> &
ButtonProps<typeof Button> & {
iconClassName?: string;
};
export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButton(
{ icon, spin, ...props }: Props,
{ icon, spin, className, iconClassName, ...props }: Props,
ref,
) {
return (
<Button ref={ref} className="group" {...props}>
<Button ref={ref} className={classnames(className, 'group')} {...props}>
<Icon
icon={icon}
spin={spin}
className={classnames(
iconClassName,
'text-gray-700 group-hover:text-gray-900',
props.disabled && 'opacity-70',
)}

View File

@@ -56,7 +56,7 @@ export function Input({
className={classnames(
containerClassName,
'relative w-full rounded-md text-gray-900 bg-gray-200/10',
'border border-gray-500/10 focus-within:border-blue-400/40',
'border border-gray-50 focus-within:border-blue-400/40',
size === 'md' && 'h-10',
size === 'sm' && 'h-8',
)}

View File

@@ -22,7 +22,7 @@ export function RequestPane({ fullHeight, request, className }: Props) {
>
<div className="pl-2">
<UrlBar
className="border-0 mb-1"
className="bg-transparent"
key={request.id}
method={request.method}
url={request.url}
@@ -36,28 +36,27 @@ export function RequestPane({ fullHeight, request, className }: Props) {
{/*<Divider className="mb-2" />*/}
<ScrollArea className="max-w-full pb-2 mx-2">
<HStack className="mt-2 hide-scrollbar" space={1}>
{['JSON', 'Params', 'Headers', 'Auth', 'Docs'].map((label, i) => (
{['JSON', 'Params', 'Headers', 'Auth'].map((label, i) => (
<Button
key={label}
size="xs"
color={i === 0 && 'gray'}
className={i !== 0 && 'opacity-50 hover:opacity-60'}
className={i !== 0 && 'opacity-80 hover:opacity-100'}
>
{label}
</Button>
))}
</HStack>
</ScrollArea>
<div className="px-0">
<Editor
height={fullHeight ? 'full' : 'auto'}
valueKey={request.id}
useTemplating
defaultValue={request.body ?? ''}
contentType="application/json"
onChange={(body) => updateRequest.mutate({ body })}
/>
</div>
<Editor
className="mt-1 !bg-gray-50"
heightMode={fullHeight ? 'full' : 'auto'}
valueKey={request.id}
useTemplating
defaultValue={request.body ?? ''}
contentType="application/json"
onChange={(body) => updateRequest.mutate({ body })}
/>
</div>
);
}

View File

@@ -46,7 +46,7 @@ export function ResponsePane({ requestId, className }: Props) {
<div
className={classnames(
className,
'max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 bg-gray-50/50 rounded-md',
'max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 bg-gray-100 rounded-md overflow-hidden border border-gray-50',
)}
>
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
@@ -58,7 +58,7 @@ export function ResponsePane({ requestId, className }: Props) {
<>
<HStack
items="center"
className="italic text-gray-500 text-sm w-full mb-1 flex-shrink-0 pl-2 py-1"
className="italic text-gray-500 text-sm w-full mb-1 flex-shrink-0 pl-2"
>
<div className="whitespace-nowrap">
{response.status}
@@ -68,7 +68,7 @@ export function ResponsePane({ requestId, className }: Props) {
{Math.round(response.body.length / 1000)} KB
</div>
<HStack items="center" className="ml-auto">
<HStack items="center" className="ml-auto h-8">
{contentType.includes('html') && (
<IconButton
icon={viewMode === 'pretty' ? 'eye' : 'code'}
@@ -97,7 +97,12 @@ export function ResponsePane({ requestId, className }: Props) {
})),
]}
>
<IconButton icon="gear" className="ml-auto" size="sm" />
<IconButton
icon="clock"
className="ml-auto"
iconClassName="text-gray-300"
size="sm"
/>
</Dropdown>
</HStack>
</HStack>
@@ -113,7 +118,7 @@ export function ResponsePane({ requestId, className }: Props) {
</div>
) : response?.body ? (
<Editor
backgroundColor="red"
className="mr-1 !bg-gray-100"
valueKey={`${contentType}:${response.body}`}
defaultValue={response?.body}
contentType={contentType}

View File

@@ -20,11 +20,14 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
export function Sidebar({ className, activeRequestId, workspaceId, requests, ...props }: Props) {
const createRequest = useRequestCreate({ workspaceId, navigateAfter: true });
const { toggleTheme } = useTheme();
const { appearance, toggleAppearance } = useTheme();
const [open, setOpen] = useState<boolean>(false);
return (
<div
className={classnames(className, 'w-52 bg-gray-50 h-full border-gray-100/50 relative z-10')}
className={classnames(
className,
'w-52 bg-violet-600 dark:bg-violet-50 h-full border-gray-100/50 relative z-10',
)}
{...props}
>
<HStack as={WindowDragRegion} items="center" justify="end">
@@ -34,17 +37,14 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
Save
</Button>
</Dialog>
{/*<IconButton*/}
{/* size="sm"*/}
{/* icon="camera"*/}
{/* onClick={() => {*/}
{/* setOpen((v) => !v);*/}
{/* }}*/}
{/*/>*/}
<IconButton size="sm" icon="sun" onClick={toggleTheme} />
<IconButton
size="sm"
icon="plus-circled"
icon={appearance === 'dark' ? 'moon' : 'sun'}
onClick={toggleAppearance}
/>
<IconButton
size="sm"
icon="plusCircle"
onClick={async () => {
await createRequest.mutate({ name: 'Test Request' });
}}
@@ -54,6 +54,19 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
{requests.map((r) => (
<SidebarItem key={r.id} request={r} active={r.id === activeRequestId} />
))}
<div>
<div className="w-10 h-5 bg-blue-50" />
<div className="w-10 h-5 bg-blue-100" />
<div className="w-10 h-5 bg-blue-200" />
<div className="w-10 h-5 bg-blue-300" />
<div className="w-10 h-5 bg-blue-400" />
<div className="w-10 h-5 bg-blue-500" />
<div className="w-10 h-5 bg-blue-600" />
<div className="w-10 h-5 bg-blue-700" />
<div className="w-10 h-5 bg-blue-800" />
<div className="w-10 h-5 bg-blue-900" />
<div className="w-10 h-5 bg-blue-950" />
</div>
</VStack>
</div>
);
@@ -64,12 +77,13 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
<li key={request.id}>
<Button
as={Link}
color="tint"
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
className={classnames('w-full', active && 'bg-gray-500/[0.1] text-gray-900')}
size="xs"
justify="start"
>
{request.name}
{request.name || request.url}
</Button>
</li>
);

View File

@@ -58,8 +58,8 @@ export function UrlBar({
<Button
type="button"
disabled={loading}
size="sm"
className="ml-1 mr-2 !px-2 !text-gray-800"
size="xs"
className="mx-0.5 !text-gray-800"
justify="start"
>
{method.toUpperCase()}
@@ -69,11 +69,11 @@ export function UrlBar({
rightSlot={
<IconButton
type="submit"
size="sm"
icon={loading ? 'update' : 'paper-plane'}
className="mr-0.5"
size="xs"
icon={loading ? 'update' : 'paperPlane'}
spin={loading}
disabled={loading}
className="mx-1 !px-4"
title="Send Request"
/>
}