Better dropdown separator

This commit is contained in:
Gregory Schier
2023-03-25 11:06:05 -07:00
parent 41390e9142
commit 09c574bf30
6 changed files with 73 additions and 61 deletions

View File

@@ -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" /> : <></>,

View File

@@ -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}
/>
);
});

View File

@@ -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]);

View File

@@ -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

View File

@@ -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>
);
}

View File

@@ -10,6 +10,9 @@ module.exports = {
opacity: {
"disabled": "0.3"
},
fontSize: {
"xs": "0.8rem"
},
height: {
"xs": "1.5rem",
"sm": "2.00rem",