[T-4921: feat] Update project card component (#136)

* 🎨 style: add hover interaction to the card

* ️ feat: make the whole card clickable

* 🎨 style: adjust hovered background for wavy border

* ♻️ refactor: move wavy border class to project card theme

* 🎨 style: add transition when hover

* 📝 docs: add todo to experiment using `Link` componnt
This commit is contained in:
Wahyu Kurniawan 2024-02-28 16:50:55 +07:00 committed by GitHub
parent 8ee61c0c85
commit b1bf47d104
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 33 additions and 12 deletions

View File

@ -8,6 +8,7 @@ export const projectCardTheme = tv({
'rounded-2xl', 'rounded-2xl',
'flex', 'flex',
'flex-col', 'flex-col',
'group',
], ],
upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'], upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'],
content: ['flex', 'flex-col', 'gap-1', 'flex-1'], content: ['flex', 'flex-col', 'gap-1', 'flex-1'],
@ -20,13 +21,15 @@ export const projectCardTheme = tv({
description: ['text-xs', 'text-elements-low-em'], description: ['text-xs', 'text-elements-low-em'],
icons: ['flex', 'items-center', 'gap-1'], icons: ['flex', 'items-center', 'gap-1'],
lowerContent: [ lowerContent: [
'bg-surface-card-hovered', 'transition-colors',
'duration-150',
'px-4', 'px-4',
'py-4', 'py-4',
'flex', 'flex',
'flex-col', 'flex-col',
'gap-2', 'gap-2',
'rounded-b-2xl', 'rounded-b-2xl',
'group-hover:bg-surface-card-hovered',
], ],
latestDeployment: ['flex', 'items-center', 'gap-2'], latestDeployment: ['flex', 'items-center', 'gap-2'],
deploymentStatusContainer: [ deploymentStatusContainer: [
@ -46,6 +49,12 @@ export const projectCardTheme = tv({
'items-center', 'items-center',
'gap-2', 'gap-2',
], ],
wavyBorder: [
'bg-surface-card',
'transition-colors',
'duration-150',
'group-hover:bg-surface-card-hovered',
],
}, },
variants: { variants: {
status: { status: {

View File

@ -1,4 +1,8 @@
import React, { ComponentPropsWithoutRef, MouseEvent } from 'react'; import React, {
ComponentPropsWithoutRef,
MouseEvent,
useCallback,
} from 'react';
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme'; import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
import { Project } from 'gql-client'; import { Project } from 'gql-client';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
@ -11,7 +15,7 @@ import {
WarningDiamondIcon, WarningDiamondIcon,
} from 'components/shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { relativeTimeMs } from 'utils/time'; import { relativeTimeMs } from 'utils/time';
import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Avatar } from 'components/shared/Avatar'; import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials'; import { getInitials } from 'utils/geInitials';
import { import {
@ -27,6 +31,8 @@ export interface ProjectCardProps
project: Project; project: Project;
} }
// TODO: Update the whole component to use `Link` from `react-router-dom` and remove the `useNavigate` hook,
// currently it's not possible to use `Link` because the dot menu is not a direct child of the `Link` component
export const ProjectCard = ({ export const ProjectCard = ({
className, className,
project, project,
@ -38,14 +44,24 @@ export const ProjectCard = ({
// TODO: Update this to use the actual status from the API // TODO: Update this to use the actual status from the API
const hasError = status === 'failure'; const hasError = status === 'failure';
const navigate = useNavigate();
const handleOptionsClick = ( const handleOptionsClick = (
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>, e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
) => { ) => {
e.stopPropagation(); e.stopPropagation();
}; };
const handleClick = useCallback(() => {
navigate(`projects/${project.id}`);
}, [project.id, navigate]);
return ( return (
<div {...props} className={theme.wrapper({ className })}> <div
{...props}
className={theme.wrapper({ className })}
onClick={handleClick}
>
{/* Upper content */} {/* Upper content */}
<div className={theme.upperContent()}> <div className={theme.upperContent()}>
{/* Icon container */} {/* Icon container */}
@ -54,14 +70,13 @@ export const ProjectCard = ({
imageSrc={project.icon} imageSrc={project.icon}
initials={getInitials(project.name)} initials={getInitials(project.name)}
/> />
{/* </div> */}
{/* Title and website */} {/* Title and website */}
<Link to={`projects/${project.id}`} className={theme.content()}> <div className={theme.content()}>
<p className={theme.title()}>{project.name}</p> <p className={theme.title()}>{project.name}</p>
<p className={theme.description()}> <p className={theme.description()}>
{project.deployments[0]?.domain?.name ?? 'No domain'} {project.deployments[0]?.domain?.name ?? 'No domain'}
</p> </p>
</Link> </div>
{/* Icons */} {/* Icons */}
<div className={theme.icons()}> <div className={theme.icons()}>
{hasError && <WarningDiamondIcon className="text-elements-danger" />} {hasError && <WarningDiamondIcon className="text-elements-danger" />}
@ -87,10 +102,7 @@ export const ProjectCard = ({
</div> </div>
</div> </div>
{/* Wave */} {/* Wave */}
<WavyBorder <WavyBorder variant="stroke-and-fill" className={theme.wavyBorder()} />
variant="stroke-and-fill"
className="bg-surface-card-hovered"
/>
{/* Lower content */} {/* Lower content */}
<div className={theme.lowerContent()}> <div className={theme.lowerContent()}>
{/* Latest deployment */} {/* Latest deployment */}

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import templates from 'assets/templates'; import templates from 'assets/templates';
import { RepositoryList } from 'components/projects/create/RepositoryList'; import RepositoryList from 'components/projects/create/RepositoryList';
import ConnectAccount from 'components/projects/create/ConnectAccount'; import ConnectAccount from 'components/projects/create/ConnectAccount';
import { useOctokit } from 'context/OctokitContext'; import { useOctokit } from 'context/OctokitContext';
import { Heading } from 'components/shared/Heading'; import { Heading } from 'components/shared/Heading';