Files
Jyotisha/frontend/src/components/profile-gender-field.tsx
T
Jesse_ChenandClaude Opus 5.5 d79dada970 feat(ui): optional gender radio in the people form and 个人资料 (T2)
One ProfileGenderField (女 / 男 / 不填, 44px, one hint line) in the
people-archive edit form (saved with the form, 不填 → null) and in
设置 → 个人资料 for the owner (saves on its own PATCH; birth-data saves never
send gender). The detail card shows 性别 (未填 when empty). Not asked in
onboarding. DESIGN/VOICE updated; 390/1280 screenshots with fictional data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-27 18:30:58 +08:00

62 lines
1.9 KiB
TypeScript

"use client";
import {
PROFILE_GENDER_HINT,
PROFILE_GENDER_LABELS,
PROFILE_GENDER_TITLE,
PROFILE_GENDER_UNSET_LABEL,
type ProfileGender,
} from "@/lib/profile-gender";
const OPTIONS: ReadonlyArray<{ readonly value: ProfileGender | null; readonly label: string }> = [
{ value: "female", label: PROFILE_GENDER_LABELS.female },
{ value: "male", label: PROFILE_GENDER_LABELS.male },
{ value: null, label: PROFILE_GENDER_UNSET_LABEL },
];
/**
* Optional gender (TASK-consult-gender-optional-20260927): 女 / 男 / 不填.
* 不填 is stored as null. One line under the choices says what it is for.
*/
export function ProfileGenderField({
value,
onChange,
idPrefix,
disabled = false,
}: {
readonly value: ProfileGender | null | undefined;
readonly onChange: (next: ProfileGender | null) => void;
readonly idPrefix: string;
readonly disabled?: boolean;
}) {
const titleId = `${idPrefix}-gender-title`;
const hintId = `${idPrefix}-gender-hint`;
const current = value ?? null;
return (
<div className="profile-gender-field">
<span className="profile-gender-title" id={titleId}>{PROFILE_GENDER_TITLE}</span>
<div className="profile-gender-options" role="radiogroup" aria-labelledby={titleId} aria-describedby={hintId}>
{OPTIONS.map((option) => {
const selected = option.value === current;
return (
<button
key={option.label}
className={`profile-gender-option${selected ? " is-selected" : ""}`}
type="button"
role="radio"
aria-checked={selected}
disabled={disabled}
onClick={() => {
if (!selected) onChange(option.value);
}}
>
{option.label}
</button>
);
})}
</div>
<p className="profile-gender-hint" id={hintId}>{PROFILE_GENDER_HINT}</p>
</div>
);
}