Refactor Cart UI to Enhance Responsiveness and User Experience

- Replaced CartSheet and CartSidebar components with a new CartResponsive component that adapts to screen size, providing a unified cart experience.
- Implemented responsive animations for the cart, allowing it to slide in from the right on desktop and from the bottom on mobile.
- Updated styles and layout in tailwind.css to support new animation effects for the cart component.
This commit is contained in:
Bastian Masanek
2025-11-05 03:09:44 +01:00
parent 1349f42f41
commit 808b62645f
6 changed files with 184 additions and 86 deletions

View File

@@ -9,9 +9,7 @@
// 15px, which shifted the entire page content 15px to the left. This prop prevents
// that unwanted padding injection and layout shift.
import { ConfigProvider } from 'reka-ui'
// Import cart UI components
const { isMobile } = useCartUI()
import CartResponsive from '~/components/Cart/CartResponsive.vue'
</script>
<template>
@@ -19,10 +17,8 @@ const { isMobile } = useCartUI()
<div>
<NuxtPage />
<!-- Cart UI: Render appropriate component based on screen size -->
<!-- Mobile: Bottom sheet, Desktop: Right sidebar -->
<CartSheet v-if="isMobile" />
<CartSidebar v-else />
<!-- Responsive Cart: Slides from right on desktop, bottom on mobile -->
<CartResponsive />
</div>
</ConfigProvider>
</template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { useMediaQuery } from '@vueuse/core'
import {
Sheet,
SheetContent,
@@ -14,6 +15,17 @@ import CartSummary from './CartSummary.vue'
const { items, itemCount, total, loading, updateItem, removeItem } = useCart()
const { isOpen, close } = useCartUI()
// Detect mobile viewport (< 640px = Tailwind sm breakpoint)
const isMobile = useMediaQuery('(max-width: 639px)')
// Responsive side prop: bottom for mobile, right for desktop
const side = computed(() => isMobile.value ? 'bottom' : 'right')
// Responsive sizing classes
const sizeClasses = computed(() =>
isMobile.value ? 'h-[90vh]' : 'w-full sm:w-[400px]'
)
// Handle quantity update
async function handleUpdateQuantity(itemId: string, quantity: number) {
await updateItem(itemId, quantity)
@@ -33,7 +45,7 @@ function handleCheckout() {
<template>
<Sheet :open="isOpen" @update:open="(open) => !open && close()">
<SheetContent side="right" class="w-full sm:w-[400px] p-0 flex flex-col">
<SheetContent :side="side" :class="[sizeClasses, 'p-0 flex flex-col']">
<!-- Header -->
<SheetHeader class="px-6 py-4 border-b">
<SheetTitle class="text-xl font-bold">

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import { ScrollArea } from '@/components/ui/scroll-area'
import CartEmpty from './CartEmpty.vue'
import CartItem from './CartItem.vue'
import CartSummary from './CartSummary.vue'
// Get cart state and UI controls
const { items, itemCount, total, loading, updateItem, removeItem } = useCart()
const { isOpen, close } = useCartUI()
// Handle quantity update
async function handleUpdateQuantity(itemId: string, quantity: number) {
await updateItem(itemId, quantity)
}
// Handle item removal
async function handleRemoveItem(itemId: string) {
await removeItem(itemId)
}
// Navigate to checkout
function handleCheckout() {
close()
navigateTo('/checkout')
}
</script>
<template>
<Sheet :open="isOpen" @update:open="(open) => !open && close()">
<SheetContent side="bottom" class="h-[90vh] p-0 flex flex-col">
<!-- Header -->
<SheetHeader class="px-6 py-4 border-b">
<SheetTitle class="text-xl font-bold">
Warenkorb ({{ itemCount }})
</SheetTitle>
</SheetHeader>
<!-- Empty State -->
<div v-if="itemCount === 0" class="flex-1 flex items-center justify-center px-6">
<CartEmpty />
</div>
<!-- Cart Items + Summary -->
<template v-else>
<!-- Scrollable Items List -->
<ScrollArea class="flex-1 px-6">
<div class="space-y-4 py-4">
<CartItem v-for="item in items" :key="item.id" :item="item" :loading="loading"
@update:quantity="(qty) => handleUpdateQuantity(item.id, qty)" @remove="handleRemoveItem(item.id)" />
</div>
</ScrollArea>
<!-- Sticky Footer with Summary -->
<div class="border-t px-6 py-4 bg-background">
<CartSummary :items="items" :total="total" :loading="loading" @checkout="handleCheckout" />
</div>
</template>
</SheetContent>
</Sheet>
</template>

View File

@@ -2,5 +2,4 @@ export { default as CartItem } from './CartItem.vue'
export { default as CartSummary } from './CartSummary.vue'
export { default as CartEmpty } from './CartEmpty.vue'
export { default as CartFAB } from './CartFAB.vue'
export { default as CartSidebar } from './CartSidebar.vue'
export { default as CartSheet } from './CartSheet.vue'
export { default as CartResponsive } from './CartResponsive.vue'

View File

@@ -2,12 +2,8 @@
import AppHeader from '~/components/navigation/AppHeader.vue'
import BottomNav from '~/components/navigation/BottomNav.vue'
import CartFAB from '~/components/Cart/CartFAB.vue'
import CartSidebar from '~/components/Cart/CartSidebar.vue'
import CartSheet from '~/components/Cart/CartSheet.vue'
import CartResponsive from '~/components/Cart/CartResponsive.vue'
import { Toaster } from '~/components/ui/sonner'
// Determine which cart UI to show based on screen size
const { isMobile } = useCartUI()
</script>
<template>
@@ -29,11 +25,8 @@ const { isMobile } = useCartUI()
<!-- Floating Action Button (FAB) for Cart on Product Pages -->
<CartFAB />
<!-- Cart Sidebar (Desktop: >= 1024px) -->
<CartSidebar v-if="!isMobile" />
<!-- Cart Sheet (Mobile: < 1024px) -->
<CartSheet v-if="isMobile" />
<!-- Responsive Cart (slides from right on desktop, bottom on mobile) -->
<CartResponsive />
<!-- Toast Notifications -->
<Toaster position="top-center" :duration="3000" rich-colors />