Show response headers

This commit is contained in:
Gregory Schier
2023-04-01 23:43:22 -07:00
parent 818595e961
commit bfa186aebb
19 changed files with 270 additions and 129 deletions

View File

@@ -0,0 +1,21 @@
import classnames from 'classnames';
import type { ReactNode } from 'react';
interface Props {
children: ReactNode;
className?: string;
}
export function Banner({ children, className }: Props) {
return (
<div>
<div
className={classnames(
className,
'border border-red-500 bg-red-300/10 text-red-800 px-3 py-2 rounded select-auto cursor-text',
)}
>
{children}
</div>
</div>
);
}

View File

@@ -18,6 +18,7 @@ const colorStyles = {
export type ButtonProps = HTMLAttributes<HTMLElement> & {
to?: string;
color?: keyof typeof colorStyles;
isLoading?: boolean;
size?: 'sm' | 'md' | 'xs';
justify?: 'start' | 'center';
type?: 'button' | 'submit';
@@ -30,6 +31,7 @@ export type ButtonProps = HTMLAttributes<HTMLElement> & {
const _Button = forwardRef<any, ButtonProps>(function Button(
{
to,
isLoading,
className,
children,
forDropdown,
@@ -68,8 +70,9 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
} else {
return (
<button ref={ref} className={classes} {...props}>
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
{children}
{forDropdown && <Icon icon="chevronDown" size="sm" className="ml-1 -mr-1" />}
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
</button>
);
}

View File

@@ -0,0 +1,12 @@
interface Props {
count: number;
}
export function CountBadge({ count }: Props) {
if (count === 0) return null;
return (
<div aria-hidden className="opacity-80 text-2xs border rounded px-1 mb-0.5 ml-1 h-4">
{count}
</div>
);
}

View File

@@ -47,7 +47,7 @@
}
.placeholder-widget {
@apply text-[0.9em] text-gray-800 dark:text-gray-900 px-1 rounded cursor-default dark:shadow;
@apply text-xs text-gray-800 dark:text-gray-900 px-1 rounded cursor-default dark:shadow;
/* NOTE: Background and border are translucent so we can see text selection through it */
@apply bg-gray-300/40 border border-gray-300 border-opacity-40 hover:border-opacity-80;

View File

@@ -33,6 +33,7 @@ export interface EditorProps {
useTemplating?: boolean;
onChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
singleLine?: boolean;
format?: (v: string) => string;
autocomplete?: GenericCompletionConfig;
@@ -52,6 +53,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
forceUpdateKey,
onChange,
onFocus,
onBlur,
className,
singleLine,
format,
@@ -75,6 +77,12 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
handleFocus.current = onFocus;
}, [onFocus]);
// Use ref so we can update the onChange handler without re-initializing the editor
const handleBlur = useRef<EditorProps['onBlur']>(onBlur);
useEffect(() => {
handleBlur.current = onBlur;
}, [onBlur]);
// Update placeholder
const placeholderCompartment = useRef(new Compartment());
useEffect(() => {
@@ -125,6 +133,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
container,
onChange: handleChange,
onFocus: handleFocus,
onBlur: handleBlur,
readOnly,
singleLine,
}),
@@ -195,10 +204,12 @@ function getExtensions({
singleLine,
onChange,
onFocus,
onBlur,
}: Pick<EditorProps, 'singleLine' | 'readOnly'> & {
container: HTMLDivElement | null;
onChange: MutableRefObject<EditorProps['onChange']>;
onFocus: MutableRefObject<EditorProps['onFocus']>;
onBlur: MutableRefObject<EditorProps['onBlur']>;
}) {
// TODO: Ensure tooltips render inside the dialog if we are in one.
const parent =
@@ -234,9 +245,8 @@ function getExtensions({
// Handle onFocus
EditorView.domEventHandlers({
focus: () => {
onFocus.current?.();
},
focus: onFocus.current,
blur: onBlur.current,
}),
// Handle onChange

View File

@@ -17,6 +17,7 @@ export type InputProps = Omit<HTMLAttributes<HTMLInputElement>, 'onChange' | 'on
containerClassName?: string;
onChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
defaultValue?: string;
leftSlot?: ReactNode;
rightSlot?: ReactNode;
@@ -45,6 +46,8 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
defaultValue,
validate,
require,
onFocus,
onBlur,
forceUpdateKey,
...props
}: InputProps,
@@ -52,6 +55,18 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
) {
const [obscured, setObscured] = useState(type === 'password');
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
const [focused, setFocused] = useState(false);
const handleOnFocus = useCallback(() => {
setFocused(true);
onFocus?.();
}, [onFocus]);
const handleOnBlur = useCallback(() => {
setFocused(false);
onBlur?.();
}, [onBlur]);
const id = `input-${name}`;
const inputClassName = classnames(
className,
@@ -92,7 +107,8 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
className={classnames(
containerClassName,
'relative w-full rounded-md text-gray-900',
'border border-highlight focus-within:border-focus',
'border border-highlight',
focused && 'border-focus',
!isValid && '!border-invalid',
size === 'md' && 'h-md leading-md',
size === 'sm' && 'h-sm leading-sm',
@@ -109,6 +125,8 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
placeholder={placeholder}
onChange={handleChange}
className={inputClassName}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
{...props}
/>
{type === 'password' && (

View File

@@ -337,7 +337,7 @@ const FormRow = memo(function FormRow({
size="sm"
title="Delete header"
onClick={handleDelete}
className="ml-0.5 opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
className="ml-0.5 !opacity-0 group-hover:!opacity-100 focus-visible:!opacity-100"
/>
</div>
);

View File

@@ -10,7 +10,7 @@ import { HStack } from '../Stacks';
export type TabItem =
| {
value: string;
label: string;
label: ReactNode;
}
| {
value: string;
@@ -73,16 +73,17 @@ export function Tabs({
aria-label={label}
className={classnames(
tabListClassName,
'h-md flex items-center overflow-x-auto pb-0.5 hide-scrollbars',
'h-md flex items-center overflow-x-auto hide-scrollbars',
// Give space for button focus states within overflow boundary
'px-2 -mx-2',
)}
>
<HStack space={1} className="flex-shrink-0">
<HStack space={3} className="-ml-1 flex-shrink-0">
{tabs.map((t) => {
const isActive = t.value === value;
const btnClassName = classnames(
isActive ? 'bg-gray-100 text-gray-800' : 'text-gray-600 hover:text-gray-900',
'!px-1',
isActive ? 'text-gray-900' : 'text-gray-600 hover:text-gray-800',
);
if ('options' in t) {
const option = t.options.items.find(