feat: add TechStackBrick and TechStackGrid components to shared/ui
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { TechStackBrick, TechStackGrid } from './ui/TechStack'
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { TechStackBrick, TechStackGrid } from './TechStack'
|
||||
|
||||
describe('TechStackBrick', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders the technology name', () => {
|
||||
render(<TechStackBrick name="TypeScript" />)
|
||||
expect(screen.getByText('TypeScript')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('styling', () => {
|
||||
it('has brutal-border class', () => {
|
||||
const { container } = render(<TechStackBrick name="React" />)
|
||||
expect(container.firstChild).toHaveClass('brutal-border')
|
||||
})
|
||||
it('has brutal-shadow class', () => {
|
||||
const { container } = render(<TechStackBrick name="React" />)
|
||||
expect(container.firstChild).toHaveClass('brutal-shadow')
|
||||
})
|
||||
it('name span has uppercase and tracking-wide', () => {
|
||||
render(<TechStackBrick name="Go" />)
|
||||
const span = screen.getByText('Go')
|
||||
expect(span).toHaveClass('uppercase', 'tracking-wide')
|
||||
})
|
||||
it('applies custom className', () => {
|
||||
const { container } = render(<TechStackBrick name="Go" className="w-full" />)
|
||||
expect(container.firstChild).toHaveClass('w-full')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('TechStackGrid', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders all skill names', () => {
|
||||
render(<TechStackGrid skills={['React', 'TypeScript', 'Go']} />)
|
||||
expect(screen.getByText('React')).toBeInTheDocument()
|
||||
expect(screen.getByText('TypeScript')).toBeInTheDocument()
|
||||
expect(screen.getByText('Go')).toBeInTheDocument()
|
||||
})
|
||||
it('renders correct number of bricks', () => {
|
||||
const { container } = render(<TechStackGrid skills={['A', 'B', 'C']} />)
|
||||
expect(container.firstChild!.childNodes).toHaveLength(3)
|
||||
})
|
||||
it('renders empty grid with no skills', () => {
|
||||
const { container } = render(<TechStackGrid skills={[]} />)
|
||||
expect(container.firstChild!.childNodes).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('layout', () => {
|
||||
it('has grid class', () => {
|
||||
const { container } = render(<TechStackGrid skills={['A']} />)
|
||||
expect(container.firstChild).toHaveClass('grid')
|
||||
})
|
||||
it('applies custom className', () => {
|
||||
const { container } = render(<TechStackGrid skills={[]} className="my-custom" />)
|
||||
expect(container.firstChild).toHaveClass('my-custom')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import { cn } from '$shared/lib'
|
||||
|
||||
interface TechStackBrickProps {
|
||||
/**
|
||||
* Technology name displayed in the brick
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* CSS classes
|
||||
*/
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Single technology label brick with brutalist border and hover effect.
|
||||
*/
|
||||
export function TechStackBrick({ name, className }: TechStackBrickProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'brutal-border brutal-shadow bg-white px-4 py-3 text-center',
|
||||
'transition-all duration-200 hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px]',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className="text-sm uppercase tracking-wide">{name}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface TechStackGridProps {
|
||||
/**
|
||||
* List of technology names to render as bricks
|
||||
*/
|
||||
skills: string[]
|
||||
/**
|
||||
* CSS classes
|
||||
*/
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsive grid of TechStackBrick items.
|
||||
*/
|
||||
export function TechStackGrid({ skills, className }: TechStackGridProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{skills.map((skill, index) => (
|
||||
<TechStackBrick key={index} name={skill} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user