1- import * as React from 'react'
21import * as Dialog from '@radix-ui/react-dialog'
32import { Link } from '@tanstack/react-router'
43import { Minus , Plus , ShoppingCart , Trash2 , X } from 'lucide-react'
54import { twMerge } from 'tailwind-merge'
65import { useCart , useRemoveCartLine , useUpdateCartLine } from '~/hooks/useCart'
76import { formatMoney , shopifyImageUrl } from '~/utils/shopify-format'
87import type { CartLineDetail } from '~/utils/shopify-queries'
8+ import { ShopLabel , ShopMono } from './ui'
99
1010type CartDrawerProps = {
1111 open : boolean
@@ -14,10 +14,8 @@ type CartDrawerProps = {
1414
1515/**
1616 * Slide-in cart drawer. Shares state with /shop/cart through the same
17- * useCart React Query key, so adds in the drawer mirror the full page
18- * and vice-versa. Pinned to the right on desktop; full-width slide-up on
19- * mobile would be nice later, but a right-anchored sheet is the standard
20- * Shopify-theme pattern and works on phones too.
17+ * useCart React Query key. The root element wears `shop-scope` because the
18+ * drawer is portaled outside the ShopLayout tree.
2119 */
2220export function CartDrawer ( { open, onOpenChange } : CartDrawerProps ) {
2321 const { cart, totalQuantity } = useCart ( )
@@ -26,34 +24,36 @@ export function CartDrawer({ open, onOpenChange }: CartDrawerProps) {
2624 return (
2725 < Dialog . Root open = { open } onOpenChange = { onOpenChange } >
2826 < Dialog . Portal >
29- < Dialog . Overlay className = "cart-overlay fixed inset-0 z-[100] bg-black/20 " />
27+ < Dialog . Overlay className = "cart-overlay fixed inset-0 z-[100] bg-black/40 " />
3028 < Dialog . Content
3129 className = { twMerge (
32- 'cart-panel' ,
30+ 'shop-scope cart-panel' ,
3331 'fixed right-4 top-[calc(var(--navbar-height,56px)+0.5rem)] z-[100]' ,
3432 'w-[calc(100vw-2rem)] sm:w-[24rem]' ,
3533 'max-h-[calc(100dvh-var(--navbar-height,56px)-1rem)]' ,
36- 'flex flex-col' ,
37- 'rounded-xl shadow-2xl ' ,
38- 'bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 ' ,
34+ 'flex flex-col rounded-xl ' ,
35+ 'bg-shop-bg-2 border border-shop-line text-shop-text ' ,
36+ 'shadow-[0_25px_50px_-12px_rgba(0,0,0,0.4)] ' ,
3937 ) }
4038 aria-describedby = { undefined }
4139 >
42- < header className = "flex items-center justify-between px-5 py-3 border-b border-gray-200 dark:border-gray-800" >
43- < Dialog . Title className = "font-semibold text-sm" >
44- Cart{ totalQuantity > 0 ? ` (${ totalQuantity } )` : '' }
40+ < header className = "flex items-center justify-between px-5 py-3 border-b border-shop-line" >
41+ < Dialog . Title asChild >
42+ < ShopLabel as = "h2" >
43+ Cart{ totalQuantity > 0 ? ` (${ totalQuantity } )` : '' }
44+ </ ShopLabel >
4545 </ Dialog . Title >
4646 < Dialog . Close
47- className = "p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-900"
4847 aria-label = "Close cart"
48+ className = "p-1 rounded-md text-shop-text-2 hover:text-shop-text"
4949 >
5050 < X className = "w-3.5 h-3.5" />
5151 </ Dialog . Close >
5252 </ header >
5353
5454 { hasLines ? (
5555 < >
56- < ul className = "overflow-y-auto px-5 divide-y divide-gray-200 dark:divide-gray-800 " >
56+ < ul className = "overflow-y-auto px-5 flex-1 min-h-0 " >
5757 { cart . lines . nodes . map ( ( line ) => (
5858 < DrawerCartLine
5959 key = { line . id }
@@ -75,15 +75,22 @@ export function CartDrawer({ open, onOpenChange }: CartDrawerProps) {
7575
7676function DrawerEmpty ( { onClose } : { onClose : ( ) => void } ) {
7777 return (
78- < div className = "flex-1 flex flex-col items-center justify-center gap-4 p-6 text-center" >
79- < ShoppingCart className = "w-10 h-10 text-gray-400 " />
80- < p className = "text-gray-600 dark:text-gray-400" > Your cart is empty.</ p >
78+ < div className = "flex-1 flex flex-col items-center justify-center gap-4 p-8 text-center text-shop-text-2 " >
79+ < ShoppingCart className = "w-10 h-10 text-shop-muted " />
80+ < p > Your cart is empty.</ p >
8181 < Link
8282 to = "/shop"
8383 onClick = { onClose }
84- className = "inline-flex items-center px-4 py-2 rounded-lg bg-black text-white dark:bg-white dark:text-black font-semibold"
84+ className = "
85+ inline-flex items-center gap-2 px-4 py-2.5 rounded-md
86+ bg-shop-accent text-shop-accent-ink font-semibold text-[13px]
87+ transition-[filter] hover:brightness-110 group
88+ "
8589 >
8690 Shop all products
91+ < span className = "transition-transform group-hover:translate-x-[3px]" >
92+ →
93+ </ span >
8794 </ Link >
8895 </ div >
8996 )
@@ -98,26 +105,33 @@ function DrawerFooter({
98105} ) {
99106 const subtotal = cart . cost . subtotalAmount
100107 return (
101- < footer className = "border-t border-gray-200 dark:border-gray-800 px-6 py-4 flex flex-col gap-3" >
108+ < footer className = "px-5 py-4 flex flex-col gap-3 border-t border-shop-line " >
102109 < div className = "flex justify-between text-sm" >
103- < span className = "text-gray-600 dark: text-gray-400 " > Subtotal</ span >
104- < span className = "font-semibold " >
110+ < span className = "text-shop- text-2 " > Subtotal</ span >
111+ < ShopMono className = "font-medium text-shop-text " >
105112 { formatMoney ( subtotal . amount , subtotal . currencyCode ) }
106- </ span >
113+ </ ShopMono >
107114 </ div >
108- < p className = "text-xs text-gray-500 dark:text-gray-500 " >
115+ < p className = "font-shop-mono text-xs text-shop-muted tracking-[0.06em] " >
109116 Shipping and taxes calculated at checkout.
110117 </ p >
111118 < a
112119 href = { cart . checkoutUrl }
113- className = "w-full text-center px-6 py-3 rounded-lg bg-black text-white dark:bg-white dark:text-black font-semibold"
120+ className = "
121+ w-full h-10 rounded-md bg-shop-accent text-shop-accent-ink
122+ font-semibold text-[13px] flex items-center justify-center gap-2
123+ transition-[filter] hover:brightness-110 group
124+ "
114125 >
115126 Checkout
127+ < span className = "transition-transform group-hover:translate-x-[3px]" >
128+ →
129+ </ span >
116130 </ a >
117131 < Link
118132 to = "/shop/cart"
119133 onClick = { onClose }
120- className = "w-full text-center text-sm text-gray-600 dark: text-gray-400 hover:underline"
134+ className = "text-center text-sm text-shop- text-2 hover:underline"
121135 >
122136 View cart
123137 </ Link >
@@ -143,42 +157,40 @@ function DrawerCartLine({
143157 const isBusy = update . isPending || remove . isPending
144158
145159 return (
146- < li className = "flex gap-3 py-4" >
160+ < li className = "flex gap-3 py-4 border-b border-shop-line " >
147161 < Link
148162 to = "/shop/products/$handle"
149163 params = { { handle : merchandise . product . handle } }
150164 onClick = { onClose }
151- className = "shrink-0"
165+ className = "shrink-0 w-16 h-16 rounded-md border border-shop-line bg-shop-panel overflow-hidden "
152166 >
153- < div className = "w-16 h-16 rounded-lg overflow-hidden bg-gray-100 dark:bg-gray-900" >
154- { merchandise . image ? (
155- < img
156- src = { shopifyImageUrl ( merchandise . image . url , {
157- width : 160 ,
158- format : 'webp' ,
159- } ) }
160- alt = { merchandise . image . altText ?? merchandise . product . title }
161- className = "h-full w-full object-cover"
162- />
163- ) : null }
164- </ div >
167+ { merchandise . image ? (
168+ < img
169+ src = { shopifyImageUrl ( merchandise . image . url , {
170+ width : 160 ,
171+ format : 'webp' ,
172+ } ) }
173+ alt = { merchandise . image . altText ?? merchandise . product . title }
174+ className = "w-full h-full object-cover"
175+ />
176+ ) : null }
165177 </ Link >
166178 < div className = "flex-1 min-w-0 flex flex-col gap-1" >
167179 < Link
168180 to = "/shop/products/$handle"
169181 params = { { handle : merchandise . product . handle } }
170182 onClick = { onClose }
171- className = "text-sm font-semibold hover:underline truncate"
183+ className = "text-sm font-semibold text-shop-text hover:underline truncate"
172184 >
173185 { merchandise . product . title }
174186 </ Link >
175187 { options ? (
176- < p className = "text-xs text-gray-600 dark:text-gray-400 truncate" >
188+ < ShopMono className = "block text-xs text-shop-muted truncate" >
177189 { options }
178- </ p >
190+ </ ShopMono >
179191 ) : null }
180192 < div className = "flex items-center justify-between mt-1" >
181- < div className = "inline-flex items-center rounded-md border border-gray-200 dark:border-gray-800 text-xs" >
193+ < div className = "inline-flex items-center border border-shop-line rounded-md text-xs" >
182194 < button
183195 type = "button"
184196 onClick = { ( ) => {
@@ -193,11 +205,13 @@ function DrawerCartLine({
193205 } }
194206 disabled = { isBusy }
195207 aria-label = "Decrease quantity"
196- className = "p-1.5 disabled:opacity-50"
208+ className = "p-1.5 text-shop-text-2 hover:text-shop-text disabled:opacity-50"
197209 >
198210 < Minus className = "w-3 h-3" />
199211 </ button >
200- < span className = "min-w-[1.5rem] text-center" > { line . quantity } </ span >
212+ < span className = "font-shop-mono text-shop-text min-w-[1.5rem] text-center" >
213+ { line . quantity }
214+ </ span >
201215 < button
202216 type = "button"
203217 onClick = { ( ) =>
@@ -208,24 +222,24 @@ function DrawerCartLine({
208222 }
209223 disabled = { isBusy }
210224 aria-label = "Increase quantity"
211- className = "p-1.5 disabled:opacity-50"
225+ className = "p-1.5 text-shop-text-2 hover:text-shop-text disabled:opacity-50"
212226 >
213227 < Plus className = "w-3 h-3" />
214228 </ button >
215229 </ div >
216230 < div className = "flex items-center gap-2" >
217- < span className = "text-sm font-semibold " >
231+ < ShopMono className = "text-sm font-medium text-shop-text " >
218232 { formatMoney (
219233 line . cost . totalAmount . amount ,
220234 line . cost . totalAmount . currencyCode ,
221235 ) }
222- </ span >
236+ </ ShopMono >
223237 < button
224238 type = "button"
225239 onClick = { ( ) => remove . mutate ( { lineId : line . id } ) }
226240 disabled = { isBusy }
227241 aria-label = "Remove from cart"
228- className = "p-1 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 disabled:opacity-50"
242+ className = "p-1 rounded-md text-shop-muted hover:text-shop-text disabled:opacity-50"
229243 >
230244 < Trash2 className = "w-3.5 h-3.5" />
231245 </ button >
0 commit comments