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
77 changes: 77 additions & 0 deletions apps/code/src/renderer/components/ui/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Tooltip } from "@components/ui/Tooltip";
import { Flex, Button as RadixButton, Text } from "@radix-ui/themes";
import {
type ComponentPropsWithoutRef,
forwardRef,
type ReactNode,
} from "react";

export type ButtonProps = ComponentPropsWithoutRef<typeof RadixButton> & {
/** Primary tooltip explaining what the button does. */
tooltipContent?: ReactNode;
/**
* When non-null and the button is disabled, shown after "Disabled because" in the tooltip.
* Must be null when the action is allowed.
*/
disabledReason?: string | null;
};

function disabledBecauseLabel(detail: string): string {
const d = detail.trim().replace(/\.$/, "");
return `Disabled because ${d}.`;
}

function buildTooltipContent(
tooltipContent: ReactNode | undefined,
disabledReason: string | null | undefined,
disabled: boolean | undefined,
): ReactNode | undefined {
const reason = disabled ? disabledReason : null;
if (tooltipContent != null && reason) {
return (
<Flex direction="column" gap="2" style={{ maxWidth: 280 }}>
<Text as="span" size="1" style={{ color: "var(--gray-12)" }}>
{tooltipContent}
</Text>
<Text as="span" color="gray" size="1" style={{ lineHeight: 1.45 }}>
{disabledBecauseLabel(reason)}
</Text>
</Flex>
);
}
if (reason) {
return disabledBecauseLabel(reason);
}
if (tooltipContent != null) {
return tooltipContent;
}
return undefined;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
function Button({ tooltipContent, disabledReason, disabled, ...props }, ref) {
const tip = buildTooltipContent(
tooltipContent,
disabledReason ?? null,
disabled,
);

const button = <RadixButton ref={ref} disabled={disabled} {...props} />;

if (tip === undefined) {
return button;
}

// Disabled buttons don't receive pointer events; span keeps the tooltip hover target.
const trigger =
disabled === true ? (
<span className="inline-flex cursor-not-allowed">{button}</span>
) : (
button
);

return <Tooltip content={tip}>{trigger}</Tooltip>;
},
);

