mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
fix: prevent nested buttons in tooltip button (#12177)
Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
|
|
||||||
interface ChatActionTooltipProps {
|
interface ChatActionTooltipProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
tooltip: string | React.ReactNode;
|
tooltip: string | React.ReactNode;
|
||||||
ariaLabel: string;
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatActionTooltip({
|
export function ChatActionTooltip({
|
||||||
@@ -12,14 +12,12 @@ export function ChatActionTooltip({
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
}: ChatActionTooltipProps) {
|
}: ChatActionTooltipProps) {
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip
|
||||||
tooltip={tooltip}
|
content={tooltip}
|
||||||
ariaLabel={ariaLabel}
|
|
||||||
disabled={false}
|
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
tooltipClassName="bg-white text-black text-xs font-medium leading-5"
|
tooltipClassName="bg-white text-black text-xs font-medium leading-5"
|
||||||
>
|
>
|
||||||
{children}
|
<span data-aria-label={ariaLabel}>{children}</span>
|
||||||
</TooltipButton>
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { cn } from "#/utils/utils";
|
import { cn } from "#/utils/utils";
|
||||||
import { CopyToClipboardButton } from "#/components/shared/buttons/copy-to-clipboard-button";
|
import { CopyToClipboardButton } from "#/components/shared/buttons/copy-to-clipboard-button";
|
||||||
import { OpenHandsSourceType } from "#/types/core/base";
|
import { OpenHandsSourceType } from "#/types/core/base";
|
||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
@@ -53,7 +53,7 @@ export function ChatMessage({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl relative w-fit max-w-full last:mb-4",
|
"rounded-xl relative w-fit max-w-full last:mb-4",
|
||||||
"flex flex-col gap-2",
|
"flex flex-col gap-2",
|
||||||
type === "user" && " p-4 bg-tertiary self-end",
|
type === "user" && "p-4 bg-tertiary self-end",
|
||||||
type === "agent" && "mt-6 w-full max-w-full bg-transparent",
|
type === "agent" && "mt-6 w-full max-w-full bg-transparent",
|
||||||
isFromPlanningAgent && "border border-[#597ff4] bg-tertiary p-4",
|
isFromPlanningAgent && "border border-[#597ff4] bg-tertiary p-4",
|
||||||
)}
|
)}
|
||||||
@@ -67,21 +67,16 @@ export function ChatMessage({
|
|||||||
>
|
>
|
||||||
{actions?.map((action, index) =>
|
{actions?.map((action, index) =>
|
||||||
action.tooltip ? (
|
action.tooltip ? (
|
||||||
<TooltipButton
|
<StyledTooltip key={index} content={action.tooltip} placement="top">
|
||||||
key={index}
|
|
||||||
tooltip={action.tooltip}
|
|
||||||
ariaLabel={action.tooltip}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
className="button-base p-1 cursor-pointer"
|
className="button-base p-1 cursor-pointer"
|
||||||
aria-label={`Action ${index + 1}`}
|
aria-label={action.tooltip}
|
||||||
>
|
>
|
||||||
{action.icon}
|
{action.icon}
|
||||||
</button>
|
</button>
|
||||||
</TooltipButton>
|
</StyledTooltip>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
@@ -112,6 +107,7 @@ export function ChatMessage({
|
|||||||
>
|
>
|
||||||
<MarkdownRenderer includeStandard>{message}</MarkdownRenderer>
|
<MarkdownRenderer includeStandard>{message}</MarkdownRenderer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
|
|
||||||
interface GitControlBarTooltipWrapperProps {
|
interface GitControlBarTooltipWrapperProps {
|
||||||
tooltipMessage: string;
|
tooltipMessage: string;
|
||||||
@@ -18,16 +18,15 @@ export function GitControlBarTooltipWrapper({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip
|
||||||
tooltip={tooltipMessage}
|
content={tooltipMessage}
|
||||||
ariaLabel={tooltipMessage}
|
|
||||||
testId={testId}
|
|
||||||
placement="top"
|
placement="top"
|
||||||
className="hover:opacity-100"
|
|
||||||
tooltipClassName="bg-white text-black"
|
|
||||||
showArrow
|
showArrow
|
||||||
|
tooltipClassName="bg-white text-black"
|
||||||
>
|
>
|
||||||
{children}
|
<span data-testid={testId} className="hover:opacity-100">
|
||||||
</TooltipButton>
|
{children}
|
||||||
|
</span>
|
||||||
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { ConversationStatus } from "#/types/conversation-status";
|
import { ConversationStatus } from "#/types/conversation-status";
|
||||||
import { cn, getConversationStatusLabel } from "#/utils/utils";
|
import { cn, getConversationStatusLabel } from "#/utils/utils";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
|
|
||||||
interface ConversationStatusIndicatorProps {
|
interface ConversationStatusIndicatorProps {
|
||||||
conversationStatus: ConversationStatus;
|
conversationStatus: ConversationStatus;
|
||||||
@@ -17,7 +17,7 @@ export function ConversationStatusIndicator({
|
|||||||
const conversationStatusBackgroundColor = useMemo(() => {
|
const conversationStatusBackgroundColor = useMemo(() => {
|
||||||
switch (conversationStatus) {
|
switch (conversationStatus) {
|
||||||
case "STOPPED":
|
case "STOPPED":
|
||||||
return "bg-[#3C3C49]"; // Inactive/stopped - grey
|
return "bg-[#3C3C49]";
|
||||||
case "RUNNING":
|
case "RUNNING":
|
||||||
return "bg-[#1FBD53]"; // Running/online - green
|
return "bg-[#1FBD53]"; // Running/online - green
|
||||||
case "STARTING":
|
case "STARTING":
|
||||||
@@ -34,13 +34,10 @@ export function ConversationStatusIndicator({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip
|
||||||
tooltip={statusLabel}
|
content={statusLabel}
|
||||||
ariaLabel={statusLabel}
|
|
||||||
placement="right"
|
placement="right"
|
||||||
showArrow
|
showArrow
|
||||||
asSpan
|
|
||||||
className="p-0 border-0 bg-transparent hover:opacity-100"
|
|
||||||
tooltipClassName="bg-[#1a1a1a] text-white text-xs shadow-lg"
|
tooltipClassName="bg-[#1a1a1a] text-white text-xs shadow-lg"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -49,6 +46,6 @@ export function ConversationStatusIndicator({
|
|||||||
conversationStatusBackgroundColor,
|
conversationStatusBackgroundColor,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</TooltipButton>
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { GitProviderIcon } from "#/components/shared/git-provider-icon";
|
import { GitProviderIcon } from "#/components/shared/git-provider-icon";
|
||||||
import { GitRepository } from "#/types/git";
|
import { GitRepository } from "#/types/git";
|
||||||
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
|
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
|
||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
|
|
||||||
interface MicroagentManagementAccordionTitleProps {
|
interface MicroagentManagementAccordionTitleProps {
|
||||||
repository: GitRepository;
|
repository: GitRepository;
|
||||||
@@ -14,17 +14,17 @@ export function MicroagentManagementAccordionTitle({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<GitProviderIcon gitProvider={repository.git_provider} />
|
<GitProviderIcon gitProvider={repository.git_provider} />
|
||||||
<TooltipButton
|
|
||||||
tooltip={repository.full_name}
|
<StyledTooltip content={repository.full_name} placement="bottom">
|
||||||
ariaLabel={repository.full_name}
|
<span
|
||||||
testId="repository-name-tooltip"
|
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[194px] translate-y-[-1px]"
|
||||||
placement="bottom"
|
data-testid="repository-name-tooltip"
|
||||||
asSpan
|
>
|
||||||
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[194px] translate-y-[-1px]"
|
{repository.full_name}
|
||||||
>
|
</span>
|
||||||
<span>{repository.full_name}</span>
|
</StyledTooltip>
|
||||||
</TooltipButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MicroagentManagementAddMicroagentButton repository={repository} />
|
<MicroagentManagementAddMicroagentButton repository={repository} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import ListIcon from "#/icons/list.svg?react";
|
import ListIcon from "#/icons/list.svg?react";
|
||||||
import { TooltipButton } from "./tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
import { cn } from "#/utils/utils";
|
import { cn } from "#/utils/utils";
|
||||||
|
|
||||||
interface ConversationPanelButtonProps {
|
interface ConversationPanelButtonProps {
|
||||||
@@ -17,23 +17,28 @@ export function ConversationPanelButton({
|
|||||||
}: ConversationPanelButtonProps) {
|
}: ConversationPanelButtonProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const label = t(I18nKey.SIDEBAR$CONVERSATIONS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip content={label}>
|
||||||
testId="toggle-conversation-panel"
|
<button
|
||||||
tooltip={t(I18nKey.SIDEBAR$CONVERSATIONS)}
|
type="button"
|
||||||
ariaLabel={t(I18nKey.SIDEBAR$CONVERSATIONS)}
|
data-testid="toggle-conversation-panel"
|
||||||
onClick={onClick}
|
aria-label={label}
|
||||||
disabled={disabled}
|
onClick={onClick}
|
||||||
>
|
disabled={disabled}
|
||||||
<ListIcon
|
className="p-0 bg-transparent border-0"
|
||||||
width={24}
|
>
|
||||||
height={24}
|
<ListIcon
|
||||||
className={cn(
|
width={24}
|
||||||
"cursor-pointer",
|
height={24}
|
||||||
isOpen ? "text-white" : "text-[#B1B9D3]",
|
className={cn(
|
||||||
disabled && "opacity-50",
|
"cursor-pointer",
|
||||||
)}
|
isOpen ? "text-white" : "text-[#B1B9D3]",
|
||||||
/>
|
disabled && "opacity-50",
|
||||||
</TooltipButton>
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { NavLink } from "react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { TooltipButton } from "./tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
import RobotIcon from "#/icons/robot.svg?react";
|
import RobotIcon from "#/icons/robot.svg?react";
|
||||||
|
|
||||||
interface MicroagentManagementButtonProps {
|
interface MicroagentManagementButtonProps {
|
||||||
@@ -15,14 +16,21 @@ export function MicroagentManagementButton({
|
|||||||
const microagentManagement = t(I18nKey.MICROAGENT_MANAGEMENT$TITLE);
|
const microagentManagement = t(I18nKey.MICROAGENT_MANAGEMENT$TITLE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip content={microagentManagement}>
|
||||||
tooltip={microagentManagement}
|
<NavLink
|
||||||
ariaLabel={microagentManagement}
|
to="/microagent-management"
|
||||||
navLinkTo="/microagent-management"
|
data-testid="microagent-management-button"
|
||||||
testId="microagent-management-button"
|
aria-label={microagentManagement}
|
||||||
disabled={disabled}
|
tabIndex={disabled ? -1 : 0}
|
||||||
>
|
onClick={(e) => {
|
||||||
<RobotIcon width={28} height={28} />
|
if (disabled) {
|
||||||
</TooltipButton>
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={disabled ? "pointer-events-none opacity-50" : undefined}
|
||||||
|
>
|
||||||
|
<RobotIcon width={28} height={28} />
|
||||||
|
</NavLink>
|
||||||
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import { useLocation } from "react-router";
|
import { NavLink } from "react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { TooltipButton } from "./tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
import PlusIcon from "#/icons/u-plus.svg?react";
|
import PlusIcon from "#/icons/u-plus.svg?react";
|
||||||
|
import { cn } from "#/utils/utils";
|
||||||
|
|
||||||
interface NewProjectButtonProps {
|
interface NewProjectButtonProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NewProjectButton({ disabled = false }: NewProjectButtonProps) {
|
export function NewProjectButton({ disabled = false }: NewProjectButtonProps) {
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const startNewProject = t(I18nKey.CONVERSATION$START_NEW);
|
const startNewProject = t(I18nKey.CONVERSATION$START_NEW);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip
|
||||||
tooltip={startNewProject}
|
content={startNewProject}
|
||||||
ariaLabel={startNewProject}
|
placement="right"
|
||||||
navLinkTo="/"
|
tooltipClassName="bg-transparent"
|
||||||
testId="new-project-button"
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
>
|
||||||
<PlusIcon
|
<NavLink
|
||||||
width={24}
|
to="/"
|
||||||
height={24}
|
data-testid="new-project-button"
|
||||||
color={pathname === "/" ? "#ffffff" : "#B1B9D3"}
|
aria-label={startNewProject}
|
||||||
/>
|
tabIndex={disabled ? -1 : 0}
|
||||||
</TooltipButton>
|
onClick={(e) => {
|
||||||
|
if (disabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cn("inline-flex items-center justify-center", {
|
||||||
|
"pointer-events-none opacity-50": disabled,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<PlusIcon width={24} height={24} />
|
||||||
|
</NavLink>
|
||||||
|
</StyledTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
|
import { NavLink } from "react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import OpenHandsLogo from "#/assets/branding/openhands-logo.svg?react";
|
import OpenHandsLogo from "#/assets/branding/openhands-logo.svg?react";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { TooltipButton } from "./tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
|
|
||||||
export function OpenHandsLogoButton() {
|
export function OpenHandsLogoButton() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const tooltipText = t(I18nKey.BRANDING$OPENHANDS);
|
||||||
|
const ariaLabel = t(I18nKey.BRANDING$OPENHANDS_LOGO);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipButton
|
<StyledTooltip content={tooltipText}>
|
||||||
tooltip={t(I18nKey.BRANDING$OPENHANDS)}
|
<NavLink to="/" aria-label={ariaLabel}>
|
||||||
ariaLabel={t(I18nKey.BRANDING$OPENHANDS_LOGO)}
|
<OpenHandsLogo width={46} height={30} />
|
||||||
navLinkTo="/"
|
</NavLink>
|
||||||
>
|
</StyledTooltip>
|
||||||
<OpenHandsLogo width={46} height={30} />
|
|
||||||
</TooltipButton>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
32
frontend/src/components/shared/buttons/styled-tooltip.tsx
Normal file
32
frontend/src/components/shared/buttons/styled-tooltip.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Tooltip, TooltipProps } from "@heroui/react";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
|
export interface StyledTooltipProps {
|
||||||
|
children: ReactNode;
|
||||||
|
content: string | ReactNode;
|
||||||
|
tooltipClassName?: React.HTMLAttributes<HTMLDivElement>["className"];
|
||||||
|
placement?: TooltipProps["placement"];
|
||||||
|
showArrow?: boolean;
|
||||||
|
closeDelay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StyledTooltip({
|
||||||
|
children,
|
||||||
|
content,
|
||||||
|
tooltipClassName,
|
||||||
|
placement = "right",
|
||||||
|
showArrow = false,
|
||||||
|
closeDelay = 100,
|
||||||
|
}: StyledTooltipProps) {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={content}
|
||||||
|
closeDelay={closeDelay}
|
||||||
|
placement={placement}
|
||||||
|
className={tooltipClassName}
|
||||||
|
showArrow={showArrow}
|
||||||
|
>
|
||||||
|
<div className="inline-flex">{children}</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { Tooltip, TooltipProps } from "@heroui/react";
|
|
||||||
import React, { ReactNode } from "react";
|
|
||||||
import { NavLink } from "react-router";
|
|
||||||
import { cn } from "#/utils/utils";
|
|
||||||
|
|
||||||
export interface TooltipButtonProps {
|
|
||||||
children: ReactNode;
|
|
||||||
tooltip: string | ReactNode;
|
|
||||||
onClick?: () => void;
|
|
||||||
href?: string;
|
|
||||||
navLinkTo?: string;
|
|
||||||
ariaLabel: string;
|
|
||||||
testId?: string;
|
|
||||||
className?: React.HTMLAttributes<HTMLButtonElement>["className"];
|
|
||||||
tooltipClassName?: React.HTMLAttributes<HTMLDivElement>["className"];
|
|
||||||
disabled?: boolean;
|
|
||||||
placement?: TooltipProps["placement"];
|
|
||||||
showArrow?: boolean;
|
|
||||||
asSpan?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TooltipButton({
|
|
||||||
children,
|
|
||||||
tooltip,
|
|
||||||
onClick,
|
|
||||||
href,
|
|
||||||
navLinkTo,
|
|
||||||
ariaLabel,
|
|
||||||
testId,
|
|
||||||
className,
|
|
||||||
tooltipClassName,
|
|
||||||
disabled = false,
|
|
||||||
placement = "right",
|
|
||||||
showArrow = false,
|
|
||||||
asSpan = false,
|
|
||||||
}: TooltipButtonProps) {
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
|
||||||
if (onClick && !disabled) {
|
|
||||||
onClick();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isClickable = !!onClick && !disabled;
|
|
||||||
let buttonContent: React.ReactNode;
|
|
||||||
if (asSpan) {
|
|
||||||
if (isClickable) {
|
|
||||||
buttonContent = (
|
|
||||||
<span
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
onClick={handleClick}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
onClick();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"hover:opacity-80",
|
|
||||||
disabled && "opacity-50 cursor-not-allowed",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
aria-disabled={disabled}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
buttonContent = (
|
|
||||||
<span
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
className={cn(
|
|
||||||
"hover:opacity-80",
|
|
||||||
disabled && "opacity-50 cursor-not-allowed",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
aria-disabled={disabled}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buttonContent = (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
onClick={handleClick}
|
|
||||||
className={cn(
|
|
||||||
"hover:opacity-80",
|
|
||||||
disabled && "opacity-50 cursor-not-allowed",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content;
|
|
||||||
|
|
||||||
if (navLinkTo && !disabled) {
|
|
||||||
content = (
|
|
||||||
<NavLink
|
|
||||||
to={navLinkTo}
|
|
||||||
onClick={handleClick}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
cn(
|
|
||||||
"hover:opacity-80",
|
|
||||||
isActive ? "text-white" : "text-[#9099AC]",
|
|
||||||
className,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
} else if (navLinkTo && disabled) {
|
|
||||||
// If disabled and has navLinkTo, render a button that looks like a NavLink but doesn't navigate
|
|
||||||
content = (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
className={cn(
|
|
||||||
"text-[#9099AC]",
|
|
||||||
"opacity-50 cursor-not-allowed",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
} else if (href && !disabled) {
|
|
||||||
content = (
|
|
||||||
<a
|
|
||||||
href={href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
className={cn("hover:opacity-80", className)}
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else if (href && disabled) {
|
|
||||||
// If disabled and has href, render a button that looks like a link but doesn't navigate
|
|
||||||
content = (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={ariaLabel}
|
|
||||||
data-testid={testId}
|
|
||||||
className={cn("opacity-50 cursor-not-allowed", className)}
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
content = buttonContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
content={tooltip}
|
|
||||||
closeDelay={100}
|
|
||||||
placement={placement}
|
|
||||||
className={tooltipClassName}
|
|
||||||
showArrow={showArrow}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import { useSettings } from "#/hooks/query/use-settings";
|
|||||||
import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set";
|
import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set";
|
||||||
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
|
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
|
||||||
import { SettingsSwitch } from "#/components/features/settings/settings-switch";
|
import { SettingsSwitch } from "#/components/features/settings/settings-switch";
|
||||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip";
|
||||||
import QuestionCircleIcon from "#/icons/question-circle.svg?react";
|
import QuestionCircleIcon from "#/icons/question-circle.svg?react";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { SettingsInput } from "#/components/features/settings/settings-input";
|
import { SettingsInput } from "#/components/features/settings/settings-input";
|
||||||
@@ -693,13 +693,13 @@ function LlmSettingsScreen() {
|
|||||||
>
|
>
|
||||||
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||||
</SettingsSwitch>
|
</SettingsSwitch>
|
||||||
<TooltipButton
|
<StyledTooltip
|
||||||
tooltip={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
|
content={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
|
||||||
ariaLabel={t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
|
||||||
className="text-[#9099AC] hover:text-white cursor-help"
|
|
||||||
>
|
>
|
||||||
<QuestionCircleIcon width={16} height={16} />
|
<span className="text-[#9099AC] hover:text-white cursor-help">
|
||||||
</TooltipButton>
|
<QuestionCircleIcon width={16} height={16} />
|
||||||
|
</span>
|
||||||
|
</StyledTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{confirmationModeEnabled && (
|
{confirmationModeEnabled && (
|
||||||
|
|||||||
Reference in New Issue
Block a user