mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 18:31:16 +01:00
Start of themes
This commit is contained in:
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
)}
|
||||
|
||||
@@ -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',
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user