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