feat: add InfoIcon component with tooltip support and integrate into TextField

This commit is contained in:
GW_MC
2025-12-19 20:08:39 +08:00
parent 737797f6dd
commit a0a9584a4d
4 changed files with 69 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
import type { AnyFieldMeta } from '@tanstack/react-form'; import type { AnyFieldMeta } from '@tanstack/react-form';
import { LucideEye, LucideEyeClosed } from 'lucide-react'; import { LucideEye, LucideEyeClosed } from 'lucide-react';
import { useCallback, useId, useState } from 'react'; import { useCallback, useId, useState } from 'react';
import { InfoIcon, type InfoIconProps } from '../info';
export type TextFieldProps = { export type TextFieldProps = {
label?: string; label?: string;
@@ -8,12 +9,13 @@ export type TextFieldProps = {
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
labelProps?: React.LabelHTMLAttributes<HTMLLabelElement>; labelProps?: React.LabelHTMLAttributes<HTMLLabelElement>;
labelDivProps?: React.HTMLAttributes<HTMLDivElement>; labelDivProps?: React.HTMLAttributes<HTMLDivElement>;
infoIconProps?: InfoIconProps;
} & React.InputHTMLAttributes<HTMLInputElement> & { } & React.InputHTMLAttributes<HTMLInputElement> & {
type?: 'password'; type?: 'password';
showPasswordToggle?: boolean; showPasswordToggle?: boolean;
}; };
export function TextField({ label, value, onChange, labelProps, labelDivProps, showPasswordToggle, ...rest }: TextFieldProps) { export function TextField({ label, value, onChange, labelProps, labelDivProps, showPasswordToggle, infoIconProps, ...rest }: TextFieldProps) {
const id = useId(); const id = useId();
const [isPasswordVisible, setIsPasswordVisible] = useState(false); const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const handlePasswordVisibilitySet = useCallback( const handlePasswordVisibilitySet = useCallback(
@@ -24,11 +26,13 @@ export function TextField({ label, value, onChange, labelProps, labelDivProps, s
}, },
[rest.type] [rest.type]
); );
return ( return (
<label htmlFor={id} style={{ display: 'block', marginBottom: 8 }} {...labelProps}> <label htmlFor={id} style={{ display: 'block', marginBottom: 8 }} {...labelProps}>
{label && ( {label && (
<div style={{ fontSize: 12, color: 'var(--gray-9)', marginBottom: 6 }} {...labelDivProps}> <div style={{ fontSize: 12, color: 'var(--gray-9)', marginBottom: 6, display: 'flex', alignItems: 'center' }} {...labelDivProps}>
{label} {label}
{infoIconProps && <InfoIcon {...infoIconProps} style={{ marginLeft: 4, verticalAlign: 'middle' }} />}
</div> </div>
)} )}
<div style={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 8 }}>

View File

@@ -0,0 +1,59 @@
import { Box } from '@radix-ui/themes';
import { Info, type LucideProps } from 'lucide-react';
import { Tooltip } from 'radix-ui';
import type { PropsWithChildren } from 'react';
export type InfoIconProps = PropsWithChildren<
{
tooltipContainerProps?: Omit<Tooltip.TooltipContentProps & React.RefAttributes<HTMLDivElement>, 'children'>;
} & Omit<LucideProps, 'ref'> &
React.RefAttributes<SVGSVGElement>
>;
export function InfoIcon({ tooltipContainerProps, children, ...iconProps }: InfoIconProps) {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Info size={16} {...iconProps} />
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
//
side="top"
align="center"
sideOffset={5}
alignOffset={0}
avoidCollisions={true}
style={{
color: 'black',
backgroundColor: 'white',
fontSize: 12,
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.3)',
border: '1px solid var(--gray-5)',
}}
{...tooltipContainerProps}
>
{children}
<Tooltip.Arrow className="TooltipArrow" fill="white" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}
export function TooltipContentContainer({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<Box
style={{
padding: '8px 12px',
color: 'black',
backgroundColor: 'white',
borderRadius: 4,
fontSize: 12,
}}
{...props}
>
{children}
</Box>
);
}

View File

@@ -12,6 +12,7 @@
"generate:openapi": "typed-openapi ../api/swagger.json --tanstack tanstack-client.ts -o ./app/generated/api-client/api-client.ts" "generate:openapi": "typed-openapi ../api/swagger.json --tanstack tanstack-client.ts -o ./app/generated/api-client/api-client.ts"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/themes": "^3.2.1", "@radix-ui/themes": "^3.2.1",
"@react-router/node": "^7.9.2", "@react-router/node": "^7.9.2",
"@react-router/serve": "^7.9.2", "@react-router/serve": "^7.9.2",

View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@radix-ui/react-tooltip':
specifier: ^1.2.8
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/themes': '@radix-ui/themes':
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 3.2.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)