feat: add robotics components and data structures
- Introduced RobotProductCard component for displaying robot details. - Added WhyUs component highlighting key reasons for choosing our robotics solutions. - Implemented CursorSpotlight for enhanced user interaction. - Created GlassPanel for a stylish UI element. - Developed MotionSection for animated section visibility. - Added PremiumButton for versatile button options. - Established data structures for industries and robots, including detailed specifications and use cases. - Included utility functions for retrieving robots by slug and category.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import { Suspense, useMemo, useState } from 'react';
|
||||
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
|
||||
import { ROBOTS, CATEGORY_LABELS, BRANDS, type RobotCategory, type RobotBrand } from '@/data/robots';
|
||||
import { RobotProductCard } from '@/components/robotics/RobotProductCard';
|
||||
import { ProductFilterTabs, type Tab } from '@/components/robotics/ProductFilterTabs';
|
||||
|
||||
type Filter = { brand: RobotBrand | 'all'; category: RobotCategory | 'all' };
|
||||
|
||||
function CatalogInner() {
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const initialBrand = (params.get('brand') as RobotBrand) || 'all';
|
||||
const initialCategory = (params.get('category') as RobotCategory) || 'all';
|
||||
|
||||
const [filter, setFilter] = useState<Filter>({ brand: initialBrand, category: initialCategory });
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return ROBOTS.filter((r) => {
|
||||
if (filter.brand !== 'all' && r.brand !== filter.brand) return false;
|
||||
if (filter.category !== 'all' && !r.categories.includes(filter.category)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [filter]);
|
||||
|
||||
const updateFilter = (next: Partial<Filter>) => {
|
||||
const newFilter = { ...filter, ...next };
|
||||
setFilter(newFilter);
|
||||
const sp = new URLSearchParams();
|
||||
if (newFilter.brand !== 'all') sp.set('brand', newFilter.brand);
|
||||
if (newFilter.category !== 'all') sp.set('category', newFilter.category);
|
||||
const qs = sp.toString();
|
||||
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
|
||||
};
|
||||
|
||||
const brandTabs: Tab[] = [
|
||||
{ id: 'all', label: 'All brands' },
|
||||
...(Object.keys(BRANDS) as RobotBrand[]).map((b) => ({
|
||||
id: b,
|
||||
label: BRANDS[b].name.split(' ')[0],
|
||||
accent: BRANDS[b].accent,
|
||||
})),
|
||||
];
|
||||
|
||||
const categoryTabs: Tab[] = [
|
||||
{ id: 'all', label: 'All' },
|
||||
...(Object.keys(CATEGORY_LABELS) as RobotCategory[]).map((c) => ({
|
||||
id: c,
|
||||
label: CATEGORY_LABELS[c],
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<div className="glass" style={{ padding: '1rem 1.25rem', borderRadius: '1.25rem', display: 'flex', flexWrap: 'wrap', gap: '1.5rem' }}>
|
||||
<ProductFilterTabs
|
||||
title="Brand"
|
||||
tabs={brandTabs}
|
||||
activeId={filter.brand}
|
||||
onChange={(id) => updateFilter({ brand: id as RobotBrand | 'all' })}
|
||||
/>
|
||||
<ProductFilterTabs
|
||||
title="Category"
|
||||
tabs={categoryTabs}
|
||||
activeId={filter.category}
|
||||
onChange={(id) => updateFilter({ category: id as RobotCategory | 'all' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||||
<span style={{ color: '#94908a', fontSize: '0.85rem' }}>
|
||||
{filtered.length} robot{filtered.length === 1 ? '' : 's'} found
|
||||
</span>
|
||||
{(filter.brand !== 'all' || filter.category !== 'all') && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateFilter({ brand: 'all', category: 'all' })}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
color: '#e0c896',
|
||||
fontSize: '0.78rem',
|
||||
letterSpacing: '0.14em',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className="card" style={{ padding: '2rem', textAlign: 'center', color: '#cbc4b3' }}>
|
||||
No robots match these filters yet. Try a different brand or category, or{' '}
|
||||
<a href="/contact/" style={{ color: '#e0c896', textDecoration: 'none' }}>contact us</a> for tailored options.
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: '1.25rem',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
|
||||
}}
|
||||
>
|
||||
{filtered.map((r) => (
|
||||
<RobotProductCard key={r.id} robot={r} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CatalogClient() {
|
||||
return (
|
||||
<Suspense fallback={<div className="card" style={{ padding: '2rem', color: '#cbc4b3' }}>Loading catalog…</div>}>
|
||||
<CatalogInner />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user