estimate wizard now fully responsive-mobile first
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
Reference in New Issue
Block a user