diff --git a/src/shared/ui/TechStack/index.ts b/src/shared/ui/TechStack/index.ts new file mode 100644 index 0000000..7b8d6da --- /dev/null +++ b/src/shared/ui/TechStack/index.ts @@ -0,0 +1 @@ +export { TechStackBrick, TechStackGrid } from './ui/TechStack' diff --git a/src/shared/ui/TechStack/ui/TechStack.test.tsx b/src/shared/ui/TechStack/ui/TechStack.test.tsx new file mode 100644 index 0000000..21cd240 --- /dev/null +++ b/src/shared/ui/TechStack/ui/TechStack.test.tsx @@ -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() + expect(screen.getByText('TypeScript')).toBeInTheDocument() + }) + }) + + describe('styling', () => { + it('has brutal-border class', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('brutal-border') + }) + it('has brutal-shadow class', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('brutal-shadow') + }) + it('name span has uppercase and tracking-wide', () => { + render() + const span = screen.getByText('Go') + expect(span).toHaveClass('uppercase', 'tracking-wide') + }) + it('applies custom className', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('w-full') + }) + }) +}) + +describe('TechStackGrid', () => { + describe('rendering', () => { + it('renders all skill names', () => { + render() + expect(screen.getByText('React')).toBeInTheDocument() + expect(screen.getByText('TypeScript')).toBeInTheDocument() + expect(screen.getByText('Go')).toBeInTheDocument() + }) + it('renders correct number of bricks', () => { + const { container } = render() + expect(container.firstChild!.childNodes).toHaveLength(3) + }) + it('renders empty grid with no skills', () => { + const { container } = render() + expect(container.firstChild!.childNodes).toHaveLength(0) + }) + }) + + describe('layout', () => { + it('has grid class', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('grid') + }) + it('applies custom className', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('my-custom') + }) + }) +}) diff --git a/src/shared/ui/TechStack/ui/TechStack.tsx b/src/shared/ui/TechStack/ui/TechStack.tsx new file mode 100644 index 0000000..f7fe5de --- /dev/null +++ b/src/shared/ui/TechStack/ui/TechStack.tsx @@ -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 ( +
+ {name} +
+ ) +} + +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 ( +
+ {skills.map((skill, index) => ( + + ))} +
+ ) +}