[#1] Less cluttered projects page
This commit is contained in:
@ -53,7 +53,7 @@ const ContactCard = ({ portraitUrl, socials }: ContactCardProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex min-h-[calc(100vh-6rem)] items-center justify-center">
|
<div className="flex min-h-[calc(100dvh-6rem)] items-center justify-center">
|
||||||
<div className="card bg-base-100 shadow-xl md:card-side">
|
<div className="card bg-base-100 shadow-xl md:card-side">
|
||||||
<figure>
|
<figure>
|
||||||
<img
|
<img
|
||||||
|
@ -73,7 +73,7 @@ const Project = ({ project }: Props) => {
|
|||||||
<h2 className="sm:hidden font-bold text-3xl text-center">Images</h2>
|
<h2 className="sm:hidden font-bold text-3xl text-center">Images</h2>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-4 pt-8 justify-center h-fit">
|
<div className="flex flex-wrap gap-4 pt-8 justify-center h-fit sm:p-8">
|
||||||
{project.images.length > 0 &&
|
{project.images.length > 0 &&
|
||||||
project.images.map((image, i) => (
|
project.images.map((image, i) => (
|
||||||
<a
|
<a
|
||||||
@ -81,7 +81,7 @@ const Project = ({ project }: Props) => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-xl"
|
className="rounded-box"
|
||||||
>
|
>
|
||||||
<img src={image} alt="" className="rounded-xl" />
|
<img src={image} alt="" className="rounded-xl" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
import { useLayoutEffect, useRef, useState } from 'react'
|
|
||||||
|
|
||||||
import { format, isAfter, startOfToday } from 'date-fns'
|
|
||||||
import parseHtml from 'react-html-parser'
|
|
||||||
import { FindProjects } from 'types/graphql'
|
|
||||||
|
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
import { calculateLuminance } from 'src/lib/color'
|
|
||||||
|
|
||||||
const CARD_WIDTH = 384
|
|
||||||
|
|
||||||
const ProjectsShowcase = ({ projects }: FindProjects) => {
|
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
|
||||||
const [columns, setColumns] = useState<number>(
|
|
||||||
Math.max(
|
|
||||||
Math.floor(ref.current?.getBoundingClientRect().width / CARD_WIDTH),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const handleResize = () =>
|
|
||||||
setColumns(
|
|
||||||
Math.max(
|
|
||||||
Math.floor(ref.current?.getBoundingClientRect().width / CARD_WIDTH),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
handleResize()
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', handleResize)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={ref} className="flex flex-wrap justify-center gap-2">
|
|
||||||
{split(
|
|
||||||
projects
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) =>
|
|
||||||
isAfter(new Date(b.date), new Date(a.date)) ? 1 : -1
|
|
||||||
),
|
|
||||||
columns
|
|
||||||
).map((projectChunk, i) => (
|
|
||||||
<div className="flex flex-col gap-2" key={i}>
|
|
||||||
{projectChunk.map((project, j) => (
|
|
||||||
<Link key={`${i}-${j}`} to={routes.project({ id: project.id })}>
|
|
||||||
<div className="card card-compact bg-base-100 w-96 h-fit shadow-xl transition-all hover:-translate-y-1 hover:shadow-2xl">
|
|
||||||
{project.images.length > 0 && (
|
|
||||||
<img
|
|
||||||
src={project.images[0]}
|
|
||||||
alt={`${i}`}
|
|
||||||
className="object-contain rounded-box size-fit p-2"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="card-body">
|
|
||||||
<div className="card-title overflow-auto">
|
|
||||||
<p className="whitespace-nowrap">{project.title}</p>
|
|
||||||
</div>
|
|
||||||
<div className="line-clamp-5">
|
|
||||||
<article className="prose text-sm">
|
|
||||||
{parseHtml(project.description)}
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div className="card-actions justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{isAfter(new Date(project.date), startOfToday()) && (
|
|
||||||
<div className="badge badge-info">planned</div>
|
|
||||||
)}
|
|
||||||
<div className="badge badge-ghost">
|
|
||||||
{format(project.date, 'yyyy-MM-dd')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{project.tags.map((tag, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="badge whitespace-nowrap"
|
|
||||||
style={{
|
|
||||||
backgroundColor: tag.color,
|
|
||||||
color:
|
|
||||||
calculateLuminance(tag.color) > 0.5
|
|
||||||
? 'black'
|
|
||||||
: 'white',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tag.tag}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProjectsShowcase
|
|
||||||
|
|
||||||
function split<T>(arr: T[], chunks: number): T[][] {
|
|
||||||
const result: T[][] = []
|
|
||||||
const chunkSize = Math.ceil(arr.length / chunks)
|
|
||||||
|
|
||||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
||||||
result.push(arr.slice(i, i + chunkSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
@ -11,8 +11,7 @@ import {
|
|||||||
import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty'
|
import CellEmpty from 'src/components/Cell/CellEmpty/CellEmpty'
|
||||||
import CellFailure from 'src/components/Cell/CellFailure/CellFailure'
|
import CellFailure from 'src/components/Cell/CellFailure/CellFailure'
|
||||||
import CellLoading from 'src/components/Cell/CellLoading/CellLoading'
|
import CellLoading from 'src/components/Cell/CellLoading/CellLoading'
|
||||||
|
import ProjectsShowcaseList from 'src/components/ProjectsShowcaseList/ProjectsShowcaseList'
|
||||||
import ProjectsShowcase from '../ProjectsShowcase/ProjectsShowcase'
|
|
||||||
|
|
||||||
export const QUERY: TypedDocumentNode<FindProjects, FindProjectsVariables> =
|
export const QUERY: TypedDocumentNode<FindProjects, FindProjectsVariables> =
|
||||||
gql`
|
gql`
|
||||||
@ -52,6 +51,6 @@ export const Success = ({
|
|||||||
url: routes.projects(),
|
url: routes.projects(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ProjectsShowcase projects={projects} />
|
<ProjectsShowcaseList projects={projects} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import { format, isAfter, startOfToday } from 'date-fns'
|
||||||
|
import parseHtml from 'react-html-parser'
|
||||||
|
import { FindProjects } from 'types/graphql'
|
||||||
|
|
||||||
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
|
|
||||||
|
import { calculateLuminance } from 'src/lib/color'
|
||||||
|
|
||||||
|
const ProjectsShowcaseList = ({ projects }: FindProjects) => (
|
||||||
|
<div className="flex flex-col gap-4 w-fit mx-auto">
|
||||||
|
{projects
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (isAfter(new Date(b.date), new Date(a.date)) ? 1 : -1))
|
||||||
|
.map((project, i) => (
|
||||||
|
<Link
|
||||||
|
key={i}
|
||||||
|
to={routes.project({ id: project.id })}
|
||||||
|
className="bg-base-100 flex flex-col sm:flex-row p-2 gap-2 shadow-xl rounded-box hover:shadow-2xl transition-all hover:-translate-y-1 sm:max-h-64 sm:max-w-5xl"
|
||||||
|
>
|
||||||
|
{project.images.length > 0 && (
|
||||||
|
<img
|
||||||
|
src={project.images[0]}
|
||||||
|
alt={`${i}`}
|
||||||
|
className="object-cover rounded-lg sm:max-w-[33.33%]"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col gap-2 p-4">
|
||||||
|
<div className="card-title overflow-auto">
|
||||||
|
<p className="whitespace-nowrap">{project.title}</p>
|
||||||
|
</div>
|
||||||
|
<div className="line-clamp-5 mb-auto">
|
||||||
|
<article className="prose text-sm max-w-none">
|
||||||
|
{parseHtml(project.description)}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div className="card-actions justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{isAfter(new Date(project.date), startOfToday()) && (
|
||||||
|
<div className="badge badge-info">planned</div>
|
||||||
|
)}
|
||||||
|
<div className="badge badge-ghost">
|
||||||
|
{format(project.date, 'yyyy-MM-dd')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{project.tags.slice(0, 3).map((tag, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="badge whitespace-nowrap"
|
||||||
|
style={{
|
||||||
|
backgroundColor: tag.color,
|
||||||
|
color:
|
||||||
|
calculateLuminance(tag.color) > 0.5 ? 'black' : 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag.tag}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{project.tags.length > 3 && (
|
||||||
|
<div key={i} className="badge badge-ghost whitespace-nowrap">
|
||||||
|
+{project.tags.length - 3}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ProjectsShowcaseList
|
@ -27,7 +27,7 @@ const ContactPage = () => {
|
|||||||
}, [width, height])
|
}, [width, height])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-[calc(100vh-6rem)] items-center justify-center">
|
<div className="flex min-h-[calc(100dvh-6rem)] items-center justify-center">
|
||||||
<ContactCardCell />
|
<ContactCardCell />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ const HomePage = () => (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="hero min-h-[calc(100vh-6rem)]">
|
<div className="hero min-h-[calc(100dvh-6rem)]">
|
||||||
<div className="hero-content flex flex-col gap-8">
|
<div className="hero-content flex flex-col gap-8">
|
||||||
<div className="flex flex-col text-center gap-8">
|
<div className="flex flex-col text-center gap-8">
|
||||||
<h1 className="text-3xl sm:text-5xl font-bold">
|
<h1 className="text-3xl sm:text-5xl font-bold">
|
||||||
|
@ -9,7 +9,7 @@ const ProjectsPage = () => {
|
|||||||
<div className="hero-content">
|
<div className="hero-content">
|
||||||
<div className="max-w-md text-center">
|
<div className="max-w-md text-center">
|
||||||
<h1 className="text-5xl font-bold">Projects</h1>
|
<h1 className="text-5xl font-bold">Projects</h1>
|
||||||
<p className="py-6">
|
<p className="mt-8">
|
||||||
{mobile({
|
{mobile({
|
||||||
tablet: true,
|
tablet: true,
|
||||||
})
|
})
|
||||||
@ -20,7 +20,6 @@ const ProjectsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProjectsShowcaseCell />
|
<ProjectsShowcaseCell />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user