feat: ExperienceCard stack field and Card subcomponent layout
This commit is contained in:
@@ -6,6 +6,7 @@ const DEFAULT_PROPS = {
|
|||||||
company: 'Acme Corp',
|
company: 'Acme Corp',
|
||||||
period: '2021 – 2024',
|
period: '2021 – 2024',
|
||||||
description: 'Built scalable frontend systems.',
|
description: 'Built scalable frontend systems.',
|
||||||
|
stack: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('ExperienceCard', () => {
|
describe('ExperienceCard', () => {
|
||||||
@@ -32,9 +33,9 @@ describe('ExperienceCard', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('structure', () => {
|
describe('structure', () => {
|
||||||
it('title is rendered as an h4', () => {
|
it('title is rendered as an h3', () => {
|
||||||
render(<ExperienceCard {...DEFAULT_PROPS} />);
|
render(<ExperienceCard {...DEFAULT_PROPS} />);
|
||||||
expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('Senior Developer');
|
expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Senior Developer');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('period badge has brutal-border, bg-blue, text-cream, text-sm', () => {
|
it('period badge has brutal-border, bg-blue, text-cream, text-sm', () => {
|
||||||
@@ -43,11 +44,11 @@ describe('ExperienceCard', () => {
|
|||||||
expect(badge).toHaveClass('brutal-border', 'bg-blue', 'text-cream', 'text-sm');
|
expect(badge).toHaveClass('brutal-border', 'bg-blue', 'text-cream', 'text-sm');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('company paragraph has opacity-80', () => {
|
it('company paragraph has opacity-60', () => {
|
||||||
render(<ExperienceCard {...DEFAULT_PROPS} />);
|
render(<ExperienceCard {...DEFAULT_PROPS} />);
|
||||||
const company = screen.getByText('Acme Corp');
|
const company = screen.getByText('Acme Corp');
|
||||||
expect(company.tagName).toBe('P');
|
expect(company.tagName).toBe('P');
|
||||||
expect(company).toHaveClass('opacity-80');
|
expect(company).toHaveClass('opacity-60');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('description renders via RichText with rich-text class', () => {
|
it('description renders via RichText with rich-text class', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, RichText } from '$shared/ui';
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle, RichText } from '$shared/ui';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +17,10 @@ type Props = {
|
|||||||
* Description of responsibilities and achievements
|
* Description of responsibilities and achievements
|
||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
|
/**
|
||||||
|
* Technologies used during this role
|
||||||
|
*/
|
||||||
|
stack: string[];
|
||||||
/**
|
/**
|
||||||
* Additional CSS classes forwarded to the card
|
* Additional CSS classes forwarded to the card
|
||||||
*/
|
*/
|
||||||
@@ -24,19 +28,30 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Work experience card with title, company, period, and description.
|
* Work experience card with title, company, period, description, and tech stack.
|
||||||
*/
|
*/
|
||||||
export function ExperienceCard({ title, company, period, description, className }: Props) {
|
export function ExperienceCard({ title, company, period, description, stack, className }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between mb-4 gap-4">
|
<CardHeader className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 pb-6 md:pb-8 brutal-border-bottom">
|
||||||
<div className="flex-1 max-w-[700px]">
|
<div className="flex-1">
|
||||||
<h4>{title}</h4>
|
<CardTitle className="font-heading">{title}</CardTitle>
|
||||||
<p className="text-base opacity-80">{company}</p>
|
<p className="text-base opacity-60">{company}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="brutal-border px-4 py-2 bg-blue text-cream text-sm self-start">{period}</span>
|
<span className="brutal-border px-4 py-2 bg-blue text-cream text-sm self-start">{period}</span>
|
||||||
</div>
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
<RichText html={description} />
|
<RichText html={description} />
|
||||||
|
</CardContent>
|
||||||
|
{stack.length > 0 && (
|
||||||
|
<CardFooter className="flex flex-wrap gap-2">
|
||||||
|
{stack.map((tech) => (
|
||||||
|
<span key={tech} className="brutal-border px-3 py-1 bg-cream text-blue text-sm uppercase tracking-wide">
|
||||||
|
{tech}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</CardFooter>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ export type ExperienceRecord = BaseRecord & {
|
|||||||
* Rich text description of responsibilities and achievements
|
* Rich text description of responsibilities and achievements
|
||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
|
/**
|
||||||
|
* Technologies used during this role
|
||||||
|
*/
|
||||||
|
stack: string[];
|
||||||
/**
|
/**
|
||||||
* Sorting weight for chronological display
|
* Sorting weight for chronological display
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const mockItems = [
|
|||||||
start_date: '2022-01-01T00:00:00Z',
|
start_date: '2022-01-01T00:00:00Z',
|
||||||
end_date: null,
|
end_date: null,
|
||||||
description: 'Built critical systems.',
|
description: 'Built critical systems.',
|
||||||
|
stack: ['React', 'TypeScript'],
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,6 +32,7 @@ const mockItems = [
|
|||||||
start_date: '2020-01-01T00:00:00Z',
|
start_date: '2020-01-01T00:00:00Z',
|
||||||
end_date: '2021-12-31T00:00:00Z',
|
end_date: '2021-12-31T00:00:00Z',
|
||||||
description: 'Learned the ropes.',
|
description: 'Learned the ropes.',
|
||||||
|
stack: [],
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default async function ExperienceSection() {
|
|||||||
company={exp.company}
|
company={exp.company}
|
||||||
period={formatYearRange(exp.start_date, exp.end_date)}
|
period={formatYearRange(exp.start_date, exp.end_date)}
|
||||||
description={exp.description}
|
description={exp.description}
|
||||||
|
stack={exp.stack}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user