mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:18:30 +02:00
Better dropdown separator
This commit is contained in:
@@ -76,7 +76,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
|||||||
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
||||||
onSelect: toggleViewMode,
|
onSelect: toggleViewMode,
|
||||||
},
|
},
|
||||||
'-----',
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Clear Response',
|
label: 'Clear Response',
|
||||||
onSelect: deleteResponse.mutate,
|
onSelect: deleteResponse.mutate,
|
||||||
@@ -88,7 +88,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
|||||||
hidden: responses.length <= 1,
|
hidden: responses.length <= 1,
|
||||||
disabled: responses.length === 0,
|
disabled: responses.length === 0,
|
||||||
},
|
},
|
||||||
'-----',
|
{ type: 'separator' },
|
||||||
...responses.slice(0, 10).map((r) => ({
|
...responses.slice(0, 10).map((r) => ({
|
||||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sidebarStyles = useMemo(() => ({ width: width.value }), [width.value]);
|
const sidebarStyles = useMemo(() => ({ width: width.value }), [width.value]);
|
||||||
const sidebarWidth = width.value - 1; // Minus 1 for the border
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -89,6 +88,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
>
|
>
|
||||||
<HStack as={WindowDragRegion} alignItems="center" justifyContent="end">
|
<HStack as={WindowDragRegion} alignItems="center" justifyContent="end">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size="sm"
|
||||||
title="Add Request"
|
title="Add Request"
|
||||||
className="mx-1"
|
className="mx-1"
|
||||||
icon="plusCircle"
|
icon="plusCircle"
|
||||||
@@ -101,12 +101,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
<VStack as="ul" className="relative py-3" draggable={false}>
|
<VStack as="ul" className="relative py-3 overflow-auto" draggable={false}>
|
||||||
<SidebarItems
|
<SidebarItems activeRequestId={activeRequest?.id} requests={requests} />
|
||||||
sidebarWidth={sidebarWidth}
|
|
||||||
activeRequestId={activeRequest?.id}
|
|
||||||
requests={requests}
|
|
||||||
/>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
<HStack className="mx-1 pb-1" alignItems="center" justifyContent="end">
|
<HStack className="mx-1 pb-1" alignItems="center" justifyContent="end">
|
||||||
<ToggleThemeButton />
|
<ToggleThemeButton />
|
||||||
@@ -119,11 +115,9 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
function SidebarItems({
|
function SidebarItems({
|
||||||
requests,
|
requests,
|
||||||
activeRequestId,
|
activeRequestId,
|
||||||
sidebarWidth,
|
|
||||||
}: {
|
}: {
|
||||||
requests: HttpRequest[];
|
requests: HttpRequest[];
|
||||||
activeRequestId?: string;
|
activeRequestId?: string;
|
||||||
sidebarWidth: number;
|
|
||||||
}) {
|
}) {
|
||||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
const updateRequest = useUpdateAnyRequest();
|
const updateRequest = useUpdateAnyRequest();
|
||||||
@@ -178,7 +172,6 @@ function SidebarItems({
|
|||||||
requestName={r.name}
|
requestName={r.name}
|
||||||
workspaceId={r.workspaceId}
|
workspaceId={r.workspaceId}
|
||||||
active={r.id === activeRequestId}
|
active={r.id === activeRequestId}
|
||||||
sidebarWidth={sidebarWidth}
|
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
onEnd={handleEnd}
|
onEnd={handleEnd}
|
||||||
/>
|
/>
|
||||||
@@ -194,12 +187,11 @@ type SidebarItemProps = {
|
|||||||
requestId: string;
|
requestId: string;
|
||||||
requestName: string;
|
requestName: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
sidebarWidth: number;
|
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _SidebarItem = forwardRef(function SidebarItem(
|
const _SidebarItem = forwardRef(function SidebarItem(
|
||||||
{ className, requestName, requestId, workspaceId, active, sidebarWidth }: SidebarItemProps,
|
{ className, requestName, requestId, workspaceId, active }: SidebarItemProps,
|
||||||
ref: ForwardedRef<HTMLLIElement>,
|
ref: ForwardedRef<HTMLLIElement>,
|
||||||
) {
|
) {
|
||||||
const updateRequest = useUpdateRequest(requestId);
|
const updateRequest = useUpdateRequest(requestId);
|
||||||
@@ -215,7 +207,6 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
el?.select();
|
el?.select();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const itemStyles = useMemo(() => ({ width: sidebarWidth }), [sidebarWidth]);
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent<HTMLElement>) => {
|
(e: KeyboardEvent<HTMLElement>) => {
|
||||||
// Hitting enter on active request during keyboard nav will start edit
|
// Hitting enter on active request during keyboard nav will start edit
|
||||||
@@ -242,12 +233,8 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li ref={ref} className={classnames(className, 'block group/item px-2 pb-0.5')}>
|
||||||
ref={ref}
|
<div className="relative">
|
||||||
className={classnames(className, 'block group/item px-2 pb-0.5')}
|
|
||||||
style={itemStyles}
|
|
||||||
>
|
|
||||||
<div className="relative w-full">
|
|
||||||
<Button
|
<Button
|
||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -258,7 +245,6 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
justify="start"
|
justify="start"
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'w-full',
|
|
||||||
editing && 'focus-within:border-focus',
|
editing && 'focus-within:border-focus',
|
||||||
active
|
active
|
||||||
? 'bg-highlight text-gray-900'
|
? 'bg-highlight text-gray-900'
|
||||||
@@ -315,7 +301,6 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
|||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
active,
|
active,
|
||||||
sidebarWidth,
|
|
||||||
onMove,
|
onMove,
|
||||||
onEnd,
|
onEnd,
|
||||||
}: DraggableSidebarItemProps) {
|
}: DraggableSidebarItemProps) {
|
||||||
@@ -358,7 +343,6 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
|||||||
requestId={requestId}
|
requestId={requestId}
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
active={active}
|
active={active}
|
||||||
sidebarWidth={sidebarWidth}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
|
||||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||||
import { useRoutes } from '../hooks/useRoutes';
|
import { useRoutes } from '../hooks/useRoutes';
|
||||||
@@ -36,17 +34,24 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
...workspaceItems,
|
...workspaceItems,
|
||||||
'-----',
|
{
|
||||||
|
type: 'separator',
|
||||||
|
label: activeWorkspace?.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
leftSlot: <Icon icon="trash" />,
|
||||||
|
onSelect: () => deleteWorkspace.mutate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
label: 'Actions',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'New Workspace',
|
label: 'New Workspace',
|
||||||
leftSlot: <Icon icon="plus" />,
|
leftSlot: <Icon icon="plus" />,
|
||||||
onSelect: () => createWorkspace.mutate({ name: 'New Workspace' }),
|
onSelect: () => createWorkspace.mutate({ name: 'New Workspace' }),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Delete Workspace',
|
|
||||||
leftSlot: <Icon icon="trash" />,
|
|
||||||
onSelect: () => deleteWorkspace.mutate(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}, [workspaces, activeWorkspaceId]);
|
}, [workspaces, activeWorkspaceId]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { VStack } from './Stacks';
|
|||||||
|
|
||||||
export type DropdownItem =
|
export type DropdownItem =
|
||||||
| {
|
| {
|
||||||
|
type?: 'default';
|
||||||
label: string;
|
label: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
@@ -15,7 +16,10 @@ export type DropdownItem =
|
|||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
onSelect?: () => void;
|
onSelect?: () => void;
|
||||||
}
|
}
|
||||||
| '-----';
|
| {
|
||||||
|
type: 'separator';
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface DropdownProps {
|
export interface DropdownProps {
|
||||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
||||||
@@ -93,7 +97,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
let nextIndex = (currIndex ?? 0) - 1;
|
let nextIndex = (currIndex ?? 0) - 1;
|
||||||
const maxTries = items.length;
|
const maxTries = items.length;
|
||||||
for (let i = 0; i < maxTries; i++) {
|
for (let i = 0; i < maxTries; i++) {
|
||||||
if (items[nextIndex] === '-----') {
|
if (items[nextIndex]?.type === 'separator') {
|
||||||
nextIndex--;
|
nextIndex--;
|
||||||
} else if (nextIndex < 0) {
|
} else if (nextIndex < 0) {
|
||||||
nextIndex = items.length - 1;
|
nextIndex = items.length - 1;
|
||||||
@@ -110,7 +114,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
let nextIndex = (currIndex ?? -1) + 1;
|
let nextIndex = (currIndex ?? -1) + 1;
|
||||||
const maxTries = items.length;
|
const maxTries = items.length;
|
||||||
for (let i = 0; i < maxTries; i++) {
|
for (let i = 0; i < maxTries; i++) {
|
||||||
if (items[nextIndex] === '-----') {
|
if (items[nextIndex]?.type === 'separator') {
|
||||||
nextIndex++;
|
nextIndex++;
|
||||||
} else if (nextIndex >= items.length) {
|
} else if (nextIndex >= items.length) {
|
||||||
nextIndex = 0;
|
nextIndex = 0;
|
||||||
@@ -122,26 +126,29 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const containerStyles: CSSProperties = useMemo(() => {
|
const { containerStyles, triangleStyles } = useMemo<{
|
||||||
|
containerStyles: CSSProperties;
|
||||||
|
triangleStyles: CSSProperties;
|
||||||
|
}>(() => {
|
||||||
const docWidth = document.documentElement.getBoundingClientRect().width;
|
const docWidth = document.documentElement.getBoundingClientRect().width;
|
||||||
const spaceRemaining = docWidth - triggerRect.left;
|
const spaceRemaining = docWidth - triggerRect.left;
|
||||||
if (spaceRemaining < 200) {
|
const top = triggerRect?.bottom + 5;
|
||||||
return {
|
const onRight = spaceRemaining < 200;
|
||||||
top: triggerRect?.bottom,
|
const containerStyles = onRight
|
||||||
right: 0,
|
? { top, right: docWidth - triggerRect?.right }
|
||||||
};
|
: { top, left: triggerRect?.left };
|
||||||
}
|
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
|
||||||
return {
|
const triangleStyles = onRight
|
||||||
top: triggerRect?.bottom,
|
? { right: triggerRect.width / 2, marginRight: '-0.2rem', ...size }
|
||||||
left: triggerRect?.left,
|
: { left: triggerRect.width / 2, marginLeft: '-0.2rem', ...size };
|
||||||
};
|
return { containerStyles, triangleStyles };
|
||||||
}, [triggerRect]);
|
}, [triggerRect]);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(i: DropdownItem) => {
|
(i: DropdownItem) => {
|
||||||
onClose();
|
onClose();
|
||||||
setSelectedIndex(null);
|
setSelectedIndex(null);
|
||||||
if (i !== '-----') {
|
if (i.type !== 'separator') {
|
||||||
i.onSelect?.();
|
i.onSelect?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -160,6 +167,11 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
style={containerStyles}
|
style={containerStyles}
|
||||||
className={classnames(className, 'pointer-events-auto fixed z-50')}
|
className={classnames(className, 'pointer-events-auto fixed z-50')}
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
style={triangleStyles}
|
||||||
|
aria-hidden
|
||||||
|
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
|
||||||
|
/>
|
||||||
{containerStyles && (
|
{containerStyles && (
|
||||||
<VStack
|
<VStack
|
||||||
space={0.5}
|
space={0.5}
|
||||||
@@ -169,11 +181,12 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
className,
|
className,
|
||||||
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
|
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
|
||||||
'border-gray-200 overflow-auto m-1',
|
'border-gray-200 overflow-auto mb-1 mx-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
if (item === '-----') return <Separator key={i} className="my-1.5" />;
|
if (item.type === 'separator')
|
||||||
|
return <Separator key={i} className="my-1.5" label={item.label} />;
|
||||||
if (item.hidden) return null;
|
if (item.hidden) return null;
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -211,7 +224,7 @@ function MenuItem({ className, focused, item, onSelect, ...props }: MenuItemProp
|
|||||||
[focused],
|
[focused],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (item === '-----') return <Separator className="my-1.5" />;
|
if (item.type === 'separator') return <Separator className="my-1.5" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -4,19 +4,26 @@ interface Props {
|
|||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
variant?: 'primary' | 'secondary';
|
variant?: 'primary' | 'secondary';
|
||||||
className?: string;
|
className?: string;
|
||||||
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Separator({ className, variant = 'primary', orientation = 'horizontal' }: Props) {
|
export function Separator({
|
||||||
|
className,
|
||||||
|
variant = 'primary',
|
||||||
|
orientation = 'horizontal',
|
||||||
|
label,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div role="separator" className={classnames(className, 'flex items-center')}>
|
||||||
role="separator"
|
{label && <div className="text-xs text-gray-500 mx-2 whitespace-nowrap">{label}</div>}
|
||||||
className={classnames(
|
<div
|
||||||
className,
|
className={classnames(
|
||||||
variant === 'primary' && 'bg-highlight',
|
variant === 'primary' && 'bg-highlight',
|
||||||
variant === 'secondary' && 'bg-highlightSecondary',
|
variant === 'secondary' && 'bg-highlightSecondary',
|
||||||
orientation === 'horizontal' && 'w-full h-[1px]',
|
orientation === 'horizontal' && 'w-full h-[1px]',
|
||||||
orientation === 'vertical' && 'h-full w-[1px]',
|
orientation === 'vertical' && 'h-full w-[1px]',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ module.exports = {
|
|||||||
opacity: {
|
opacity: {
|
||||||
"disabled": "0.3"
|
"disabled": "0.3"
|
||||||
},
|
},
|
||||||
|
fontSize: {
|
||||||
|
"xs": "0.8rem"
|
||||||
|
},
|
||||||
height: {
|
height: {
|
||||||
"xs": "1.5rem",
|
"xs": "1.5rem",
|
||||||
"sm": "2.00rem",
|
"sm": "2.00rem",
|
||||||
|
|||||||
Reference in New Issue
Block a user