Create favorites section (#84)
This commit is contained in:
parent
89f1dcc0d0
commit
d6139f2c43
9 changed files with 108 additions and 35 deletions
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -8648,9 +8648,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.31.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz",
|
||||
"integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==",
|
||||
"version": "0.31.2",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.2.tgz",
|
||||
"integrity": "sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
|
|
@ -8659,7 +8659,7 @@
|
|||
"detect-libc": "^2.0.1",
|
||||
"node-addon-api": "^5.0.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"semver": "^7.3.8",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^2.1.1",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
|
|
@ -8672,9 +8672,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
|
@ -16642,9 +16642,9 @@
|
|||
"integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g=="
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.31.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz",
|
||||
"integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==",
|
||||
"version": "0.31.2",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.2.tgz",
|
||||
"integrity": "sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
|
|
@ -16652,16 +16652,16 @@
|
|||
"detect-libc": "^2.0.1",
|
||||
"node-addon-api": "^5.0.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"semver": "^7.3.8",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^2.1.1",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
|
|
|
|||
|
|
@ -14,16 +14,17 @@ const BookTile = 'a';
|
|||
|
||||
<BookTile
|
||||
href={value.url}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', props.class]}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', 'w-full', props.class]}
|
||||
>
|
||||
<Image
|
||||
class:list={['rounded-lg', 'shadow-md', 'object-cover']}
|
||||
class:list={['rounded-lg', 'shadow-md', 'aspect-[3/4]', 'object-cover']}
|
||||
src={value.cover}
|
||||
aspectRatio="1.3"
|
||||
aspectRatio={3 / 4}
|
||||
width="auto"
|
||||
alt={value.title}
|
||||
format="webp"
|
||||
/>
|
||||
<div class:list={['tile-content', 'gap-1']}>
|
||||
<div class:list={['tile-content', 'gap-1', 'w-full']}>
|
||||
<Typography class:list={['leading-5', 'hover:text-gray-900']} variant="tile-title">
|
||||
{value.title}
|
||||
</Typography>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ type Props = IconProps & Omit<TooltipProps, 'children'>;
|
|||
|
||||
const IconWithTooltip = ({ name, color, size, ...tooltipProps }: Props) => (
|
||||
<Tooltip {...tooltipProps}>
|
||||
<div>
|
||||
<div className="cursor-pointer">
|
||||
<Icon name={name} color={color} size={size} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@ const MediaTile = 'a';
|
|||
|
||||
<MediaTile
|
||||
href={value.url}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', props.class]}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', 'w-full', props.class]}
|
||||
>
|
||||
<Image
|
||||
class:list={['rounded-lg', 'shadow-md', 'object-cover']}
|
||||
class:list={['rounded-lg', 'shadow-md', 'aspect-square', 'object-cover']}
|
||||
src={value.image}
|
||||
width="auto"
|
||||
aspectRatio={1 / 1}
|
||||
alt={value.title}
|
||||
aspectRatio="1.1"
|
||||
format="webp"
|
||||
/>
|
||||
<div class:list={['tile-content', 'gap-1']}>
|
||||
|
|
|
|||
|
|
@ -14,21 +14,19 @@ const PersonTile = 'a';
|
|||
|
||||
<PersonTile
|
||||
href={value.url}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', props.class]}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', 'w-full', props.class]}
|
||||
>
|
||||
<Image
|
||||
class:list={['rounded-lg', 'shadow-md', 'object-cover', 'transition', 'duration-300']}
|
||||
class:list={['rounded-lg', 'shadow-md', 'transition', 'duration-300', 'aspect-square', 'object-cover']}
|
||||
width="auto"
|
||||
aspectRatio={1 / 1}
|
||||
src={value.image}
|
||||
alt={value.name}
|
||||
aspectRatio="1/1"
|
||||
format="webp"
|
||||
/>
|
||||
<div class:list={['tile-content', 'gap-1']}>
|
||||
<Typography class:list={['leading-5', 'hover:text-gray-900']} variant="tile-title">
|
||||
{value.name}
|
||||
</Typography>
|
||||
<Typography variant="tile-subtitle">
|
||||
{value.name}
|
||||
</Typography>
|
||||
</div>
|
||||
</PersonTile>
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ const getVideoThumbnail = (url: string) => {
|
|||
|
||||
<VideoTile
|
||||
href={value.url}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', props.class]}
|
||||
class:list={['flex', 'flex-col', 'gap-3', 'transition', 'duration-300', 'hover:translate-y-2', 'w-full', props.class]}
|
||||
>
|
||||
<Image
|
||||
class:list={['rounded-lg', 'shadow-md', 'object-cover']}
|
||||
class:list={['rounded-lg', 'shadow-md', 'aspect-video', 'object-cover']}
|
||||
src={getVideoThumbnail(value.url)}
|
||||
width="auto"
|
||||
aspectRatio="1.67"
|
||||
aspectRatio={16 / 9}
|
||||
alt={value.title}
|
||||
format="webp"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,87 @@
|
|||
---
|
||||
import type { ComponentInstance } from 'astro';
|
||||
|
||||
import BookTile from '@/atoms/book-tile.astro';
|
||||
import MediaTile from '@/atoms/media-tile.astro';
|
||||
import PersonTile from '@/atoms/person-tile.astro';
|
||||
import SectionCard from '@/atoms/section-card.astro';
|
||||
import Typography from '@/atoms/typography.astro';
|
||||
import VideoTile from '@/atoms/video-tile.astro';
|
||||
import type { Section } from '@/types/data';
|
||||
import type { FavoritesSection } from '@/types/favorites-section';
|
||||
import type { Book, FavoritesSection, Media, Person, Video } from '@/types/favorites-section';
|
||||
|
||||
export interface Props extends FavoritesSection {}
|
||||
|
||||
const {
|
||||
config: { title },
|
||||
books,
|
||||
medias,
|
||||
people,
|
||||
videos,
|
||||
} = Astro.props;
|
||||
|
||||
type Subsection = 'books' | 'medias' | 'people' | 'videos';
|
||||
type SubsectionData = Book | Media | Person | Video;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- required to avoid type casting
|
||||
type SubsectionComponent = (_props: { value: any }) => ComponentInstance;
|
||||
|
||||
interface FavoritesSubsection<T extends SubsectionData> {
|
||||
name: Subsection;
|
||||
data: T[];
|
||||
title: string;
|
||||
columnsLayout: string;
|
||||
Component: SubsectionComponent;
|
||||
}
|
||||
|
||||
const booksSubsection: FavoritesSubsection<Book> = {
|
||||
name: 'books',
|
||||
columnsLayout: 'grid-cols-4',
|
||||
Component: BookTile,
|
||||
...books,
|
||||
};
|
||||
|
||||
const mediasSubsection: FavoritesSubsection<Media> = {
|
||||
name: 'medias',
|
||||
columnsLayout: 'grid-cols-6',
|
||||
Component: MediaTile,
|
||||
...medias,
|
||||
};
|
||||
|
||||
const peopleSubsection: FavoritesSubsection<Person> = {
|
||||
name: 'people',
|
||||
columnsLayout: 'grid-cols-6',
|
||||
Component: PersonTile,
|
||||
...people,
|
||||
};
|
||||
|
||||
const videosSubsection: FavoritesSubsection<Video> = {
|
||||
name: 'videos',
|
||||
columnsLayout: 'grid-cols-3',
|
||||
Component: VideoTile,
|
||||
...videos,
|
||||
};
|
||||
|
||||
const subsections = [booksSubsection, peopleSubsection, videosSubsection, mediasSubsection];
|
||||
|
||||
const section: Section = 'favorites';
|
||||
---
|
||||
|
||||
<SectionCard section={section}
|
||||
><Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
|
||||
<SectionCard section={section}>
|
||||
<Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
|
||||
<div class:list={['flex', 'flex-col', 'gap-16']}>
|
||||
{
|
||||
subsections.map(({ Component, data, name, columnsLayout, title: subsectionTitle }) => (
|
||||
<div class:list={['flex', 'flex-col', 'gap-6']}>
|
||||
<Typography variant="section-subtitle" id={`${section}-${name}-heading`}>
|
||||
{subsectionTitle}
|
||||
</Typography>
|
||||
<div class:list={['grid', 'gap-8', columnsLayout]}>
|
||||
{data.map((value) => (
|
||||
<Component value={value} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const { seo, ...dataWithoutSeo } = data;
|
|||
return (
|
||||
sectionData && (
|
||||
<SidebarItem
|
||||
client:load
|
||||
client:only="react"
|
||||
title={sectionData.config.title}
|
||||
icon={sectionData.config.icon}
|
||||
section={key}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import MainSection from '@/sections/main-section.astro';
|
|||
import type { MainSection as MainSectionData } from '@/types/main-section';
|
||||
|
||||
const mainSectionData: MainSectionData = {
|
||||
config: {
|
||||
title: 'Main Section',
|
||||
icon: 'fa6-solid:user',
|
||||
},
|
||||
image: import('@/assets/my-image.jpeg'),
|
||||
fullName: 'Mark Freeman',
|
||||
role: 'Senior React Developer',
|
||||
|
|
|
|||
Loading…
Reference in a new issue