Skip to content

Commit

Permalink
feat: add workCard components
Browse files Browse the repository at this point in the history
  • Loading branch information
tosaken1116 committed Dec 28, 2023
1 parent 80ca9c3 commit c730c13
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 0 deletions.
152 changes: 152 additions & 0 deletions src/components/model/Work/components/WorkCard/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { WorkCard, WorkCardLoading } from './presentations';

import type { Meta, StoryObj } from '@storybook/react';

import { dateFormat } from '@/libs/dateFormat';

const meta: Meta<typeof WorkCard> = {
component: WorkCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof WorkCard>;

export const Default: Story = {
args: {
isPublic: true,
title: 'title',
tags: [
{
id: '1',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '2',
name: 'name',
textColor: '#ffffff',
color: '#000000',
},
],
createdAt: dateFormat(new Date().toString(), 'yyyy/MM/dd'),
creator: {
id: '1',
displayName: 'name',
avatarUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
thumbnailUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
};

export const Private: Story = {
args: {
isPublic: false,
title: 'title',
tags: [
{
id: '1',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '2',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
],
createdAt: dateFormat(new Date().toString(), 'yyyy/MM/dd'),
creator: {
id: '1',
displayName: 'name',
avatarUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
thumbnailUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
};

export const LongTitle: Story = {
args: {
isPublic: true,
title:
'とても長いタイトルですううううううううううううううううううううううううううううううううう',
tags: [
{
id: '1',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '2',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
],
createdAt: dateFormat(new Date().toString(), 'yyyy/MM/dd'),
creator: {
id: '1',
displayName: 'name',
avatarUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
thumbnailUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
};

export const NumerousTags: Story = {
args: {
isPublic: true,
title: 'title',
tags: [
{
id: '1',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '2',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '3',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '4',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
{
id: '5',
name: 'name',
color: '#000000',
textColor: '#ffffff',
},
],
createdAt: dateFormat(new Date().toString(), 'yyyy/MM/dd'),
creator: {
id: '1',
displayName: 'name',
avatarUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
thumbnailUrl: 'https://avatars.githubusercontent.com/u/94045195?v=4',
},
};

export const Loading: Story = {
render: () => <WorkCardLoading />,
};
72 changes: 72 additions & 0 deletions src/components/model/Work/components/WorkCard/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { render } from '@testing-library/react';

import '@testing-library/jest-dom';
import { WorkCard } from '.';

import type { ImageProps } from 'next/image';

jest.mock('next/image', () => ({
__esModule: true,
default: (props: ImageProps & { src?: string }): JSX.Element => (
// eslint-disable-next-line @next/next/no-img-element
<img src={props.src} alt={props.alt} />
),
}));

describe('model/WorkCard', () => {
const mockProps = {
isPublic: true,
title: 'Sample Title',
tags: [
{ id: '1', name: 'Tag 1', color: 'red', textColor: 'white' },
{ id: '2', name: 'Tag 2', color: 'blue', textColor: 'white' },
],
creator: {
id: '1',
avatarUrl: '/mock.png',
displayName: 'John Doe',
},
createdAt: '2022-01-01',
thumbnailUrl: '/mock.png',
};

it('renders the WorkCard component', () => {
const { container } = render(<WorkCard {...mockProps} />);
expect(container.firstChild).toHaveClass('p-2 w-60 h-80');
});

it('renders the thumbnail image', () => {
const { getByAltText } = render(<WorkCard {...mockProps} />);
const thumbnailImage = getByAltText(`${mockProps.title}のサムネイル`);
expect(thumbnailImage).toBeInTheDocument();
expect(thumbnailImage).toHaveAttribute('src', mockProps.thumbnailUrl);
});

it('renders the title', () => {
const { getByText } = render(<WorkCard {...mockProps} />);
const titleElement = getByText(mockProps.title);
expect(titleElement).toBeInTheDocument();
});

it('renders the tags', () => {
const { getByText } = render(<WorkCard {...mockProps} />);
mockProps.tags.forEach((tag) => {
const tagElement = getByText(tag.name);
expect(tagElement).toBeInTheDocument();
expect(tagElement).toHaveStyle(`background-color: ${tag.color}`);
expect(tagElement).toHaveStyle('color: white');
});
});

it('renders the creator', () => {
const { getByText } = render(<WorkCard {...mockProps} />);
const creatorElement = getByText(mockProps.creator.displayName);
expect(creatorElement).toBeInTheDocument();
});

it('renders the creation date', () => {
const { getByText } = render(<WorkCard {...mockProps} />);
const creationDateElement = getByText(mockProps.createdAt);
expect(creationDateElement).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions src/components/model/Work/components/WorkCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './presentations';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './main';

export { WorkCardLoading } from './loading';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { FC } from 'react';

import { Card, CardContent } from '@/components/ui/Card';
import { Skeleton } from '@/components/ui/Skeleton';

export const WorkCardLoading: FC = () => (
<Card className="p-2 w-60 h-80">
<Skeleton className="aspect-thumbnail rounded-sm" />
<CardContent className="flex p-0 gap-4 flex-col pt-4">
<Skeleton className="w-24 h-6" />
<TagLoading />
<div>
<UserLoading />
<div className="flex-grow text-end float-end">
<Skeleton className="w-16 h-3" />
</div>
</div>
</CardContent>
</Card>
);

const TagLoading: FC = () => (
<div className="flex flex-row gap-2 pb-2">
{Array.from({ length: 3 }, (_, i) => (
<Skeleton key={i} className="w-12 h-5" />
))}
</div>
);

const UserLoading: FC = () => (
<div className="flex items-center gap-2">
<Skeleton className="w-6 h-6 rounded-full" />
<div className="flex flex-col">
<Skeleton className="w-20 h-4" />
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { FC } from 'react';

import { Globe, Lock } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';

import { Avatar, AvatarImage } from '@/components/ui/Avatar';
import { Card, CardContent, CardTitle } from '@/components/ui/Card';
import { Typography } from '@/components/ui/Typography';

{
/* TODO:user domainが作成されたらそこからimportする */
}
type User = {
id: string;
avatarUrl: string;
displayName: string;
};

{
/* TODO:tag domainが作成されたらそこからimportする */
}
type Tag = {
id: string;
name: string;
color: string;
textColor: string;
};

type Props = {
isPublic: boolean;
title: string;
tags: Tag[];
creator: User;
createdAt: string;
thumbnailUrl: string;
};

export const WorkCard: FC<Props> = ({
isPublic,
title,
tags,
createdAt,
creator,
thumbnailUrl,
}) => (
<Card className="p-2 w-60 h-80">
<div className="relative aspect-thumbnail rounded-sm overflow-hidden">
<Visibility isPublic={isPublic} />
<Image
src={thumbnailUrl}
alt={`${title}のサムネイル`}
fill
className="object-cover"
/>
</div>
<CardContent className="flex p-0 gap-4 flex-col pt-4">
<CardTitle className="text-ellipsis overflow-hidden text-nowrap">
{title}
</CardTitle>
<div className="flex flex-row gap-2 overflow-scroll pb-2">
{/* TODO:tag domainが作成されたらそこからimportする */}
{tags.map((tag) => (
<span
key={tag.id}
className="rounded-sm px-2 text-sm"
style={{ backgroundColor: tag.color, color: tag.textColor }}
>
{tag.name}
</span>
))}
</div>
<div>
{/* TODO:user domainが作成されたらそこからimportする */}
<UserCard {...creator} />
<span className="flex-grow text-end float-end">
<Typography variant="caption">{createdAt}</Typography>
</span>
</div>
</CardContent>
</Card>
);

const UserCard: FC<User> = ({ id, avatarUrl, displayName }) => (
<div className="flex flex-row gap-2 relative">
<Link className="w-full h-full absolute" href={`/users/${id}`} />
<Avatar className="w-6 h-6 border-[0.5px] border-black">
<AvatarImage src={avatarUrl} />
</Avatar>
<p className="font-bold">{displayName}</p>
</div>
);

const Visibility: FC<{ isPublic: boolean }> = ({ isPublic }) => (
<span className="absolute z-50 left-1 top-1 text-green-light drop-shadow">
{isPublic ? <Globe className="w-8 h-8" /> : <Lock className="w-8 h-8" />}
</span>
);
2 changes: 2 additions & 0 deletions src/components/model/Work/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export * from './WorkCard';
3 changes: 3 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ module.exports = {
},
},
extend: {
aspectRatio: {
thumbnail: '21/16',
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
Expand Down

0 comments on commit c730c13

Please sign in to comment.