estimate wizard now fully responsive-mobile first

This commit is contained in:
Mayur Shinde
2026-06-10 14:05:43 +05:30
parent a139516d7f
commit ed87e097f1
4 changed files with 30 additions and 18 deletions
@@ -12,7 +12,7 @@ import WizardShell from './components/WizardShell';
const CompletionScreen = () => { const CompletionScreen = () => {
const { inputs, reset } = useEstimateWizard(); const { inputs, reset } = useEstimateWizard();
return ( return (
<div className="min-h-screen w-full bg-zinc-50 dark:bg-[#09090b] flex items-center justify-center px-4"> <div className="min-h-[100dvh] w-full bg-zinc-50 dark:bg-[#09090b] flex items-center justify-center px-4 py-10">
<div className="max-w-md text-center"> <div className="max-w-md text-center">
<CheckCircle2 size={56} className="text-green-500 mx-auto mb-4" /> <CheckCircle2 size={56} className="text-green-500 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-zinc-900 dark:text-white"> <h1 className="text-2xl font-bold text-zinc-900 dark:text-white">
@@ -34,7 +34,7 @@ const CompletionScreen = () => {
}; };
const FeatureDisabledScreen = () => ( const FeatureDisabledScreen = () => (
<div className="min-h-screen w-full bg-zinc-50 dark:bg-[#09090b] flex items-center justify-center px-4"> <div className="min-h-[100dvh] w-full bg-zinc-50 dark:bg-[#09090b] flex items-center justify-center px-4 py-10">
<div className="max-w-md text-center"> <div className="max-w-md text-center">
<h1 className="text-xl font-bold text-zinc-900 dark:text-white">Estimate wizard unavailable</h1> <h1 className="text-xl font-bold text-zinc-900 dark:text-white">Estimate wizard unavailable</h1>
<p className="text-zinc-500 dark:text-zinc-400 mt-2"> <p className="text-zinc-500 dark:text-zinc-400 mt-2">
@@ -102,14 +102,14 @@ const WizardShell = () => {
}; };
return ( return (
<div className="min-h-screen w-full bg-zinc-50 dark:bg-[#09090b] flex flex-col"> <div className="min-h-[100dvh] w-full bg-zinc-50 dark:bg-[#09090b] flex flex-col">
{/* Top bar */} {/* Top bar */}
<header className="w-full border-b border-zinc-200 dark:border-white/5 bg-white/70 dark:bg-zinc-900/60 backdrop-blur-md"> <header className="w-full border-b border-zinc-200 dark:border-white/5 bg-white/70 dark:bg-zinc-900/60 backdrop-blur-md">
<div className="max-w-2xl mx-auto px-4 py-4"> <div className="max-w-2xl mx-auto px-4 py-3 sm:py-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between gap-3 mb-3">
<span className="font-bold tracking-tight text-zinc-900 dark:text-white"> <span className="font-bold tracking-tight text-zinc-900 dark:text-white truncate">
LynkedUp<span className="text-amber-500">Pro</span> LynkedUp<span className="text-amber-500">Pro</span>
<span className="ml-2 text-xs font-medium text-zinc-400">Roof Options</span> <span className="ml-2 text-xs font-medium text-zinc-400 hidden xs:inline">Roof Options</span>
</span> </span>
<button <button
onClick={reset} onClick={reset}
@@ -134,7 +134,7 @@ const WizardShell = () => {
{/* Body */} {/* Body */}
<main className="flex-1 w-full"> <main className="flex-1 w-full">
<div className="max-w-2xl mx-auto px-4 py-8"> <div className="max-w-2xl mx-auto px-4 py-6 sm:py-8">
{/* key forces remount per step → re-triggers the enter animation */} {/* key forces remount per step → re-triggers the enter animation */}
<div key={step.id} className="animate-in fade-in slide-in-from-right-4 duration-300"> <div key={step.id} className="animate-in fade-in slide-in-from-right-4 duration-300">
<h1 className="text-2xl md:text-3xl font-bold text-zinc-900 dark:text-white tracking-tight"> <h1 className="text-2xl md:text-3xl font-bold text-zinc-900 dark:text-white tracking-tight">
@@ -148,22 +148,26 @@ const WizardShell = () => {
</div> </div>
</main> </main>
{/* Footer nav — one primary CTA */} {/* Footer nav — one primary CTA. Respects the mobile safe-area inset so the
<footer className="sticky bottom-0 w-full border-t border-zinc-200 dark:border-white/5 bg-white/80 dark:bg-zinc-900/70 backdrop-blur-md"> CTA clears the iPhone home indicator. */}
<div className="max-w-2xl mx-auto px-4 py-3 flex items-center gap-3"> <footer
className="sticky bottom-0 w-full border-t border-zinc-200 dark:border-white/5 bg-white/80 dark:bg-zinc-900/70 backdrop-blur-md"
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
>
<div className="max-w-2xl mx-auto px-4 py-3 flex items-center gap-2 sm:gap-3">
{stepIndex > 0 && ( {stepIndex > 0 && (
<button <button
onClick={back} onClick={back}
className="rounded-xl border border-zinc-200 dark:border-white/10 px-4 py-2.5 text-sm font-medium text-zinc-600 dark:text-zinc-300 hover:border-amber-400 flex items-center gap-1.5" className="shrink-0 rounded-xl border border-zinc-200 dark:border-white/10 px-3 sm:px-4 py-2.5 text-sm font-medium text-zinc-600 dark:text-zinc-300 hover:border-amber-400 flex items-center gap-1.5"
> >
<ArrowLeft size={16} /> Back <ArrowLeft size={16} /> <span className="hidden xs:inline">Back</span>
</button> </button>
)} )}
{step.skippable && !isLast && ( {step.skippable && !isLast && (
<button <button
onClick={next} onClick={next}
className="text-sm font-medium text-zinc-400 hover:text-amber-500 px-2" className="shrink-0 text-sm font-medium text-zinc-400 hover:text-amber-500 px-1.5 sm:px-2 py-2.5"
> >
Skip Skip
</button> </button>
@@ -172,13 +176,13 @@ const WizardShell = () => {
<button <button
onClick={handleContinue} onClick={handleContinue}
disabled={!advance} disabled={!advance}
className={`ml-auto rounded-xl px-6 py-2.5 text-sm font-semibold flex items-center gap-1.5 transition-all className={`ml-auto min-w-0 rounded-xl px-5 sm:px-6 py-2.5 text-sm font-semibold flex items-center justify-center gap-1.5 whitespace-nowrap transition-all
${advance ${advance
? 'bg-gradient-to-r from-amber-400 to-orange-500 text-white shadow-lg shadow-amber-500/25 hover:shadow-amber-500/40' ? 'bg-gradient-to-r from-amber-400 to-orange-500 text-white shadow-lg shadow-amber-500/25 hover:shadow-amber-500/40'
: 'bg-zinc-200 dark:bg-zinc-800 text-zinc-400 cursor-not-allowed'}`} : 'bg-zinc-200 dark:bg-zinc-800 text-zinc-400 cursor-not-allowed'}`}
> >
{isLast ? 'Finish' : stepIndex === 0 ? 'Start estimate' : 'Continue'} {isLast ? 'Finish' : stepIndex === 0 ? 'Start estimate' : 'Continue'}
{!isLast && <ArrowRight size={16} />} {!isLast && <ArrowRight size={16} className="shrink-0" />}
</button> </button>
</div> </div>
</footer> </footer>
@@ -52,8 +52,10 @@ const AddonsStep = () => {
triggers={triggers} triggers={triggers}
/> />
{/* Running total for the selected package */} {/* Running total for the selected package. Not sticky — the wizard's
<div className="sticky bottom-0 rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-zinc-900 p-4 shadow-sm"> footer CTA is already pinned, and two stacked sticky bars overlap on
mobile. */}
<div className="rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-zinc-900 p-4 shadow-sm">
<div className="flex items-center justify-between text-sm text-zinc-500 dark:text-zinc-400"> <div className="flex items-center justify-between text-sm text-zinc-500 dark:text-zinc-400">
<span>{pkg?.name} (base estimate)</span> <span>{pkg?.name} (base estimate)</span>
<span>{formatUSD(base)}</span> <span>{formatUSD(base)}</span>
+6
View File
@@ -7,6 +7,12 @@ export default {
darkMode: 'class', darkMode: 'class',
theme: { theme: {
extend: { extend: {
// Extra-small breakpoint for fine mobile-first control (additive — does not
// affect the default sm/md/lg/xl screens).
screens: {
xs: '400px',
},
// --- Typography --- // --- Typography ---
fontFamily: { fontFamily: {
display: ['"Instrument Serif"', 'Georgia', 'serif'], display: ['"Instrument Serif"', 'Georgia', 'serif'],