Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 11 additions & 103 deletions packages/web/src/components/row-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useEffect, useId, useState, type ReactNode} from 'react';
import type {ReactNode} from 'react';
import {Link} from 'react-router';
import {
ContextMenu,
Expand All @@ -22,7 +22,6 @@ export function RowList({header, children}: {header?: ReactNode; children: React
type RowProps = {
icon?: ReactNode;
title: ReactNode;
tooltip?: ReactNode;
// A line under the title, wrapping as long as it needs, the row growing with it.
subtitle?: ReactNode;
// Cells after the title, right-aligned in the order given.
Expand All @@ -36,31 +35,11 @@ type RowProps = {
// Its item out into the gutter of what holds the list (`--bleed`), and the row filling it, so its
// hover reaches the gutter while its text stays on the line; beside something else (`after`), the
// two share the item, the row's hover on its side only.
const ITEM = 'relative -mx-(--bleed)';
const ITEM = '-mx-(--bleed)';
const ROW =
'flex w-full min-w-0 items-center gap-3 px-(--bleed) text-left text-sm hover:bg-muted/50 active:bg-muted/50';
const TOOLTIP_CLEARANCE_PX = 112;

export function Row({to, onClick, icon, title, tooltip, subtitle, cells, menu, after}: RowProps) {
const [tooltipHovered, setTooltipHovered] = useState(false);
const [tooltipFocused, setTooltipFocused] = useState(false);
const [tooltipDismissed, setTooltipDismissed] = useState(false);
const [tooltipSide, setTooltipSide] = useState<'top' | 'bottom'>('top');
const tooltipId = useId();
const tooltipOpen = (tooltipHovered || tooltipFocused) && !tooltipDismissed;
useEffect(() => {
if (!tooltipOpen) {
return;
}

const dismiss = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setTooltipDismissed(true);
}
};
document.addEventListener('keydown', dismiss);
return () => document.removeEventListener('keydown', dismiss);
}, [tooltipOpen]);
export function Row({to, onClick, icon, title, subtitle, cells, menu, after}: RowProps) {
const className = cn(ROW, subtitle ? 'min-h-10 py-2' : 'h-10');
const content = (
<>
Expand All @@ -76,103 +55,32 @@ export function Row({to, onClick, icon, title, tooltip, subtitle, cells, menu, a
{cells}
</>
);
const tooltipElement = tooltip && (
<span
id={tooltipId}
role="tooltip"
hidden={!tooltipOpen}
className={cn(
'absolute right-0 z-50 flex max-h-24 w-max max-w-[calc(100vw-2rem)] flex-col items-start gap-0.5 overflow-y-auto rounded-md bg-foreground px-3 py-1.5 text-left text-xs break-all whitespace-normal text-background shadow-sm',
tooltipSide === 'top' ? 'bottom-full' : 'top-full',
)}
>
{tooltip}
<span
aria-hidden
className={cn(
'absolute right-4 size-2 rotate-45 bg-foreground',
tooltipSide === 'top' ? '-bottom-1' : '-top-1',
)}
/>
</span>
);
const placeTooltip = (element: HTMLElement) => {
setTooltipSide(element.getBoundingClientRect().top < TOOLTIP_CLEARANCE_PX ? 'bottom' : 'top');
setTooltipDismissed(false);
};
const showHoveredTooltip = tooltip
? (event: {currentTarget: HTMLElement}) => {
placeTooltip(event.currentTarget);
setTooltipHovered(true);
}
: undefined;
const hideHoveredTooltip = tooltip ? () => setTooltipHovered(false) : undefined;
const showFocusedTooltip = tooltip
? (event: {currentTarget: HTMLElement}) => {
placeTooltip(event.currentTarget);
setTooltipFocused(true);
}
: undefined;
const hideFocusedTooltip = tooltip ? () => setTooltipFocused(false) : undefined;
const control =
const link =
to !== undefined ? (
<Link
to={to}
className={className}
aria-describedby={tooltip ? tooltipId : undefined}
onFocus={showFocusedTooltip}
onBlur={hideFocusedTooltip}
>
<Link to={to} className={className}>
{content}
</Link>
) : (
<button
type="button"
onClick={onClick}
className={className}
aria-describedby={tooltip ? tooltipId : undefined}
onFocus={showFocusedTooltip}
onBlur={hideFocusedTooltip}
>
<button type="button" onClick={onClick} className={className}>
{content}
</button>
);
const row = after ? (
const control = after ? (
<div className="flex items-center">
{control}
{link}
<div className="flex shrink-0 items-center pr-(--bleed)">{after}</div>
</div>
) : (
control
);
const item = (
<>
{row}
{tooltipElement}
</>
link
);

if (!menu) {
return (
<li className={ITEM} onMouseEnter={showHoveredTooltip} onMouseLeave={hideHoveredTooltip}>
{item}
</li>
);
return <li className={ITEM}>{control}</li>;
}

return (
<ContextMenu>
<ContextMenuTrigger
render={
<li
className={ITEM}
onMouseEnter={showHoveredTooltip}
onMouseLeave={hideHoveredTooltip}
/>
}
>
{item}
</ContextMenuTrigger>
<ContextMenuTrigger render={<li className={ITEM} />}>{control}</ContextMenuTrigger>
<ContextMenuContent>{menu}</ContextMenuContent>
</ContextMenu>
);
Expand Down
142 changes: 0 additions & 142 deletions packages/web/src/pages/connections-page.test.tsx

This file was deleted.

Loading
Loading