+
- isActive: activeItem === 'Analytics',
+
-
+
{
}
/>
-
+
+
{
+ setActiveRoute(route)
+
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ })
+ }}
+ />
)
}
diff --git a/src/shared/composites/BottomNav/BottomNav.stories.tsx b/src/shared/composites/BottomNav/BottomNav.stories.tsx
new file mode 100644
index 0000000..1204db6
--- /dev/null
+++ b/src/shared/composites/BottomNav/BottomNav.stories.tsx
@@ -0,0 +1,147 @@
+import { useState } from 'react'
+
+import type { Meta, StoryObj } from '@storybook/react-vite'
+
+import {
+ QueueListIcon,
+ CheckIcon,
+ ExclamationTriangleIcon,
+ UsersIcon,
+ PresentationChartLineIcon,
+} from '@heroicons/react/24/outline'
+
+import { BottomNav } from './BottomNav'
+
+const items = [
+ {
+ label: 'Queue',
+
+ route: '/queue',
+
+ icon:
,
+
+ badge: 3,
+
+ badgeVariant: 'danger' as const,
+ },
+
+ {
+ label: 'Resolved',
+
+ route: '/resolved',
+
+ icon:
,
+
+ badge: 12,
+
+ badgeVariant: 'info' as const,
+ },
+
+ {
+ label: 'Escalated',
+
+ route: '/escalated',
+
+ icon:
,
+
+ badge: 1,
+
+ badgeVariant: 'danger' as const,
+ },
+
+ {
+ label: 'Users',
+
+ route: '/users',
+
+ icon:
,
+ },
+
+ {
+ label: 'Analytics',
+
+ route: '/analytics',
+
+ icon:
,
+ },
+]
+
+const meta = {
+ title: 'Shared/Composites/BottomNav',
+
+ component: BottomNav,
+
+ tags: ['autodocs'],
+
+ parameters: {
+ layout: 'fullscreen',
+
+ viewport: {
+ defaultViewport: 'mobile1',
+ },
+ },
+
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ render: () => (
+ {
+ console.log(route)
+ }}
+ />
+ ),
+}
+
+export const ThirdItemActive: Story = {
+ render: () => (
+ {
+ console.log(route)
+ }}
+ />
+ ),
+}
+
+export const Interactive: Story = {
+ render: () => {
+ const [activeRoute, setActiveRoute] = useState('/queue')
+
+ return (
+ {
+ setActiveRoute(route)
+ }}
+ />
+ )
+ },
+}
+
+export const AccessibilityPreview: Story = {
+ render: () => (
+ {
+ console.log(route)
+ }}
+ />
+ ),
+}
diff --git a/src/shared/composites/BottomNav/BottomNav.tsx b/src/shared/composites/BottomNav/BottomNav.tsx
new file mode 100644
index 0000000..9a7b29f
--- /dev/null
+++ b/src/shared/composites/BottomNav/BottomNav.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+
+import BottomNavItem from '../BottomNavItem'
+
+export interface BottomNavItemConfig {
+ label: string
+
+ route: string
+
+ icon: React.ReactNode
+
+ badge?: number
+
+ badgeVariant?: 'danger' | 'info'
+}
+
+export interface BottomNavProps {
+ items: BottomNavItemConfig[]
+
+ activeRoute: string
+
+ onNavigate: (route: string) => void
+
+ className?: string
+}
+
+export function BottomNav({ items, activeRoute, onNavigate, className = '' }: BottomNavProps) {
+ return (
+
+ )
+}
+
+export default BottomNav
diff --git a/src/shared/composites/BottomNav/index.ts b/src/shared/composites/BottomNav/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/shared/composites/BottomNavItem/BottomNavItem.stories.tsx b/src/shared/composites/BottomNavItem/BottomNavItem.stories.tsx
new file mode 100644
index 0000000..7f0b8ba
--- /dev/null
+++ b/src/shared/composites/BottomNavItem/BottomNavItem.stories.tsx
@@ -0,0 +1,89 @@
+import type { Meta, StoryObj } from '@storybook/react-vite'
+
+import { QueueListIcon, CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'
+
+import { BottomNavItem } from './BottomNavItem'
+
+const meta = {
+ title: 'Shared/Composites/BottomNavItem',
+
+ component: BottomNavItem,
+
+ tags: ['autodocs'],
+
+ parameters: {
+ layout: 'centered',
+
+ viewport: {
+ defaultViewport: 'mobile1',
+ },
+ },
+
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ label: 'Queue',
+
+ icon: ,
+ },
+}
+
+export const Active: Story = {
+ args: {
+ label: 'Queue',
+
+ isActive: true,
+
+ icon: ,
+ },
+}
+
+export const DangerBadge: Story = {
+ args: {
+ label: 'Escalated',
+
+ badge: 3,
+
+ badgeVariant: 'danger',
+
+ icon: ,
+ },
+}
+
+export const InfoBadge: Story = {
+ args: {
+ label: 'Resolved',
+
+ badge: 12,
+
+ badgeVariant: 'info',
+
+ icon: ,
+ },
+}
+
+export const AccessibilityPreview: Story = {
+ args: {
+ label: 'Queue',
+
+ badge: 3,
+
+ badgeVariant: 'danger',
+
+ isActive: true,
+
+ icon: ,
+ },
+}
diff --git a/src/shared/composites/BottomNavItem/BottomNavItem.tsx b/src/shared/composites/BottomNavItem/BottomNavItem.tsx
new file mode 100644
index 0000000..a42fa15
--- /dev/null
+++ b/src/shared/composites/BottomNavItem/BottomNavItem.tsx
@@ -0,0 +1,106 @@
+import React from 'react'
+
+import { cva } from 'class-variance-authority'
+
+import { Badge } from '@/shared/primitives/Badge'
+
+const bottomNavItem = cva(
+ `
+ relative
+ flex
+ flex-1
+ flex-col
+ items-center
+ justify-center
+ gap-1
+ rounded-lg
+ px-2
+ py-2
+ transition-colors
+ duration-150
+ select-none
+ focus-visible:outline-none
+ focus-visible:ring-2
+ focus-visible:ring-border-info
+ active:scale-[0.98]
+ `,
+ {
+ variants: {
+ isActive: {
+ true: `
+ text-text-primary
+ font-medium
+ `,
+
+ false: `
+ text-text-tertiary
+ hover:text-text-primary
+ `,
+ },
+ },
+
+ defaultVariants: {
+ isActive: false,
+ },
+ }
+)
+
+export interface BottomNavItemProps {
+ label: string
+
+ icon: React.ReactNode
+
+ isActive?: boolean
+
+ badge?: number
+
+ badgeVariant?: 'danger' | 'info'
+
+ onClick?: () => void
+
+ className?: string
+}
+
+export function BottomNavItem({
+ label,
+ icon,
+ isActive = false,
+ badge,
+ badgeVariant = 'info',
+ onClick,
+ className,
+}: BottomNavItemProps) {
+ return (
+
+ )
+}
+
+export default BottomNavItem
diff --git a/src/shared/composites/BottomNavItem/index.ts b/src/shared/composites/BottomNavItem/index.ts
new file mode 100644
index 0000000..b17d213
--- /dev/null
+++ b/src/shared/composites/BottomNavItem/index.ts
@@ -0,0 +1,3 @@
+export * from './BottomNavItem'
+
+export { default } from './BottomNavItem'
diff --git a/src/shared/composites/ListCard/ListCard.tsx b/src/shared/composites/ListCard/ListCard.tsx
index 2186009..651aeef 100644
--- a/src/shared/composites/ListCard/ListCard.tsx
+++ b/src/shared/composites/ListCard/ListCard.tsx
@@ -1,7 +1,6 @@
import React from 'react'
import { BaseCard } from '@/shared/primitives/BaseCard'
-// import { Button } from '@/stories/Button'
export interface ListCardProps {
children: React.ReactNode
@@ -34,40 +33,33 @@ export function ListCard({
onToggle,
- className = 'cursor-pointer',
-}: ListCardProps) {
- const handleToggle = () => {
- if (!isClickable || !onToggle) {
- return
- }
-
- onToggle()
- }
+ className = '',
+ testId,
+}: ListCardProps) {
return (
-
- {
- if (isClickable && (e.key === 'Enter' || e.key === ' ')) {
- e.preventDefault()
-
- handleToggle()
- }
- }}
- className="outline-none"
+
+ {/* Header Trigger */}
+
+
+
+ {/* Expanded Content */}
+ {isExpanded && (
+ <>
+ {children}
+
+ {footer && (
+ {footer}
+ )}
+ >
+ )}
)
}
diff --git a/src/shared/composites/ReportCard/ReportCard.tsx b/src/shared/composites/ReportCard/ReportCard.tsx
index b0f0e7f..4290065 100644
--- a/src/shared/composites/ReportCard/ReportCard.tsx
+++ b/src/shared/composites/ReportCard/ReportCard.tsx
@@ -267,7 +267,7 @@ export function ReportCard({
Being reviewed by {claimedBy}
) : (
-