devscard/src/web/sections/portfolio/project.astro
juyung b8a1eef13d
Some checks failed
Main Branch / Run Prettier check (push) Has been cancelled
Main Branch / Run TypeScript check (push) Has been cancelled
Main Branch / Run Astro check (push) Has been cancelled
Main Branch / Run Percy check (push) Has been cancelled
Main Branch / Create release (push) Has been cancelled
Main Branch / Deploy to Netlify (push) Has been cancelled
Main Branch / Run Lighthouse check (push) Has been cancelled
Upload
2026-03-28 05:04:47 +09:00

95 lines
3.4 KiB
Text

---
import { nanoid } from 'nanoid';
import type { PortfolioSection, Project } from '@/types/sections/portfolio-section.types';
import Description from '@/web/components/description.astro';
import LabelledValue from '@/web/components/labelled-value.astro';
import LinkButton from '@/web/components/link-button.astro';
import Photo from '@/components/photo.astro';
import TagsList from '@/web/components/tags-list.astro';
import Timestamp from '@/web/components/timestamp.astro';
import Typography from '@/web/components/typography.astro';
import Thumbnail from '@/web/components/thumbnail.astro';
export interface Props extends Project {
screenshotsConfig?: PortfolioSection['config']['screenshots'];
}
const { dates, description, details, image, links, name, tagsList, screenshots, screenshotsConfig } = Astro.props;
const alt = `${name} project thumbnail`;
const galleryId = nanoid(8);
const hasScreenshots = screenshots?.length && screenshots.length > 0;
const screenshotsIcon = screenshotsConfig?.icon || 'fa6-solid:image';
const screenshotsTooltip = screenshotsConfig?.title || 'Screenshots';
---
<div class="flex flex-col">
<!-- gap-6">-->
<div class="flex flex-col">
<!-- gap-4">-->
<div class="flex">
<!-- gap-6">-->
<div class="flex w-full flex-col gap-4">
<div class="flex gap-4">
<Thumbnail src={image} alt={alt} size="small" />
<div class="flex w-full justify-between">
<div>
<Typography variant="item-title">{name}</Typography>
<!--<Timestamp dates={dates} />-->
<Typography variant="item-subtitle-secondary">{description}</Typography>
</div>
<div class="flex gap-2">
{links.map((link) => <LinkButton {...link} />)}
{
hasScreenshots && (
<LinkButton icon={screenshotsIcon} name={screenshotsTooltip} as="button" data-gallery={galleryId} />
)
}
</div>
</div>
</div>
<div class="inline-grid w-full xl:grid-cols-[auto_auto]">
{details.map((detail) => <LabelledValue {...detail} />)}
</div>
</div>
</div>
<!--<Description content={description} class="col-span-3 col-start-1" /> -->
</div>
<TagsList {...tagsList} />
<div class="hidden" id={galleryId}>
{screenshots?.map((screenshot) => <Photo {...screenshot} />)}
</div>
</div>
<script>
import type { PhotoSwipeOptions, DataSource } from 'photoswipe';
const buttons = [...document.querySelectorAll('[data-gallery]')] as HTMLButtonElement[];
if (buttons.length > 0) {
import('photoswipe/style.css');
const getOptionsForButton = (button: HTMLButtonElement): PhotoSwipeOptions => {
const galleryId = String(button.dataset.gallery);
const galleryWrapper = document.getElementById(galleryId) as HTMLElement;
const screenshots = [...galleryWrapper.children] as HTMLImageElement[];
const dataSource: DataSource = screenshots.map((img) => ({
src: img.src,
width: img.width,
height: img.height,
alt: img.alt,
}));
return { dataSource, showHideAnimationType: 'none', index: 0 };
};
import('photoswipe').then(({ default: PhotoSwipe }) => {
buttons.forEach((button) =>
button.addEventListener('click', () => {
new PhotoSwipe(getOptionsForButton(button)).init();
})
);
});
}
</script>