feat: simplify AI design for young creators
This commit is contained in:
1 parent
7552cf5952
commit
756638a28a
17 files changed
+3595
-297
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import { Check, Sparkles, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { YouthActiveChoice } from './youth-form-projection';
|
||||
|
||||
export type CreativeChoiceGridProps = {
|
||||
choice: YouthActiveChoice;
|
||||
disabled?: boolean;
|
||||
onSelect: (optionId: string) => void;
|
||||
onReject?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A small, keyboard-friendly surface for the one decision the creator needs
|
||||
* to make next. The server-provided option label stays visible verbatim (so
|
||||
* options such as “你帮我选” remain useful), while the first option gets a
|
||||
* gentle recommendation marker.
|
||||
*/
|
||||
export function CreativeChoiceGrid({
|
||||
choice,
|
||||
disabled = false,
|
||||
onSelect,
|
||||
onReject,
|
||||
}: CreativeChoiceGridProps) {
|
||||
if (choice.options.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section
|
||||
data-testid="youth-creation-choice"
|
||||
className="rounded-2xl border border-brand/20 bg-brand/[0.035] p-4"
|
||||
>
|
||||
<div className="flex items-start gap-2.5">
|
||||
<div className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-brand/[0.1] text-brand">
|
||||
<Sparkles className="h-4 w-4" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-sm font-semibold text-foreground">{choice.title}</h3>
|
||||
<p className="mt-1 text-xs leading-5 text-muted-foreground">{choice.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset className="mt-3 space-y-2">
|
||||
<legend className="sr-only">选择一个创作方向</legend>
|
||||
{choice.options.map((option, index) => {
|
||||
const descriptionId = `${choice.id}-${option.id}-description`;
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
aria-describedby={option.description ? descriptionId : undefined}
|
||||
className={cn(
|
||||
'flex min-h-11 w-full items-center gap-3 rounded-xl border border-border/70 bg-background px-3 text-left transition-colors duration-150',
|
||||
'hover:border-brand/35 hover:bg-brand/[0.04]',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 focus-visible:ring-offset-2',
|
||||
'disabled:pointer-events-none disabled:opacity-55',
|
||||
)}
|
||||
onClick={() => onSelect(option.id)}
|
||||
>
|
||||
<span className="flex min-w-0 flex-1 flex-col justify-center gap-0.5 py-2">
|
||||
<span className="flex flex-wrap items-center gap-2 text-sm font-medium text-foreground">
|
||||
<span>{option.label}</span>
|
||||
{index === 0 && (
|
||||
<span className="rounded-full bg-brand/[0.1] px-2 py-0.5 text-[11px] font-medium text-brand">
|
||||
推荐
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{option.description && (
|
||||
<span
|
||||
id={descriptionId}
|
||||
className="text-xs leading-5 text-muted-foreground"
|
||||
>
|
||||
{option.description}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<Check className="h-4 w-4 shrink-0 text-brand/75" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
|
||||
{onReject && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="mt-2 inline-flex min-h-11 items-center gap-1.5 rounded-xl px-2.5 text-xs text-muted-foreground transition-colors hover:bg-background hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/35 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-55"
|
||||
onClick={onReject}
|
||||
>
|
||||
<X className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
先跳过这题
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user