Skip to content

Commit 94eaefa

Browse files
committed
feat: implement InstructionsAnswerCard component and integrate it into InstructionsWizard
1 parent 1515751 commit 94eaefa

File tree

2 files changed

+153
-72
lines changed

2 files changed

+153
-72
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"use client"
2+
3+
import { cn } from "@/lib/utils"
4+
import { Check, ExternalLink, Info } from "lucide-react"
5+
import type { ComponentPropsWithoutRef } from "react"
6+
7+
export type InstructionsAnswerCardProps = {
8+
label: string
9+
iconElement: React.ReactNode
10+
hasTooltipContent: boolean
11+
infoLines?: string[]
12+
example?: string
13+
tags?: string[]
14+
docs?: string
15+
selected?: boolean
16+
disabled?: boolean
17+
disabledLabel?: string
18+
onClick?: () => void
19+
} & Omit<ComponentPropsWithoutRef<"button">, "children">
20+
21+
export function InstructionsAnswerCard({
22+
label,
23+
iconElement,
24+
hasTooltipContent,
25+
infoLines,
26+
example,
27+
tags,
28+
docs,
29+
selected = false,
30+
disabled = false,
31+
disabledLabel,
32+
onClick,
33+
...buttonProps
34+
}: InstructionsAnswerCardProps) {
35+
return (
36+
<button
37+
type="button"
38+
onClick={onClick}
39+
aria-disabled={disabled}
40+
className={cn(
41+
"group relative flex h-full items-center justify-between rounded-2xl border border-border/60 bg-background/90 px-5 py-4 text-left transition-all hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
42+
disabled && "cursor-not-allowed opacity-60 hover:border-border/60 hover:shadow-none focus-visible:ring-0",
43+
selected && !disabled && "border-primary bg-primary/5 shadow-lg shadow-primary/20"
44+
)}
45+
{...buttonProps}
46+
>
47+
<div className="flex items-center gap-3">
48+
{iconElement}
49+
<div className="flex items-center gap-2">
50+
<span className="text-base font-medium text-foreground">{label}</span>
51+
{hasTooltipContent ? (
52+
<span className="relative flex items-center group/icon">
53+
<Info className="h-4 w-4 cursor-pointer text-muted-foreground transition-colors group-hover/icon:text-primary" />
54+
<div className="pointer-events-none absolute left-0 top-full z-20 hidden w-60 rounded-xl border border-border/70 bg-popover p-3 text-xs leading-relaxed text-popover-foreground shadow-xl transition-all duration-150 ease-out group-hover/icon:flex group-hover/icon:flex-col group-hover/icon:pointer-events-auto group-hover/icon:opacity-100 group-hover/icon:translate-y-0 opacity-0 translate-y-2">
55+
{infoLines?.map((line) => (
56+
<span key={line} className="text-foreground">
57+
{line}
58+
</span>
59+
))}
60+
{example ? (
61+
<span className="mt-1 text-muted-foreground">{example}</span>
62+
) : null}
63+
{tags && tags.length > 0 ? (
64+
<div className="mt-3 flex flex-wrap gap-1 text-[10px] uppercase tracking-wide text-muted-foreground/80">
65+
{tags.map((tag) => (
66+
<span key={tag} className="rounded-full bg-muted/80 px-2 py-0.5">
67+
{tag}
68+
</span>
69+
))}
70+
</div>
71+
) : null}
72+
{docs ? (
73+
<a
74+
href={docs}
75+
target="_blank"
76+
rel="noopener noreferrer"
77+
className="mt-3 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
78+
>
79+
<span>Open documentation</span>
80+
<ExternalLink className="h-3.5 w-3.5" />
81+
</a>
82+
) : null}
83+
</div>
84+
</span>
85+
) : null}
86+
{disabledLabel ? (
87+
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/80">
88+
{disabledLabel}
89+
</span>
90+
) : null}
91+
</div>
92+
</div>
93+
94+
<div className="flex items-center gap-2">
95+
{selected && !disabled ? (
96+
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary text-primary-foreground">
97+
<Check className="h-4 w-4" />
98+
</span>
99+
) : null}
100+
</div>
101+
</button>
102+
)
103+
}

components/instructions-wizard.tsx

Lines changed: 50 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import { useMemo, useState } from "react"
44
import { Button } from "@/components/ui/button"
5-
import { cn } from "@/lib/utils"
6-
import { ArrowLeft, Check, ExternalLink, Info } from "lucide-react"
5+
import { ArrowLeft } from "lucide-react"
76
import * as simpleIcons from "simple-icons"
87
import type { SimpleIcon } from "simple-icons"
98

@@ -15,6 +14,7 @@ import performanceData from "@/data/performance.json"
1514
import securityData from "@/data/security.json"
1615
import commitsData from "@/data/commits.json"
1716
import filesData from "@/data/files.json"
17+
import { InstructionsAnswerCard } from "./instructions-answer-card"
1818

