Skip to content

Commit f3c8d62

Browse files
authored
Merge pull request #59 from CleanCode366/50-component-card-base-component
feat: BaseCard primitive component created.
2 parents dfa7a60 + 21202f2 commit f3c8d62

7 files changed

Lines changed: 211 additions & 130 deletions

File tree

src/LoginCard.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react'
22
import { ChipGroup } from './Chip'
3+
import BaseCard from './shared/primitives/BaseCard'
34
// import { ProgressBar } from './shared/primitives/Progressbar'
45

56
function LoginCard() {
@@ -11,7 +12,10 @@ function LoginCard() {
1112

1213
return (
1314
<div className="bg-bg-primary fixed inset-0 flex items-center justify-center">
14-
<div className="bg-bg-secondary border-border-primary w-full max-w-sm space-y-6 rounded-lg border p-6 shadow-md">
15+
<BaseCard
16+
variant="success"
17+
className="bg-bg-secondary border-border-primary w-full max-w-sm space-y-6 rounded-lg border p-6 shadow-md"
18+
>
1519
<ChipGroup
1620
selected={selected}
1721
onChange={(value) => {
@@ -95,7 +99,7 @@ function LoginCard() {
9599
</a>
96100
</p>
97101
{/* <ProgressBar value={0.5} variant="danger" scoreLabel="AI conf." /> */}
98-
</div>
102+
</BaseCard>
99103
</div>
100104
)
101105
}

src/layouts/AdminLayout.tsx

Lines changed: 1 addition & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -23,131 +23,6 @@ const AdminLayout = () => {
2323
const [activeItem, setActiveItem] = useState('Queue')
2424

2525
return (
26-
// <div
27-
// className="
28-
// min-h-screen
29-
// bg-bg-primary
30-
// text-text-primary
31-
// "
32-
// >
33-
// <ToastProvider />
34-
35-
// {/* Sidebar */}
36-
// <aside
37-
// className={`fixed top-0 left-0 z-30 h-screen transition-all duration-300 ${collapsed ? 'w-18' : 'w-64'
38-
// } `}
39-
// >
40-
// <SidebarNav
41-
// collapsed={collapsed}
42-
// onToggle={() => {
43-
// setCollapsed(!collapsed)
44-
// }}
45-
// items={[
46-
// {
47-
// label: 'Queue',
48-
49-
// icon: <QueueListIcon className="size-4" />,
50-
51-
// badge: 3,
52-
53-
// badgeVariant: 'danger',
54-
55-
// isActive: activeItem === 'Queue',
56-
57-
// onClick: () => {
58-
// setActiveItem('Queue')
59-
60-
// console.log('Queue clicked')
61-
// },
62-
// },
63-
64-
// {
65-
// label: 'Resolved',
66-
67-
// icon: <CheckIcon className="size-4" />,
68-
69-
// badge: 12,
70-
71-
// badgeVariant: 'info',
72-
73-
// isActive: activeItem === 'Resolved',
74-
75-
// onClick: () => {
76-
// setActiveItem('Resolved')
77-
78-
// console.log('Resolved clicked')
79-
// },
80-
// },
81-
82-
// {
83-
// label: 'Escalated',
84-
85-
// icon: <ExclamationTriangleIcon className="size-4" />,
86-
87-
// badge: 1,
88-
89-
// badgeVariant: 'danger',
90-
91-
// isActive: activeItem === 'Escalated',
92-
93-
// onClick: () => {
94-
// setActiveItem('Escalated')
95-
96-
// console.log('Escalated clicked')
97-
// },
98-
// },
99-
100-
// {
101-
// label: 'Users',
102-
103-
// icon: <UsersIcon className="size-4" />,
104-
105-
// isActive: activeItem === 'Users',
106-
107-
// onClick: () => {
108-
// setActiveItem('Users')
109-
110-
// console.log('Users clicked')
111-
// },
112-
// },
113-
114-
// {
115-
// label: 'Analytics',
116-
117-
// icon: <PresentationChartLineIcon className="size-4" />,
118-
119-
// isActive: activeItem === 'Analytics',
120-
121-
// onClick: () => {
122-
// setActiveItem('Analytics')
123-
124-
// console.log('Analytics clicked')
125-
// },
126-
// },
127-
// ]}
128-
// />
129-
// </aside>
130-
// <main
131-
// className={`flex min-h-screen items-center justify-center transition-all duration-300 ${collapsed ? 'w-[72px]' : 'w-64'
132-
// } `}
133-
// >
134-
// </main>
135-
// <div
136-
// className={`
137-
// transition-all
138-
// duration-300
139-
// ${collapsed
140-
// ? 'ml-[72px]'
141-
// : 'ml-64'
142-
// }
143-
// `}
144-
// >
145-
146-
// <main className="p-6">
147-
// <Outlet />
148-
// </main>
149-
// </div>
150-
// </div>
15126
<div className="bg-bg-primary text-text-primary min-h-screen">
15227
<ToastProvider />
15328

@@ -253,7 +128,7 @@ const AdminLayout = () => {
253128
actionsSlot={<AvatarMenu name="Admin Mod" />}
254129
searchSlot={
255130
<input
256-
className="w-full bg-transparent text-sm outline-none"
131+
className="border-border-secondary w-full rounded-md border-2 bg-transparent px-3 py-2 text-sm outline-none"
257132
placeholder="Search reports..."
258133
/>
259134
}

src/shared/composites/Topbar/Topbar.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const WithSearch: Story = {
4646

4747
<input
4848
placeholder="Search reports..."
49-
className="w-full bg-transparent text-sm outline-none"
49+
className="border-border-secondary w-full border-2 bg-transparent text-sm outline-none"
5050
/>
5151
</div>
5252
),

src/shared/composites/Topbar/Topbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function Topbar({
2727
}: TopbarProps) {
2828
return (
2929
<header
30-
className={`border-border-tertiary bg-bg-secondary sticky top-0 z-20 flex h-16 items-center justify-between border-b px-4 md:px-6 ${className} `}
30+
className={`border-border-secondary bg-bg-secondary sticky top-0 z-20 flex h-16 items-center justify-between border-b px-4 md:px-6 ${className} `}
3131
>
3232
{/* Left */}
3333
<div className="flex items-center gap-3 md:min-w-[200px]">
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite'
2+
3+
import { BaseCard } from './BaseCard'
4+
5+
const meta = {
6+
title: 'Shared/Primitives/BaseCard',
7+
8+
component: BaseCard,
9+
10+
tags: ['autodocs'],
11+
} satisfies Meta<typeof BaseCard>
12+
13+
export default meta
14+
15+
type Story = StoryObj<typeof meta>
16+
17+
export const Default: Story = {
18+
args: {
19+
children: 'Default card content',
20+
},
21+
}
22+
23+
export const Danger: Story = {
24+
args: {
25+
variant: 'danger',
26+
27+
children: 'Danger card content',
28+
},
29+
}
30+
31+
export const Warning: Story = {
32+
args: {
33+
variant: 'warning',
34+
35+
children: 'Warning card content',
36+
},
37+
}
38+
39+
export const Success: Story = {
40+
args: {
41+
variant: 'success',
42+
43+
children: 'Success card content',
44+
},
45+
}
46+
47+
export const Info: Story = {
48+
args: {
49+
variant: 'info',
50+
51+
children: 'Info card content',
52+
},
53+
}
54+
55+
export const PaddingSmall: Story = {
56+
args: {
57+
padding: 'sm',
58+
59+
children: 'Small padding',
60+
},
61+
}
62+
63+
export const PaddingMedium: Story = {
64+
args: {
65+
padding: 'md',
66+
67+
children: 'Medium padding',
68+
},
69+
}
70+
71+
export const PaddingLarge: Story = {
72+
args: {
73+
padding: 'lg',
74+
75+
children: 'Large padding',
76+
},
77+
}
78+
79+
export const NoPadding: Story = {
80+
args: {
81+
padding: 'none',
82+
83+
children: <div className="bg-bg-secondary p-6">Parent controls spacing</div>,
84+
},
85+
}
86+
87+
export const NestedBaseCards: Story = {
88+
args: {
89+
children: '',
90+
},
91+
render: () => (
92+
<BaseCard padding="lg">
93+
<div className="space-y-4">
94+
<p>Parent card</p>
95+
96+
<BaseCard variant="info" padding="sm">
97+
Nested card
98+
</BaseCard>
99+
</div>
100+
</BaseCard>
101+
),
102+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React from 'react'
2+
3+
import { cva, type VariantProps } from 'class-variance-authority'
4+
5+
const baseCardStyles = cva(
6+
`
7+
w-full
8+
rounded-xl
9+
border
10+
transition-colors
11+
`,
12+
{
13+
variants: {
14+
variant: {
15+
default: `
16+
border-border-secondary
17+
bg-bg-primary
18+
`,
19+
20+
danger: `
21+
border-border-danger
22+
bg-bg-danger
23+
`,
24+
25+
warning: `
26+
border-border-warning
27+
bg-bg-warning
28+
`,
29+
30+
success: `
31+
border-border-success
32+
bg-bg-success
33+
`,
34+
35+
info: `
36+
border-border-info
37+
bg-bg-info
38+
`,
39+
},
40+
41+
padding: {
42+
none: '',
43+
44+
sm: 'p-3',
45+
46+
md: 'p-4',
47+
48+
lg: 'p-6',
49+
},
50+
},
51+
52+
defaultVariants: {
53+
variant: 'default',
54+
55+
padding: 'md',
56+
},
57+
}
58+
)
59+
60+
export interface BaseCardProps
61+
extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof baseCardStyles> {
62+
children: React.ReactNode
63+
64+
testId?: string
65+
}
66+
67+
export function BaseCard({
68+
children,
69+
70+
variant,
71+
72+
padding,
73+
74+
className = '',
75+
76+
testId,
77+
78+
...props
79+
}: BaseCardProps) {
80+
return (
81+
<div
82+
data-testid={testId}
83+
className={baseCardStyles({
84+
variant,
85+
86+
padding,
87+
88+
className,
89+
})}
90+
{...props}
91+
>
92+
{children}
93+
</div>
94+
)
95+
}
96+
97+
export default BaseCard
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './BaseCard'
2+
3+
export { default } from './BaseCard'

0 commit comments

Comments
 (0)