11import Area from '@components/common/Area.js' ;
22import { Form } from '@components/common/form/Form.js' ;
3+ import {
4+ Collapsible ,
5+ CollapsibleContent ,
6+ CollapsibleTrigger
7+ } from '@components/common/ui/Collapsible.js' ;
8+ import { Skeleton } from '@components/common/ui/Skeleton.js' ;
9+ import { useCartState } from '@components/frontStore/cart/CartContext.js' ;
310import { CartItems } from '@components/frontStore/cart/CartItems.js' ;
411import { CartSummaryItemsList } from '@components/frontStore/cart/CartSummaryItems.js' ;
512import { CartTotalSummary } from '@components/frontStore/cart/CartTotalSummary.js' ;
@@ -11,10 +18,70 @@ import { Shipment } from '@components/frontStore/checkout/Shipment.js';
1118import { ShippingNote } from '@components/frontStore/checkout/ShippingNote.js' ;
1219import { useCustomer } from '@components/frontStore/customer/CustomerContext.js' ;
1320import { _ } from '@evershop/evershop/lib/locale/translate/_' ;
21+ import { ChevronDown } from 'lucide-react' ;
1422import React from 'react' ;
1523import { useForm } from 'react-hook-form' ;
1624import './Checkout.scss' ;
1725
26+ function OrderSummaryContent ( ) {
27+ return (
28+ < >
29+ < CartItems >
30+ { ( { items, loading, showPriceIncludingTax } ) => (
31+ < CartSummaryItemsList
32+ items = { items }
33+ loading = { loading }
34+ showPriceIncludingTax = { showPriceIncludingTax }
35+ />
36+ ) }
37+ </ CartItems >
38+ < div className = "mt-4" >
39+ < CartTotalSummary />
40+ </ div >
41+ </ >
42+ ) ;
43+ }
44+
45+ /**
46+ * Below `lg` the summary rail is hidden, and this sticky disclosure bar takes
47+ * its place above the form: collapsed it shows only the label and the grand
48+ * total; expanded it reveals the full summary (items, coupon, totals) in a
49+ * panel that scrolls internally so the bar itself never grows past the
50+ * viewport. The `-mx-4` bleeds the bar through `.page-width`'s 1rem gutter so
51+ * it spans edge to edge while stuck.
52+ */
53+ function MobileOrderSummary ( ) {
54+ const { data : cart , loadingStates } = useCartState ( ) ;
55+ const updating = Object . values ( loadingStates ) . some (
56+ ( state ) => state === true || ( typeof state === 'string' && state !== null )
57+ ) ;
58+ return (
59+ < Collapsible className = "checkout-mobile-summary sticky top-0 z-20 -mx-4 mb-8 border-y border-border bg-card lg:hidden" >
60+ < CollapsibleTrigger className = "group flex w-full items-center justify-between gap-4 px-4 py-3 text-left" >
61+ < span className = "flex items-center gap-1 text-sm font-medium" >
62+ { _ ( 'Order summary' ) }
63+ < ChevronDown
64+ className = "h-4 w-4 text-muted-foreground transition-transform duration-200 group-aria-expanded:rotate-180"
65+ aria-hidden = "true"
66+ />
67+ </ span >
68+ { updating ? (
69+ < Skeleton className = "h-5 w-16" />
70+ ) : (
71+ < span className = "text-base font-semibold tabular-nums" >
72+ { cart ?. grandTotal ?. text || '' }
73+ </ span >
74+ ) }
75+ </ CollapsibleTrigger >
76+ < CollapsibleContent className = "h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0" >
77+ < div className = "max-h-[60vh] overflow-y-auto border-t border-border px-4 py-4" >
78+ < OrderSummaryContent />
79+ </ div >
80+ </ CollapsibleContent >
81+ </ Collapsible >
82+ ) ;
83+ }
84+
1885interface CheckoutPageProps {
1986 placeOrderApi : string ;
2087 getPaymentMethodApi : string ;
@@ -70,6 +137,7 @@ export default function CheckoutPage({
70137 < h1 className = "mb-8 text-3xl font-semibold tracking-tight" >
71138 { _ ( 'Checkout' ) }
72139 </ h1 >
140+ < MobileOrderSummary />
73141 < div className = "grid grid-cols-1 gap-8 pb-16 lg:grid-cols-[1fr_400px]" >
74142 < Form form = { form } submitBtn = { false } >
75143 < Area id = "checkoutFormBefore" noOuter />
@@ -81,27 +149,24 @@ export default function CheckoutPage({
81149 < ContactInformation />
82150 < Shipment />
83151 < Payment />
152+ { /* Below `lg` the summary rail is hidden, so the note moves into
153+ the form flow, right before the place-order button. Both this
154+ and the rail instance edit the same checkoutData.note. */ }
155+ { showShippingNote && (
156+ < div className = "mt-6 lg:hidden" >
157+ < ShippingNote />
158+ </ div >
159+ ) }
84160 < CheckoutButton />
85161 </ div >
86162 < Area id = "checkoutForm" noOuter />
87163 < Area id = "checkoutFormAfter" noOuter />
88164 </ Form >
89- < div className = "h-fit space-y-6 lg:sticky lg:top-8" >
165+ < div className = "hidden h-fit space-y-6 lg:sticky lg:top-8 lg:block " >
90166 { showShippingNote && < ShippingNote /> }
91167 < div className = "rounded-lg border border-border bg-card p-6" >
92168 < h2 className = "mb-4 h4" > { _ ( 'Order summary' ) } </ h2 >
93- < CartItems >
94- { ( { items, loading, showPriceIncludingTax } ) => (
95- < CartSummaryItemsList
96- items = { items }
97- loading = { loading }
98- showPriceIncludingTax = { showPriceIncludingTax }
99- />
100- ) }
101- </ CartItems >
102- < div className = "mt-4" >
103- < CartTotalSummary />
104- </ div >
169+ < OrderSummaryContent />
105170 </ div >
106171 </ div >
107172 </ div >
0 commit comments