1919
type IdeConfig = {
2020
id: string
@@ -358,6 +358,7 @@ export function InstructionsWizard({ onClose }: InstructionsWizardProps) {
358358
const [responses, setResponses] = useState<Responses>({})
359359
const [dynamicSteps, setDynamicSteps] = useState<WizardStep[]>([])
360360
const [isComplete, setIsComplete] = useState(false)
361+
const [showResetConfirm, setShowResetConfirm] = useState(false)
361362

362363
const wizardSteps = useMemo(
363364
() => [...preFrameworkSteps, ...dynamicSteps, ...postFrameworkSteps],
@@ -568,6 +569,19 @@ export function InstructionsWizard({ onClose }: InstructionsWizardProps) {
568569
setIsComplete(false)
569570
}
570571

572+
const requestResetWizard = () => {
573+
setShowResetConfirm(true)
574+
}
575+
576+
const confirmResetWizard = () => {
577+
resetWizard()
578+
setShowResetConfirm(false)
579+
}
580+
581+
const cancelResetWizard = () => {
582+
setShowResetConfirm(false)
583+
}
584+
571585
const renderCompletion = () => {
572586
const summary = wizardSteps.flatMap((step) =>
573587
step.questions.map((question) => {
@@ -622,7 +636,7 @@ export function InstructionsWizard({ onClose }: InstructionsWizardProps) {
622636
</div>
623637

624638
<div className="flex flex-wrap gap-2">
625-
<Button variant="ghost" onClick={resetWizard}>
639+
<Button variant="ghost" onClick={requestResetWizard}>
626640
Start Over
627641
</Button>
628642
<Button onClick={() => onClose?.()}>
@@ -698,79 +712,22 @@ export function InstructionsWizard({ onClose }: InstructionsWizardProps) {
698712
)
699713

700714
return (
701-
<button
715+
<InstructionsAnswerCard
702716
key={answer.value}
703-
type="button"
704717
onClick={() => {
705718
void handleAnswerClick(answer)
706719
}}
707-
aria-disabled={answer.disabled}
708-
className={cn(
709-
"group relative flex h-full items-center justify-between rounded-2xl border border-border/60 bg-background/90 px-5 py-4 text-left transition-all hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
710-
answer.disabled &&
711-
"cursor-not-allowed opacity-60 hover:border-border/60 hover:shadow-none focus-visible:ring-0",
712-
isAnswerSelected(answer.value) &&
713-
!answer.disabled &&
714-
"border-primary bg-primary/5 shadow-lg shadow-primary/20"
715-
)}
716-
>
717-
<div className="flex items-center gap-3">
718-
{iconElement}
719-
<div className="flex items-center gap-2">
720-
<span className="text-base font-medium text-foreground">
721-
{answer.label}
722-
</span>
723-
{hasTooltipContent ? (
724-
<span className="relative flex items-center group/icon">
725-
<Info className="h-4 w-4 cursor-pointer text-muted-foreground transition-colors group-hover/icon:text-primary" />
726-
<div className="pointer-events-none absolute left-0 top-full z-20 hidden w-60 rounded-xl border border-border/70 bg-popover p-3 text-xs leading-relaxed text-popover-foreground shadow-xl transition-all duration-150 ease-out group-hover/icon:flex group-hover/icon:flex-col group-hover/icon:pointer-events-auto group-hover/icon:opacity-100 group-hover/icon:translate-y-0 opacity-0 translate-y-2">
727-
{answer.infoLines?.map((line) => (
728-
<span key={line} className="text-foreground">
729-
{line}
730-
</span>
731-
))}
732-
{answer.example ? (
733-
<span className="mt-1 text-muted-foreground">{answer.example}</span>
734-
) : null}
735-
{answer.tags && answer.tags.length > 0 ? (
736-
<div className="mt-3 flex flex-wrap gap-1 text-[10px] uppercase tracking-wide text-muted-foreground/80">
737-
{answer.tags.map((tag) => (
738-
<span key={tag} className="rounded-full bg-muted/80 px-2 py-0.5">
739-
{tag}
740-
</span>
741-
))}
742-
</div>
743-
) : null}
744-
{answer.docs ? (
745-
<a
746-
href={answer.docs}
747-
target="_blank"
748-
rel="noopener noreferrer"
749-
className="mt-3 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
750-
>
751-
<span>Open documentation</span>
752-
<ExternalLink className="h-3.5 w-3.5" />
753-
</a>
754-
) : null}
755-
</div>
756-
</span>
757-
) : null}
758-
{answer.disabledLabel ? (
759-
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/80">
760-
{answer.disabledLabel}
761-
</span>
762-
) : null}
763-
</div>
764-
</div>
765-
766-
<div className="flex items-center gap-2">
767-
{isAnswerSelected(answer.value) && !answer.disabled ? (
768-
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-primary text-primary-foreground">
769-
<Check className="h-4 w-4" />
770-
</span>
771-
) : null}
772-
</div>
773-
</button>
720+
label={answer.label}
721+
iconElement={iconElement}
722+
hasTooltipContent={hasTooltipContent}
723+
infoLines={answer.infoLines}
724+
example={answer.example}
725+
tags={answer.tags}
726+
docs={answer.docs}
727+
selected={isAnswerSelected(answer.value)}
728+
disabled={answer.disabled}
729+
disabledLabel={answer.disabledLabel}
730+
/>
774731
)
775732
})}
776733
</div>
@@ -790,6 +747,27 @@ export function InstructionsWizard({ onClose }: InstructionsWizardProps) {
790747
</section>
791748
</>
792749
)}
750+
751+
{showResetConfirm ? (
752+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 p-4 backdrop-blur-sm">
753+
<div className="w-full max-w-md space-y-4 rounded-2xl border border-border/70 bg-card/95 p-6 shadow-2xl">
754+
<div className="space-y-2">
755+
<h3 className="text-lg font-semibold text-foreground">Start over?</h3>
756+
<p className="text-sm text-muted-foreground">
757+
This will clear all of your current selections. Are you sure you want to continue?
758+
</p>
759+
</div>
760+
<div className="flex flex-wrap justify-end gap-2">
761+
<Button variant="ghost" onClick={cancelResetWizard}>
762+
Keep My Answers
763+
</Button>
764+
<Button variant="destructive" onClick={confirmResetWizard}>
765+
Reset Wizard
766+
</Button>
767+
</div>
768+
</div>
769+
</div>
770+
) : null}
793771
</div>
794772
)
795773
}

0 commit comments

Comments
 (0)