176 lines
4.2 KiB
TypeScript
176 lines
4.2 KiB
TypeScript
// frontend\src\components\ui\ModelGenderIcon.tsx
|
|
|
|
'use client'
|
|
|
|
import { type ComponentType, type ReactNode } from 'react'
|
|
|
|
function cn(...parts: Array<string | false | null | undefined>) {
|
|
return parts.filter(Boolean).join(' ')
|
|
}
|
|
|
|
type GenderKind = 'female' | 'male' | 'trans' | 'couple' | null
|
|
|
|
function normalizeGender(
|
|
gender?: string | null,
|
|
): GenderKind {
|
|
const raw = `${gender || ''}`.trim().toLowerCase()
|
|
|
|
if (!raw) return null
|
|
|
|
if (raw.includes('couple') || raw.includes('pair') || raw === 'c') {
|
|
return 'couple'
|
|
}
|
|
|
|
if (
|
|
raw.includes('trans') ||
|
|
raw.includes('transgender') ||
|
|
raw === 't'
|
|
) {
|
|
return 'trans'
|
|
}
|
|
|
|
if (
|
|
raw.includes('female') ||
|
|
raw.includes('girl') ||
|
|
raw.includes('woman') ||
|
|
raw === 'f' ||
|
|
raw === 'w'
|
|
) {
|
|
return 'female'
|
|
}
|
|
|
|
if (
|
|
raw.includes('male') ||
|
|
raw.includes('boy') ||
|
|
raw.includes('man') ||
|
|
raw === 'm'
|
|
) {
|
|
return 'male'
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
type IconProps = {
|
|
className?: string
|
|
}
|
|
|
|
function BaseIcon({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth={1.8}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
aria-hidden="true"
|
|
className={cn('size-3.5', className)}
|
|
>
|
|
{children}
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function FemaleIcon({ className }: IconProps) {
|
|
return (
|
|
<BaseIcon className={className}>
|
|
<circle cx="12" cy="8" r="4.25" />
|
|
<path d="M12 12.25V20" />
|
|
<path d="M9 17H15" />
|
|
</BaseIcon>
|
|
)
|
|
}
|
|
|
|
function MaleIcon({ className }: IconProps) {
|
|
return (
|
|
<BaseIcon className={className}>
|
|
<circle cx="9" cy="15" r="4.25" />
|
|
<path d="M12 12L19 5" />
|
|
<path d="M14.75 5H19V9.25" />
|
|
</BaseIcon>
|
|
)
|
|
}
|
|
|
|
function TransIcon({ className }: IconProps) {
|
|
return (
|
|
<BaseIcon className={className}>
|
|
<circle cx="10.5" cy="10.5" r="3.75" />
|
|
<path d="M13.25 7.75L18.5 2.5" />
|
|
<path d="M15.75 2.5H18.5V5.25" />
|
|
<path d="M10.5 14.25V21" />
|
|
<path d="M7.75 18.25H13.25" />
|
|
<path d="M6.5 10.5H2.75" />
|
|
<path d="M4.5 8.75L2.75 10.5L4.5 12.25" />
|
|
</BaseIcon>
|
|
)
|
|
}
|
|
|
|
function CoupleIcon({ className }: IconProps) {
|
|
return (
|
|
<BaseIcon className={className}>
|
|
<circle cx="8" cy="9" r="3" />
|
|
<circle cx="16" cy="9" r="3" />
|
|
<path d="M3.75 18.5C4.5 15.75 6 14.5 8 14.5C10 14.5 11.5 15.75 12.25 18.5" />
|
|
<path d="M11.75 18.5C12.5 15.75 14 14.5 16 14.5C18 14.5 19.5 15.75 20.25 18.5" />
|
|
</BaseIcon>
|
|
)
|
|
}
|
|
|
|
type Props = {
|
|
gender?: string | null
|
|
variant?: 'hero' | 'default'
|
|
className?: string
|
|
iconClassName?: string
|
|
}
|
|
|
|
export default function ModelGenderIcon({
|
|
gender,
|
|
variant = 'default',
|
|
className,
|
|
iconClassName,
|
|
}: Props) {
|
|
const kind = normalizeGender(gender)
|
|
|
|
if (!kind) return null
|
|
|
|
const map: Record<Exclude<GenderKind, null>, { label: string; Icon: ComponentType<IconProps> }> = {
|
|
female: { label: 'Female', Icon: FemaleIcon },
|
|
male: { label: 'Male', Icon: MaleIcon },
|
|
trans: { label: 'Trans', Icon: TransIcon },
|
|
couple: { label: 'Couple', Icon: CoupleIcon },
|
|
}
|
|
|
|
const { label, Icon } = map[kind]
|
|
|
|
const toneClass =
|
|
kind === 'female'
|
|
? 'bg-pink-100 text-pink-700 ring-pink-200 dark:bg-pink-500/15 dark:text-pink-200 dark:ring-pink-400/25'
|
|
: kind === 'male'
|
|
? 'bg-sky-100 text-sky-700 ring-sky-200 dark:bg-sky-500/15 dark:text-sky-200 dark:ring-sky-400/25'
|
|
: kind === 'trans'
|
|
? 'bg-violet-100 text-violet-700 ring-violet-200 dark:bg-violet-500/15 dark:text-violet-200 dark:ring-violet-400/25'
|
|
: 'bg-gradient-to-r from-sky-100 to-pink-100 text-fuchsia-700 ring-fuchsia-200 dark:from-sky-500/15 dark:to-pink-500/15 dark:text-pink-200 dark:ring-pink-400/25'
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex shrink-0 items-center justify-center rounded-full ring-1 ring-inset',
|
|
variant === 'hero'
|
|
? cn('size-5 backdrop-blur shadow-sm', toneClass)
|
|
: cn('size-5', toneClass),
|
|
className
|
|
)}
|
|
title={label}
|
|
aria-label={label}
|
|
>
|
|
<Icon className={iconClassName} />
|
|
</span>
|
|
)
|
|
} |