Button.displayName = "Button";
Original file line number Diff line number Diff line change
Expand Up @@ -353,17 +353,43 @@ export function InboxSignalsTab() {
direction="column"
tabIndex={0}
className="outline-none"
// Clicking a row/button/checkbox would normally move browser focus to that
// element, losing the container's focus and breaking arrow-key navigation.
// Intercept mousedown to redirect focus back to the container instead.
// Text fields are exempt so the search box can still receive focus normally.
onMouseDownCapture={(e) => {
const target = e.target as HTMLElement;
if (target.closest("[data-report-id]")) {
if (
target.closest(
"input, textarea, select, [contenteditable='true']",
)
) {
return;
}
if (
target.closest(
"[data-report-id], button, [role='checkbox']",
)
) {
focusListPane();
}
}}
// Same redirect for focus arriving via keyboard (Tab) — if focus lands
// inside a row element rather than on the container itself, pull it back up.
onFocusCapture={(e) => {
const target = e.target as HTMLElement;
if (
target.closest(
"input, textarea, select, [contenteditable='true']",
)
) {
return;
}
if (
target !== leftPaneRef.current &&
target.closest("[data-report-id]")
target.closest(
"[data-report-id], button, [role='checkbox']",
)
) {
focusListPane();
}
Expand Down Expand Up @@ -468,18 +494,21 @@ export function InboxSignalsTab() {
display: "flex",
alignItems: "center",
justifyContent: "center",
pointerEvents: "none",
background:
"linear-gradient(to bottom, transparent 0%, var(--color-background) 30%)",
}}
>
{!hasSignalSources ? (
<WelcomePane onEnableInbox={() => setSourcesDialogOpen(true)} />
) : (
<WarmingUpPane
onConfigureSources={() => setSourcesDialogOpen(true)}
enabledProducts={enabledProducts}
/>
)}
<Box style={{ pointerEvents: "auto" }}>
{!hasSignalSources ? (
<WelcomePane onEnableInbox={() => setSourcesDialogOpen(true)} />
) : (
<WarmingUpPane
onConfigureSources={() => setSourcesDialogOpen(true)}
enabledProducts={enabledProducts}
/>
)}
</Box>
</Box>
</Box>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export function ReportListPane({
{reports.map((report, index) => (
<ReportListRow
key={report.id}
index={index}
report={report}
index={index}
isSelected={selectedReportId === report.id}
isChecked={selectedReportIds.includes(report.id)}
onClick={() => onSelectReport(report.id)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,6 @@ export function ReportListRow({
},
);

const isStrongSignal = report.total_weight >= 65 || report.signal_count >= 20;
const isMediumSignal = report.total_weight >= 30 || report.signal_count >= 6;
const strengthColor = isStrongSignal
? "var(--green-9)"
: isMediumSignal
? "var(--yellow-9)"
: "var(--gray-8)";
const strengthLabel = isStrongSignal
? "strong"
: isMediumSignal
? "medium"
: "light";

const isReady = report.status === "ready";

const isInteractiveTarget = (target: EventTarget | null): boolean => {
Expand All @@ -67,6 +54,14 @@ export function ReportListRow({
onToggleChecked();
};

const rowBgClass = isSelected
? "bg-gray-3"
: isChecked
? "bg-gray-2"
: report.is_suggested_reviewer
? "bg-blue-2"
: "";

return (
<motion.div
role="button"
Expand Down Expand Up @@ -96,23 +91,21 @@ export function ReportListRow({
handleToggleChecked(e);
}
}}
className="w-full cursor-pointer overflow-hidden border-gray-5 border-b py-2 pr-3 pl-2 text-left transition-colors hover:bg-gray-2"
style={{
backgroundColor: isSelected
? "var(--gray-3)"
: isChecked
? "var(--gray-2)"
: report.is_suggested_reviewer
? "var(--blue-2)"
: "transparent",
}}
className={[
"relative isolate w-full cursor-pointer overflow-hidden border-gray-5 border-b py-2 pr-3 pl-2 text-left",
"before:pointer-events-none before:absolute before:inset-0 before:z-[1] before:bg-gray-12 before:opacity-0 hover:before:opacity-[0.07]",
rowBgClass,
]
.filter(Boolean)
.join(" ")}
>
<Flex align="start" justify="between" gap="3">
<Flex align="start" justify="between" gap="3" className="relative z-[2]">
<Flex align="start" gap="2" style={{ minWidth: 0, flex: 1 }}>
<Flex align="center" justify="center" className="shrink-0 pt-0.5">
<Checkbox
size="1"
checked={isChecked}
className="mt-0.5"
tabIndex={-1}
onMouseDown={(e) => {
e.preventDefault();
Expand All @@ -129,16 +122,16 @@ export function ReportListRow({
/>
</Flex>

<Flex direction="column" gap="1" style={{ minWidth: 0, flex: 1 }}>
<Flex direction="column" gap="0.5" style={{ minWidth: 0, flex: 1 }}>
<Flex align="start" gapX="2" className="min-w-0">
<Flex
direction="column"
align="center"
gap="0.5"
className="shrink-0 pt-1"
>
{(report.source_products ?? []).length > 0 ? (
(report.source_products ?? []).map((sp) => {
{(report.source_products ?? []).length > 0 && (
<Flex
direction="column"
align="center"
gap="0.5"
className="shrink-0 pt-1"
>
{(report.source_products ?? []).map((sp) => {
const meta = SOURCE_PRODUCT_META[sp];
if (!meta) return null;
const { Icon } = meta;
Expand All @@ -147,16 +140,9 @@ export function ReportListRow({
<Icon size={12} />
</span>
);
})
) : (
<span
title={`Signal strength: ${strengthLabel}`}
aria-hidden
className="mt-1 inline-block h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: strengthColor }}
/>
)}
</Flex>
})}
</Flex>
)}

<Flex
align="center"
Expand Down
Loading
Loading