diff options
Diffstat (limited to 'src/lib/category/components')
| -rw-r--r-- | src/lib/category/components/Breadcrumb.jsx | 224 |
1 files changed, 178 insertions, 46 deletions
diff --git a/src/lib/category/components/Breadcrumb.jsx b/src/lib/category/components/Breadcrumb.jsx index 127904ee..50557c3e 100644 --- a/src/lib/category/components/Breadcrumb.jsx +++ b/src/lib/category/components/Breadcrumb.jsx @@ -1,56 +1,188 @@ -import odooApi from '@/core/api/odooApi' -import { createSlug } from '@/core/utils/slug' +import odooApi from '@/core/api/odooApi'; +import { createSlug } from '@/core/utils/slug'; import { Breadcrumb as ChakraBreadcrumb, BreadcrumbItem, BreadcrumbLink, - Skeleton -} from '@chakra-ui/react' -import Link from 'next/link' -import React from 'react' -import { useQuery } from 'react-query' - -/** - * Render a breadcrumb component. - * - * @param {object} categoryId - The ID of the category. - * @return {JSX.Element} The breadcrumb component. - */ + Skeleton, +} from '@chakra-ui/react'; +import Link from 'next/link'; +import React from 'react'; +import { useQuery } from 'react-query'; +import useDevice from '@/core/hooks/useDevice'; + const Breadcrumb = ({ categoryId }) => { const breadcrumbs = useQuery( - `category-breadcrumbs/${categoryId}`, - async () => await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) - ) - - return ( - <div className='container mx-auto py-4 md:py-6'> - <Skeleton isLoaded={!breadcrumbs.isLoading} className='w-2/3'> - <ChakraBreadcrumb> - <BreadcrumbItem> - <BreadcrumbLink as={Link} href='/' className='!text-danger-500 whitespace-nowrap'> - Home - </BreadcrumbLink> - </BreadcrumbItem> - - {breadcrumbs.data?.map((category, index) => ( - <BreadcrumbItem key={index} isCurrentPage={index === breadcrumbs.data.length - 1}> - {index === breadcrumbs.data.length - 1 ? ( - <BreadcrumbLink className='whitespace-nowrap'>{category.name}</BreadcrumbLink> - ) : ( + ['category-breadcrumbs', categoryId], + async () => + await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) + ); + const { isDesktop, isMobile } = useDevice(); + + const items = breadcrumbs.data ?? []; + const lastIdx = items.length - 1; + + if (isDesktop) { + return ( + <div className='container mx-auto py-4 md:py-6'> + <Skeleton isLoaded={!breadcrumbs.isLoading} className='w-2/3'> + <ChakraBreadcrumb + spacing='8px' + sx={{ + '& ol': { + display: 'flex', + flexWrap: { base: 'wrap', md: 'nowrap' }, + alignItems: 'center', + }, + '& li': { display: 'inline-flex', alignItems: 'center' }, + '& li:not(:last-of-type)': { + flex: '0 0 auto', + whiteSpace: 'nowrap', + }, + '& li:last-of-type': { + flex: '1 1 auto', + minWidth: 0, + }, + }} + > + {/* Home */} + <BreadcrumbItem> + <BreadcrumbLink as={Link} href='/' className='!text-danger-500'> + Home + </BreadcrumbLink> + </BreadcrumbItem> + + {/* Categories */} + {items.map((category, index) => { + const isLast = index === lastIdx; + return ( + <BreadcrumbItem key={index} isCurrentPage={isLast}> + {isLast ? ( + <BreadcrumbLink className='block whitespace-normal break-words md:whitespace-nowrap'> + {category.name} + </BreadcrumbLink> + ) : ( + <BreadcrumbLink + as={Link} + href={createSlug( + '/shop/category/', + category.name, + category.id + )} + className='!text-danger-500' + > + {category.name} + </BreadcrumbLink> + )} + </BreadcrumbItem> + ); + })} + </ChakraBreadcrumb> + </Skeleton> + </div> + ); + } + + if (isMobile) { + const items = breadcrumbs.data ?? []; + const n = items.length; + const lastCat = n >= 1 ? items[n - 1] : null; // terakhir (current) + const secondLast = n >= 2 ? items[n - 2] : null; // sebelum current + const beforeSecond = n >= 3 ? items[n - 3] : null; // sebelum secondLast + const hiddenText = + n >= 3 + ? items + .slice(0, n - 2) + .map((c) => c.name) + .join(' / ') + : ''; + + return ( + <div className='container mx-auto py-2 mt-2'> + <Skeleton isLoaded={!breadcrumbs.isLoading} className='w-full'> + <ChakraBreadcrumb + separator={<span className='mx-1'>/</span>} // lebih rapat + spacing='4px' + sx={{ + '& ol': { + display: 'flex', + alignItems: 'center', + overflow: 'hidden', // untuk ellipsis + whiteSpace: 'nowrap', // untuk ellipsis + gap: '0', // no extra gap + }, + '& li': { display: 'inline-flex', alignItems: 'center' }, + '& li:not(:last-of-type)': { + flex: '0 0 auto', + whiteSpace: 'nowrap', + }, + '& li:last-of-type': { + flex: '0 1 auto', // jangan ambil full space biar gak keliatan “space kosong” + minWidth: 0, + }, + }} + className='text-caption-2 p-0' + > + {/* Home */} + <BreadcrumbItem> + <BreadcrumbLink as={Link} href='/' className='!text-danger-500'> + Home + </BreadcrumbLink> + </BreadcrumbItem> + + {/* Jika ada kategori sebelum secondLast, tampilkan '..' (link ke beforeSecond) */} + {beforeSecond && ( + <BreadcrumbItem> <BreadcrumbLink as={Link} - href={createSlug('/shop/category/', category.name, category.id)} - className='!text-danger-500 whitespace-nowrap' + href={createSlug( + '/shop/category/', + beforeSecond.name, + beforeSecond.id + )} + title={hiddenText} + aria-label={`Kembali ke ${beforeSecond.name}`} + className='!text-danger-500' > - {category.name} + .. </BreadcrumbLink> - )} - </BreadcrumbItem> - ))} - </ChakraBreadcrumb> - </Skeleton> - </div> - ) -} - -export default Breadcrumb + </BreadcrumbItem> + )} + + {/* secondLast sebagai link (kalau ada) */} + {secondLast && ( + <BreadcrumbItem> + <BreadcrumbLink + as={Link} + href={createSlug( + '/shop/category/', + secondLast.name, + secondLast.id + )} + className='!text-danger-500' + > + {secondLast.name} + </BreadcrumbLink> + </BreadcrumbItem> + )} + + {/* lastCat (current) dengan truncate & lebar dibatasi */} + {lastCat && ( + <BreadcrumbItem isCurrentPage> + <span + className='inline-block truncate align-bottom' + style={{ maxWidth: '60vw' }} // batasi lebar supaya gak “makan” baris & keliatan space kosong + title={lastCat.name} + > + {lastCat.name} + </span> + </BreadcrumbItem> + )} + </ChakraBreadcrumb> + </Skeleton> + </div> + ); + } +}; + +export default Breadcrumb; |
