mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-21 17:09:09 +01:00
Show response headers
This commit is contained in:
21
src-web/components/core/Banner.tsx
Normal file
21
src-web/components/core/Banner.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
12
src-web/components/core/CountBadge.tsx
Normal file
12
src-web/components/core/CountBadge.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user