refactor(ui): update knowledge graph, chunk, metadata, agent log styles (#13518)

### What problem does this PR solve?

Update UI styles:
- **Dataset** > **Knowledge graph** tooltip
- **Dataset** > **Files** > **Manage metadata** modal
- **Dataset** > **Files** > **Modify Chunking Method** > **Auto
metadata** > **Manage generation settings** modal
- **Agent** > **Canvas (Ingestion pipeline)** > **Dataflow result**

### Type of change

- [x] Refactoring
This commit is contained in:
Jimmy Ben Klieve
2026-03-11 11:27:20 +08:00
committed by GitHub
parent 2133fd76a8
commit 507ba4ea20
32 changed files with 613 additions and 485 deletions

View File

@ -1,21 +1,35 @@
import { formatDate } from '@/utils/date';
import { formatBytes } from '@/utils/file-util';
import { useTranslation } from 'react-i18next';
type Props = {
size: number;
name: string;
create_date: string;
className?: string;
};
export default ({ size, name, create_date }: Props) => {
export default ({ size, name, create_date, className }: Props) => {
const sizeName = formatBytes(size);
const dateStr = formatDate(create_date);
const { t } = useTranslation();
return (
<div>
<h2 className="text-[16px] truncate">{name}</h2>
<div className="text-text-secondary text-[12px] pt-[5px]">
Size{sizeName} Uploaded Time{dateStr}
</div>
</div>
<header className={className}>
<h2 className="text-2xl font-semibold truncate">{name}</h2>
<dl
className="
text-text-secondary text-sm flex
[&_dt]:after:content-[':'] [&_dt]:after:me-[.5ch]
[&_dd]:me-[2ch]"
>
<dt>{t('chunk.size')}</dt>
<dd>{sizeName}</dd>
<dt>{t('chunk.uploadedTime')}</dt>
<dd>{dateStr}</dd>
</dl>
</header>
);
};

View File

@ -25,7 +25,7 @@ const Preview = ({
return (
<>
{fileType === 'pdf' && highlights && setWidthAndHeight && (
<section>
<section className="h-full">
<PdfPreviewer
className={className}
highlights={highlights}

View File

@ -11,10 +11,10 @@ import {
import { Spin } from '@/components/ui/spin';
// import FileError from '@/pages/document-viewer/file-error';
import { Authorization } from '@/constants/authorization';
import { cn } from '@/lib/utils';
import FileError from '@/pages/document-viewer/file-error';
import { getAuthorization } from '@/utils/authorization-util';
import { useCatchDocumentError } from './hooks';
import styles from './index.module.less';
type PdfLoaderProps = React.ComponentProps<typeof PdfLoader> & {
httpHeaders?: Record<string, string>;
};
@ -69,7 +69,11 @@ const PdfPreview = ({
return (
<div
className={`${styles.documentContainer} rounded-[10px] overflow-hidden ${className}`}
className={cn(
'relative size-full rounded overflow-hidden',
'[&_.pdfViewer.removePageBorders_.page]:last-of-type:mb-0',
className,
)}
>
<Loader
url={url}

View File

@ -1,14 +1,11 @@
import { PlusOutlined } from '@ant-design/icons';
import React, { useEffect, useRef, useState } from 'react';
import { cn } from '@/lib/utils';
import { Trash2 } from 'lucide-react';
import { Button } from '../ui/button';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '../ui/hover-card';
import { Input } from '../ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
interface EditTagsProps {
value?: string[];
onChange?: (tags: string[]) => void;
@ -59,28 +56,35 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
const forMap = (tag: string) => {
return (
<HoverCard key={tag}>
<HoverCardContent side="top">{tag}</HoverCardContent>
<HoverCardTrigger asChild>
<div className="w-fit flex items-center justify-center gap-2 border border-border-button px-2 py-1 rounded-sm bg-bg-card">
<Tooltip key={tag}>
<TooltipContent side="top">{tag}</TooltipContent>
<TooltipTrigger asChild>
<div
className={cn(
'w-fit h-8 flex items-center justify-center gap-2 border border-border-button rounded-sm bg-bg-card',
disabled ? 'px-2' : 'ps-2 pe-1',
)}
>
<div className="flex gap-2 items-center">
<div className="max-w-80 overflow-hidden text-ellipsis">
{tag}
</div>
{!disabled && (
<Trash2
size={14}
className="text-text-secondary hover:text-state-error"
<Button
variant="delete"
size="icon-xs"
onClick={(e) => {
e.preventDefault();
handleClose(tag);
}}
/>
>
<Trash2 className="size-[1em]" />
</Button>
)}
</div>
</div>
</HoverCardTrigger>
</HoverCard>
</TooltipTrigger>
</Tooltip>
);
};
@ -107,10 +111,11 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
)}
<div className="flex gap-2 py-1 flex-wrap">
{Array.isArray(tagChild) && tagChild.length > 0 && <>{tagChild}</>}
{!inputVisible && !disabled && (
<Button
variant="ghost"
className="w-fit flex items-center justify-center gap-2 bg-bg-card border-border-button border"
variant="outline"
size="icon"
onClick={showInput}
disabled={disabled}
data-testid={addButtonTestId}

View File

@ -65,10 +65,12 @@ export default function ListFilterBar({
filters,
className,
icon,
iconClassName,
filterGroup,
}: PropsWithChildren<IProps & Omit<CheckboxFormMultipleProps, 'setOpen'>> & {
className?: string;
icon?: ReactNode;
iconClassName?: string;
filterGroup?: Record<string, string[]>;
}) {
const filterCount = useMemo(() => {
@ -95,7 +97,10 @@ export default function ListFilterBar({
<div className="text-2xl font-semibold flex items-center gap-2.5">
{typeof icon === 'string' ? (
// <IconFont name={icon} className="size-6"></IconFont>
<HomeIcon name={`${icon}`} width={'32'} />
<HomeIcon
name={`${icon}`}
imgClass={cn('size-[1em]', iconClassName)}
/>
) : (
icon
)}

View File

@ -80,7 +80,7 @@ function TimelineContent({
return (
<div
data-slot="timeline-content"
className={cn('text-muted-foreground text-sm', className)}
className={cn('text-text-secondary text-sm', className)}
{...props}
/>
);
@ -102,7 +102,7 @@ function TimelineDate({
<Comp
data-slot="timeline-date"
className={cn(
'text-muted-foreground mb-1 block text-xs font-medium group-data-[orientation=vertical]/timeline:max-sm:h-4',
'text-text-secondary mb-1 block text-xs font-medium group-data-[orientation=vertical]/timeline:max-sm:h-4',
className,
)}
{...props}
@ -158,7 +158,10 @@ function TimelineItem({ step, className, ...props }: TimelineItemProps) {
<div
data-slot="timeline-item"
className={cn(
'group/timeline-item has-[+[data-completed]]:[&_[data-slot=timeline-separator]]:bg-primary relative flex flex-1 flex-col gap-0.5 group-data-[orientation=horizontal]/timeline:mt-8 group-data-[orientation=horizontal]/timeline:not-last:pe-8 group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-12',
'group/timeline-item has-[+[data-completed]]:[&_[data-slot=timeline-separator]]:bg-primary',
'relative flex flex-1 flex-col gap-0.5',
'group-data-[orientation=horizontal]/timeline:mt-8 group-data-[orientation=horizontal]/timeline:not-last:pe-8',
'group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-12',
className,
)}
data-completed={step <= activeStep || undefined}
@ -176,7 +179,12 @@ function TimelineSeparator({
<div
data-slot="timeline-separator"
className={cn(
'bg-primary/10 absolute self-start group-last/timeline-item:hidden group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.5 group-data-[orientation=horizontal]/timeline:w-[calc(100%-1rem-0.25rem)] group-data-[orientation=horizontal]/timeline:translate-x-4.5 group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-0.25rem)] group-data-[orientation=vertical]/timeline:w-0.5 group-data-[orientation=vertical]/timeline:-translate-x-1/2 group-data-[orientation=vertical]/timeline:translate-y-4.5',
'bg-primary/10 absolute self-start group-last/timeline-item:hidden',
'group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.5',
'group-data-[orientation=horizontal]/timeline:w-[calc(100%-1rem-0.25rem)] group-data-[orientation=horizontal]/timeline:translate-x-4.5',
'group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:-left-6',
'group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-0.25rem)] group-data-[orientation=vertical]/timeline:w-0.5',
'group-data-[orientation=vertical]/timeline:-translate-x-1/2 group-data-[orientation=vertical]/timeline:translate-y-4.5',
className,
)}
aria-hidden="true"
@ -208,10 +216,8 @@ interface TimelineIndicatorNodeProps {
indicatorBorderColor?: string;
}
interface TimelineNode
extends Omit<
React.HTMLAttributes<HTMLDivElement>,
'id' | 'title' | 'content'
>,
extends
Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'title' | 'content'>,
TimelineIndicatorNodeProps {
id: string | number;
title?: React.ReactNode;
@ -231,7 +237,7 @@ interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
nodeSize?: string | number;
onStepChange?: (step: number, id: string | number) => void;
orientation?: 'horizontal' | 'vertical';
lineStyle?: 'solid' | 'dashed';
lineStyle?: 'solid' | 'dashed' | 'none';
lineColor?: string;
indicatorColor?: string;
defaultValue?: number;
@ -254,7 +260,7 @@ const CustomTimeline = ({
}: CustomTimelineProps) => {
const [internalActiveStep, setInternalActiveStep] =
React.useState(defaultValue);
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
// const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
const currentActiveStep = activeStep ?? internalActiveStep;
const handleStepChange = (step: number, id: string | number) => {
@ -281,7 +287,7 @@ const CustomTimeline = ({
const _nodeSizeTemp =
isActive && _activeStyle?.nodeSize
? _activeStyle?.nodeSize
: node.nodeSize ?? nodeSize;
: (node.nodeSize ?? nodeSize);
const _nodeSize =
typeof _nodeSizeTemp === 'number'
? `${_nodeSizeTemp}px`
@ -302,25 +308,22 @@ const CustomTimeline = ({
>
<TimelineSeparator
className={cn(
'border-0.5 border-border-default',
'group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.1 group-data-[orientation=horizontal]/timeline:-translate-y-1/2',
'group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:w-0.1 group-data-[orientation=vertical]/timeline:-translate-x-1/2 ',
// `group-data-[orientation=horizontal]/timeline:w-[calc(100%-0.5rem-1rem)] group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-1rem)] group-data-[orientation=vertical]/timeline:translate-y-7 group-data-[orientation=horizontal]/timeline:translate-x-7`,
)}
style={{
border:
lineStyle === 'dashed'
? `1px dashed ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
: lineStyle === 'solid'
? `1px solid ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
: 'none',
borderStyle: lineStyle,
// borderColor: isActive ? _activeStyle.lineColor || _lineColor : _lineColor,
backgroundColor: 'transparent',
width:
orientation === 'horizontal'
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
? `calc(100% - ${_nodeSize})`
: '1px',
height:
orientation === 'vertical'
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
? `calc(100% - ${_nodeSize})`
: '1px',
transform: `translate(${
orientation === 'horizontal' ? `${_nodeSize}` : '0'
@ -330,9 +333,11 @@ const CustomTimeline = ({
<TimelineIndicator
className={cn(
'flex items-center justify-center p-1',
isCompleted && 'bg-primary border-primary',
!isCompleted && 'border-text-secondary bg-bg-base',
'flex items-center justify-center p-1 border bg-bg-card',
isCompleted
? 'border-border-accent text-text-primary'
: 'border-border-default text-text-secondary',
isActive && 'text-accent-primary bg-accent-primary/10',
)}
style={{
width: _nodeSize,
@ -347,29 +352,15 @@ const CustomTimeline = ({
// : isCompleted
// ? indicatorColor
// : '',
backgroundColor: isActive
? _activeStyle.indicatorBgColor ||
`rgba(${r}, ${g}, ${b}, 0.1)`
: isCompleted
? `rgba(${r}, ${g}, ${b}, 0.1)`
: '',
// backgroundColor: isActive
// ? _activeStyle.indicatorBgColor ||
// `rgba(${r}, ${g}, ${b}, 0.1)`
// : isCompleted
// ? `rgba(${r}, ${g}, ${b}, 0.1)`
// : '',
}}
>
{node.icon && (
<div
className={cn(
'text-current',
`w-[${_nodeSize}] h-[${_nodeSize}]`,
isActive &&
`text-primary w-[${_activeStyle.nodeSize || _nodeSize}] h-[${_activeStyle.nodeSize || _nodeSize}]`,
)}
style={{
color: isActive ? _activeStyle.iconColor : undefined,
}}
>
{node.icon}
</div>
)}
{node.icon}
</TimelineIndicator>
<TimelineHeader className="transform -translate-x-[40%] text-center">

View File

@ -88,9 +88,7 @@ const RaptorFormFields = ({
tooltip={t('useRaptorTip')}
className="text-sm w-1/4 whitespace-break-spaces"
>
<div className="w-auto xl:w-20 2xl:w-24 3xl:w-28 4xl:w-auto ">
{t('useRaptor')}
</div>
{t('useRaptor')}
</FormLabel>
<div className="w-3/4">
<FormControl>

View File

@ -86,7 +86,7 @@ const buttonVariants = cva(
// Static
// Button has no interaction transitions
static: '',
static: 'text-text-secondary',
},
size: {
auto: '',

View File

@ -1,7 +1,7 @@
'use client';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import { LucideCheck } from 'lucide-react';
import * as React from 'react';
import { cn } from '@/lib/utils';
@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer size-4 shrink-0 rounded-2xs border border-text-disabled outline-0 transition-colors bg-transparent',
'peer size-3.5 shrink-0 rounded-2xs border border-text-disabled outline-0 transition-colors bg-transparent',
'hover:border-border-default hover:bg-border-button',
'focus-visible:border-border-default focus-visible:bg-border-default',
'disabled:cursor-not-allowed disabled:opacity-50',
@ -22,10 +22,8 @@ const Checkbox = React.forwardRef<
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<Check className="size-3" />
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
<LucideCheck className="size-2.5 stroke-[3]" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));

View File

@ -102,13 +102,14 @@ const FormLabel = React.forwardRef<
return (
<Label
ref={ref}
className={cn(className, 'flex pb-0.5')}
className={cn(
className,
required && 'before:content-["*"] before:text-state-error',
)}
htmlFor={formItemId}
{...props}
>
{required && <span className="text-state-error">*</span>}
{props.children}
<span>{props.children}</span>
{tooltip && <FormTooltip tooltip={tooltip}></FormTooltip>}
</Label>
);

View File

@ -14,16 +14,19 @@ type RadioProps = {
disabled?: boolean;
onChange?: (checked: boolean) => void;
children?: React.ReactNode;
testId?: string;
};
} & Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'value' | 'checked' | 'onChange'
>;
function Radio({
className,
value,
checked,
disabled,
onChange,
children,
testId,
...props
}: RadioProps) {
const groupContext = useContext(RadioGroupContext);
const isControlled = checked !== undefined;
@ -57,24 +60,23 @@ function Radio({
>
<input
type="radio"
name={groupContext?.name}
value={value}
checked={isChecked}
onClick={handleClick}
disabled={mergedDisabled}
data-testid={testId}
className="peer absolute size-[1px] opacity-0"
className={cn('peer absolute size-[1px] opacity-0', className)}
{...props}
name={groupContext?.name}
/>
<div
className={cn(
'flex h-4 w-4 items-center justify-center rounded-full border border-border-button transition-colors',
'group-hover/radio:border-border-default hover:border-border-default',
'peer-focus:border-primary',
'flex h-4 w-4 items-center justify-center rounded-full text-border-button border border-current transition-colors',
'group-hover/radio:text-border-default hover:text-border-default',
'peer-focus:text-text-primary',
isChecked && 'border-primary bg-primary/10',
mergedDisabled && 'border-muted',
)}
data-testid={testId}
>
<div
className={cn(

View File

@ -1,5 +1,7 @@
import { cn } from '@/lib/utils';
import { supportsCssAnchor } from '@/utils/css-support';
import * as React from 'react';
import { useId } from 'react';
import { Button, ButtonVariants } from './button';
export declare type SegmentedValue = string | number;
export declare type SegmentedRawOption = SegmentedValue;
@ -60,71 +62,152 @@ export interface SegmentedProps extends Omit<
buttonSize?: ButtonVariants['size'];
}
export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
(
{
options,
value,
onChange,
className,
activeClassName,
itemClassName,
rounded = 'default',
sizeType = 'default',
buttonSize = 'default',
},
ref,
) => {
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
React.useEffect(() => {
setSelectedValue(value);
}, [value]);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
ref={ref}
className={cn(
'flex items-center p-1 gap-2 bg-bg-card',
segmentedVariants.round[rounded],
segmentedVariants.size[sizeType],
const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
supportsCssAnchor
? (
{
options,
value,
onChange,
className,
)}
>
{options.map((option) => {
const isObject = typeof option === 'object';
const actualValue = isObject ? option.value : option;
activeClassName,
itemClassName,
rounded = 'default',
sizeType = 'default',
buttonSize = 'default',
},
ref,
) => {
const anchorNamePrefix = useId().replace(/:/g, '');
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
React.useEffect(() => {
setSelectedValue(value);
}, [value]);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
ref={ref}
className={cn(
'flex items-center p-1 gap-2 bg-bg-card',
segmentedVariants.round[rounded],
segmentedVariants.size[sizeType],
className,
)}
>
{options.map((option) => {
const isObject = typeof option === 'object';
const actualValue = isObject ? option.value : option;
return (
<Button
key={actualValue}
type="button"
size={buttonSize}
variant="static"
console.log(actualValue);
return (
<Button
key={actualValue}
type="button"
size={buttonSize}
variant="static"
className={cn(
selectedValue === actualValue && 'text-text-primary',
itemClassName,
selectedValue === actualValue && activeClassName,
'relative z-10',
)}
onClick={() => handleOnChange(actualValue)}
style={{
anchorName: `--${anchorNamePrefix}-${String(actualValue).replace('/', '')}`,
}}
>
{isObject ? option.label : option}
</Button>
);
})}
<div
className={cn(
{
'text-text-primary bg-bg-base': selectedValue === actualValue,
},
itemClassName,
activeClassName && selectedValue === actualValue
? activeClassName
: '',
'absolute bg-bg-base rounded-sm transition-all',
)}
onClick={() => handleOnChange(actualValue)}
>
{isObject ? option.label : option}
</Button>
);
})}
</div>
);
},
style={{
positionAnchor: `--${anchorNamePrefix}-${String(selectedValue).replace('/', '')}`,
width: 'anchor-size(width)',
height: 'anchor-size(height)',
top: 'anchor(top)',
left: 'anchor(left)',
}}
/>
</div>
);
}
: (
{
options,
value,
onChange,
className,
activeClassName,
itemClassName,
rounded = 'default',
sizeType = 'default',
buttonSize = 'default',
},
ref,
) => {
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
React.useEffect(() => {
setSelectedValue(value);
}, [value]);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
ref={ref}
className={cn(
'flex items-center p-1 gap-2 bg-bg-card',
segmentedVariants.round[rounded],
segmentedVariants.size[sizeType],
className,
)}
>
{options.map((option) => {
const isObject = typeof option === 'object';
const actualValue = isObject ? option.value : option;
return (
<Button
key={actualValue}
type="button"
size={buttonSize}
variant="static"
className={cn(
{
'text-text-primary bg-bg-base':
selectedValue === actualValue,
},
itemClassName,
selectedValue === actualValue && activeClassName,
)}
onClick={() => handleOnChange(actualValue)}
>
{isObject ? option.label : option}
</Button>
);
})}
</div>
);
},
);
Segmented.displayName = 'Segmented';
export { Segmented };

View File

@ -16,15 +16,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-auto scrollbar-auto rounded-md whitespace-pre-wrap border bg-bg-base px-3 py-1.5 text-sm text-text-primary shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-w-[30vw]',
className,
)}
{...props}
/>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-auto scrollbar-auto rounded-md whitespace-pre-wrap border bg-bg-base px-3 py-1.5 text-sm text-text-primary shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-w-[30vw]',
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
@ -35,11 +37,12 @@ export const FormTooltip = ({ tooltip }: { tooltip: React.ReactNode }) => {
<Tooltip>
<TooltipTrigger
tabIndex={-1}
className="align-text-top"
onClick={(e) => {
e.preventDefault(); // Prevent clicking the tooltip from triggering form save
}}
>
<CircleQuestionMark className="size-3 ml-[2px] -translate-y-1" />
<CircleQuestionMark className="size-[.85em] ml-[.25em]" />
</TooltipTrigger>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>