mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
Add body type to request and tab dropdown
This commit is contained in:
@@ -34,7 +34,19 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
</div>
|
||||
<Tabs
|
||||
tabs={[
|
||||
{ value: 'body', label: 'JSON' },
|
||||
{
|
||||
value: 'body',
|
||||
label: activeRequest.bodyType ?? 'NoBody',
|
||||
options: {
|
||||
onValueChange: (bodyType) => updateRequest.mutate({ bodyType: bodyType.value }),
|
||||
value: activeRequest.bodyType ?? 'nobody',
|
||||
items: [
|
||||
{ label: 'No Body', value: 'nobody' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: 'GraphQL', value: 'graphql' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ value: 'params', label: 'Params' },
|
||||
{ value: 'headers', label: 'Headers' },
|
||||
{ value: 'auth', label: 'Auth' },
|
||||
@@ -48,15 +60,29 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
<HeaderEditor key={activeRequest.id} request={activeRequest} />
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
useTemplating
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="application/graphql+json"
|
||||
onChange={(body) => updateRequest.mutate({ body })}
|
||||
/>
|
||||
{activeRequest.bodyType === 'json' ? (
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
useTemplating
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="application/json"
|
||||
onChange={(body) => updateRequest.mutate({ body })}
|
||||
/>
|
||||
) : activeRequest.bodyType === 'graphql' ? (
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
useTemplating
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="application/graphql+json"
|
||||
onChange={(body) => updateRequest.mutate({ body })}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full text-gray-400 flex items-center justify-center">No Body</div>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDeleteResponse } from '../hooks/useResponseDelete';
|
||||
import { useResponses } from '../hooks/useResponses';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
|
||||
import { Editor } from './core/Editor';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -97,7 +97,9 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||
})),
|
||||
]}
|
||||
>
|
||||
<IconButton icon="clock" className="ml-auto" size="sm" />
|
||||
<DropdownMenuTrigger>
|
||||
<IconButton icon="clock" className="ml-auto" size="sm" />
|
||||
</DropdownMenuTrigger>
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
@@ -21,6 +21,7 @@ export type ButtonProps = HTMLAttributes<HTMLElement> & {
|
||||
justify?: 'start' | 'center';
|
||||
type?: 'button' | 'submit';
|
||||
forDropdown?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -2,18 +2,20 @@ import * as D from '@radix-ui/react-dropdown-menu';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import classnames from 'classnames';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { ReactNode, ForwardedRef } from 'react';
|
||||
import type { ForwardedRef, ReactElement, ReactNode } from 'react';
|
||||
import { forwardRef, useImperativeHandle, useLayoutEffect, useState } from 'react';
|
||||
|
||||
interface DropdownMenuRadioProps {
|
||||
children: ReactNode;
|
||||
onValueChange: ((v: { label: string; value: string }) => void) | null;
|
||||
export interface DropdownMenuRadioItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface DropdownMenuRadioProps {
|
||||
children: ReactElement<typeof DropdownMenuTrigger>;
|
||||
onValueChange: ((v: DropdownMenuRadioItem) => void) | null;
|
||||
value: string;
|
||||
label?: string;
|
||||
items: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
items: DropdownMenuRadioItem[];
|
||||
}
|
||||
|
||||
export function DropdownMenuRadio({
|
||||
@@ -32,7 +34,7 @@ export function DropdownMenuRadio({
|
||||
|
||||
return (
|
||||
<D.Root>
|
||||
<DropdownMenuTrigger>{children}</DropdownMenuTrigger>
|
||||
{children}
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
{label && <DropdownMenuLabel>{label}</DropdownMenuLabel>}
|
||||
@@ -50,7 +52,7 @@ export function DropdownMenuRadio({
|
||||
}
|
||||
|
||||
export interface DropdownProps {
|
||||
children: ReactNode;
|
||||
children: ReactElement<typeof DropdownMenuTrigger>;
|
||||
items: (
|
||||
| {
|
||||
label: string;
|
||||
@@ -65,7 +67,7 @@ export interface DropdownProps {
|
||||
export function Dropdown({ children, items }: DropdownProps) {
|
||||
return (
|
||||
<D.Root>
|
||||
<DropdownMenuTrigger>{children}</DropdownMenuTrigger>
|
||||
{children}
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
{items.map((item, i) => {
|
||||
@@ -268,7 +270,7 @@ type DropdownMenuTriggerProps = D.DropdownMenuTriggerProps & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function DropdownMenuTrigger({ children, className, ...props }: DropdownMenuTriggerProps) {
|
||||
export function DropdownMenuTrigger({ children, className, ...props }: DropdownMenuTriggerProps) {
|
||||
return (
|
||||
<D.Trigger asChild className={classnames(className)} {...props}>
|
||||
{children}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as T from '@radix-ui/react-tabs';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '../Button';
|
||||
import { ScrollArea } from '../ScrollArea';
|
||||
import type { DropdownMenuRadioItem, DropdownMenuRadioProps } from '../Dropdown';
|
||||
import { DropdownMenuRadio, DropdownMenuTrigger } from '../Dropdown';
|
||||
import { Icon } from '../Icon';
|
||||
import { HStack } from '../Stacks';
|
||||
|
||||
import './Tabs.css';
|
||||
@@ -11,7 +13,15 @@ import './Tabs.css';
|
||||
interface Props {
|
||||
defaultValue?: string;
|
||||
label: string;
|
||||
tabs: { value: string; label: ReactNode }[];
|
||||
tabs: {
|
||||
value: string;
|
||||
label: string;
|
||||
options?: {
|
||||
onValueChange: DropdownMenuRadioProps['onValueChange'];
|
||||
value: string;
|
||||
items: DropdownMenuRadioItem[];
|
||||
};
|
||||
}[];
|
||||
tabListClassName?: string;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
@@ -32,43 +42,72 @@ export function Tabs({ defaultValue, label, children, tabs, className, tabListCl
|
||||
'h-auto flex items-center overflow-x-auto mb-1 pb-1',
|
||||
)}
|
||||
>
|
||||
{/*<ScrollArea className="w-full pb-2">*/}
|
||||
<HStack space={1}>
|
||||
{tabs.map((t) => (
|
||||
<TabTrigger key={t.value} value={t.value} active={t.value === value}>
|
||||
{t.label}
|
||||
</TabTrigger>
|
||||
))}
|
||||
{tabs.map((t) => {
|
||||
const isActive = t.value === value;
|
||||
if (t.options && isActive) {
|
||||
return (
|
||||
<DropdownMenuRadio
|
||||
key={t.value}
|
||||
items={t.options.items}
|
||||
value={t.options.value}
|
||||
onValueChange={t.options.onValueChange}
|
||||
>
|
||||
<DropdownMenuTrigger>
|
||||
<Button
|
||||
color="custom"
|
||||
size="sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={classnames(
|
||||
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 ?? ''}
|
||||
<Icon icon="triangleDown" className="-mr-1.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</DropdownMenuRadio>
|
||||
);
|
||||
} else if (t.options && !isActive) {
|
||||
return (
|
||||
<T.Trigger asChild key={t.value} value={t.value}>
|
||||
<Button
|
||||
color="custom"
|
||||
size="sm"
|
||||
className={classnames(
|
||||
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 ?? ''}
|
||||
<Icon icon="triangleDown" className="-mr-1.5" />
|
||||
</Button>
|
||||
</T.Trigger>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<T.Trigger asChild key={t.value} value={t.value}>
|
||||
<Button
|
||||
color="custom"
|
||||
size="sm"
|
||||
className={classnames(
|
||||
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
|
||||
)}
|
||||
>
|
||||
{t.label}
|
||||
</Button>
|
||||
</T.Trigger>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</HStack>
|
||||
{/*</ScrollArea>*/}
|
||||
</T.List>
|
||||
{children}
|
||||
</T.Root>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabTriggerProps {
|
||||
value: string;
|
||||
children: ReactNode;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export function TabTrigger({ value, children, active }: TabTriggerProps) {
|
||||
return (
|
||||
<T.Trigger value={value} asChild>
|
||||
<Button
|
||||
color="custom"
|
||||
size="sm"
|
||||
className={classnames(
|
||||
active ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</T.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabContentProps {
|
||||
value: string;
|
||||
children: ReactNode;
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface HttpRequest extends BaseModel {
|
||||
name: string;
|
||||
url: string;
|
||||
body: string | null;
|
||||
bodyType: string | null;
|
||||
method: string;
|
||||
headers: HttpHeader[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user