74 lines
2.4 KiB
Text
74 lines
2.4 KiB
Text
---
|
|
import { Image } from '@astrojs/image/components';
|
|
|
|
import IconButton from '@/components/icon-button.astro';
|
|
import LabelledValue from '@/components/labelled-value.astro';
|
|
import TagsList from '@/components/tags-list.astro';
|
|
import Timestamp from '@/components/timestamp.astro';
|
|
import Typography from '@/components/typography.astro';
|
|
import type { I18n } from '@/types/i18n';
|
|
import type { Project } from '@/types/portfolio-section';
|
|
|
|
export interface Props {
|
|
project: Project;
|
|
i18n: I18n;
|
|
}
|
|
const { project, i18n } = Astro.props;
|
|
const { description, details, endDate, name, socials, startDate, tags, image } = project;
|
|
|
|
// Alt has to destructured separately, because otherwise eslint complains about
|
|
// the missing alt attribute on the Image component.
|
|
const { alt, ...sharedImageProps } = {
|
|
src: image,
|
|
aspectRatio: 1,
|
|
alt: `Thumbnail for ${name} project`,
|
|
format: 'webp',
|
|
} as const;
|
|
---
|
|
|
|
<div class="flex flex-col gap-6">
|
|
<div class="flex flex-col gap-4">
|
|
<div class="flex gap-6">
|
|
<Image
|
|
class="rounded-lg object-cover max-w-[120px] overflow-hidden sm:block hidden"
|
|
{...sharedImageProps}
|
|
alt={alt}
|
|
/>
|
|
<div class="flex w-full flex-col gap-4">
|
|
<div class="flex justify-between">
|
|
<div>
|
|
<Typography variant="item-title">{name}</Typography>
|
|
<Timestamp
|
|
startDate={startDate}
|
|
endDate={endDate}
|
|
locale={i18n.locale}
|
|
translationForNow={i18n.translations.now}
|
|
/>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
{
|
|
socials?.map(({ icon, url, name: socialName }) => (
|
|
<IconButton icon={icon} href={url} target="_blank" size="small" aria-label={socialName} />
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
<Image class="rounded-lg object-cover max-w-[120px] sm:hidden" {...sharedImageProps} alt={alt} />
|
|
<div class="inline-grid w-full xl:grid-cols-[auto_auto]">
|
|
{
|
|
details.map(({ label: detailLabel, value: detailValue }) => (
|
|
<LabelledValue
|
|
label={detailLabel}
|
|
value={typeof detailValue === 'object' ? detailValue.join(', ') : detailValue}
|
|
/>
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-span-3 col-start-1">
|
|
<Typography variant="paragraph">{description}</Typography>
|
|
</div>
|
|
</div>
|
|
<TagsList tags={tags} />
|
|
</div>
|