feat: CardSidebar layout component and ExperienceCard sidebar redesign
Sidebar: period badge, company, stack tags. Main: role title and rich-text description.
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
export type { CardBackground } from './ui/Card';
|
||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './ui/Card';
|
||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardSidebar, CardTitle } from './ui/Card';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './Card';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardSidebar, CardTitle } from './Card';
|
||||
|
||||
describe('Card', () => {
|
||||
describe('rendering', () => {
|
||||
@@ -72,3 +72,51 @@ describe('CardFooter', () => {
|
||||
expect(el).toHaveClass('brutal-border-top', 'mt-6', 'pt-6', 'md:mt-8', 'md:pt-8');
|
||||
});
|
||||
});
|
||||
describe('CardSidebar', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders sidebar content', () => {
|
||||
render(<CardSidebar sidebar={<span>Sidebar</span>}>Main</CardSidebar>);
|
||||
expect(screen.getByText('Sidebar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders main content', () => {
|
||||
render(<CardSidebar sidebar={<span>Sidebar</span>}>Main</CardSidebar>);
|
||||
expect(screen.getByText('Main')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('structure', () => {
|
||||
it('root wrapper is a flex container', () => {
|
||||
const { container } = render(<CardSidebar sidebar={<span>S</span>}>M</CardSidebar>);
|
||||
expect(container.firstChild).toHaveClass('flex');
|
||||
});
|
||||
|
||||
it('sidebar column has brutal-border-sidebar class', () => {
|
||||
render(<CardSidebar sidebar={<span>Sidebar</span>}>Main</CardSidebar>);
|
||||
const sidebar = screen.getByText('Sidebar').parentElement;
|
||||
expect(sidebar).toHaveClass('brutal-border-sidebar');
|
||||
});
|
||||
|
||||
it('sidebar column has fixed width on md', () => {
|
||||
render(<CardSidebar sidebar={<span>Sidebar</span>}>Main</CardSidebar>);
|
||||
const sidebar = screen.getByText('Sidebar').parentElement;
|
||||
expect(sidebar).toHaveClass('md:w-52');
|
||||
});
|
||||
|
||||
it('main column fills remaining space', () => {
|
||||
render(<CardSidebar sidebar={<span>Sidebar</span>}>Main</CardSidebar>);
|
||||
expect(screen.getByText('Main')).toHaveClass('flex-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('className passthrough', () => {
|
||||
it('forwards className to the root wrapper', () => {
|
||||
const { container } = render(
|
||||
<CardSidebar sidebar={<span>S</span>} className="custom">
|
||||
M
|
||||
</CardSidebar>,
|
||||
);
|
||||
expect(container.firstChild).toHaveClass('custom');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,3 +85,32 @@ export function CardContent({ children, className }: SlotProps) {
|
||||
export function CardFooter({ children, className }: SlotProps) {
|
||||
return <div className={cn('mt-6 md:mt-8 pt-6 md:pt-8 brutal-border-top', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
interface CardSidebarProps {
|
||||
/**
|
||||
* Left sidebar content — metadata such as period, company, stack
|
||||
*/
|
||||
sidebar: ReactNode;
|
||||
/**
|
||||
* Main content — primary info such as role title and description
|
||||
*/
|
||||
children: ReactNode;
|
||||
/**
|
||||
* Additional CSS classes for the root wrapper
|
||||
*/
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Two-column card layout: narrow sidebar on the left, main content on the right.
|
||||
* On mobile the columns stack vertically with a bottom border separator;
|
||||
* on md+ they sit side-by-side with a right border separator.
|
||||
*/
|
||||
export function CardSidebar({ sidebar, children, className }: CardSidebarProps) {
|
||||
return (
|
||||
<div className={cn('flex flex-col md:flex-row', className)}>
|
||||
<div className="shrink-0 md:w-52 brutal-border-sidebar pb-6 md:pb-0 md:pr-8 mb-6 md:mb-0">{sidebar}</div>
|
||||
<div className="flex-1 min-w-0 md:pl-8">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ export { Badge } from './Badge';
|
||||
export type { ButtonSize, ButtonVariant } from './Button';
|
||||
export { Button } from './Button';
|
||||
export type { CardBackground } from './Card';
|
||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './Card';
|
||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardSidebar, CardTitle } from './Card';
|
||||
|
||||
export { Input, Textarea } from './Input';
|
||||
export { RichText } from './RichText';
|
||||
|
||||
Reference in New Issue
Block a user