From 2d945f6015ca709d74c551f651eea5ff8b459f33 Mon Sep 17 00:00:00 2001 From: "Wyatt J. Miller" Date: Tue, 13 Jan 2026 00:15:08 -0500 Subject: [PATCH] added ImageCarousel component, added icons to action buttons, modified project route --- frontend/fresh.gen.ts | 2 + frontend/islands/ImageCarousel.tsx | 82 ++++++++++++++++++++++++++++++ frontend/islands/ProjectCard.tsx | 8 ++- frontend/islands/ProjectModal.tsx | 8 ++- frontend/routes/projects/index.tsx | 12 ++--- 5 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 frontend/islands/ImageCarousel.tsx diff --git a/frontend/fresh.gen.ts b/frontend/fresh.gen.ts index cfeec90..1506b7f 100644 --- a/frontend/fresh.gen.ts +++ b/frontend/fresh.gen.ts @@ -15,6 +15,7 @@ import * as $projects_index from "./routes/projects/index.tsx"; import * as $rss_index from "./routes/rss/index.tsx"; import * as $sitemap_index from "./routes/sitemap/index.tsx"; import * as $Counter from "./islands/Counter.tsx"; +import * as $ImageCarousel from "./islands/ImageCarousel.tsx"; import * as $Portal from "./islands/Portal.tsx"; import * as $ProjectCard from "./islands/ProjectCard.tsx"; import * as $ProjectModal from "./islands/ProjectModal.tsx"; @@ -37,6 +38,7 @@ const manifest = { }, islands: { "./islands/Counter.tsx": $Counter, + "./islands/ImageCarousel.tsx": $ImageCarousel, "./islands/Portal.tsx": $Portal, "./islands/ProjectCard.tsx": $ProjectCard, "./islands/ProjectModal.tsx": $ProjectModal, diff --git a/frontend/islands/ImageCarousel.tsx b/frontend/islands/ImageCarousel.tsx new file mode 100644 index 0000000..2304854 --- /dev/null +++ b/frontend/islands/ImageCarousel.tsx @@ -0,0 +1,82 @@ +import { useState } from "preact/hooks"; + +export const ImageCarousel = function ImageCarousel(props: ImageCarouselProps) { + const [currentImageIndex, setCurrentImageIndex] = useState(0); + + const nextImage = (e: Event) => { + e.stopPropagation(); + if (props.images && props.images?.length > 0) { + const localImage = props.images; + setCurrentImageIndex((prev) => (prev + 1) % localImage.length || 0); + } + }; + + const prevImage = (e: Event) => { + e.stopPropagation(); + if (props.images && props.images.length > 0) { + const localImage = props.images; + setCurrentImageIndex( + (prev) => (prev - 1 + localImage.length) % localImage.length, + ); + } + }; + + return ( +
+
+ {props.images.map((image, index) => ( + {`screenshot + ))} +
+ {props.images.length > 1 && ( + <> + + +
+ {props.images.map((_, index) => ( +
+ + )} +
+ ); +}; + +type ImageCarouselProps = { + images: Array; +}; diff --git a/frontend/islands/ProjectCard.tsx b/frontend/islands/ProjectCard.tsx index c6bdbba..2144bcc 100644 --- a/frontend/islands/ProjectCard.tsx +++ b/frontend/islands/ProjectCard.tsx @@ -1,6 +1,8 @@ import { useState } from "preact/hooks"; import { Portal } from "./Portal.tsx"; import { type ModalAction, ProjectModal } from "./ProjectModal.tsx"; +import { ImageCarousel } from "./ImageCarousel.tsx"; +import * as hi from "@preact-icons/hi2"; export const ProjectCard = function ProjectCard(props: ProjectProps) { const [open, setOpen] = useState(false); @@ -8,6 +10,7 @@ export const ProjectCard = function ProjectCard(props: ProjectProps) { const modalButtons: Array = [ { label: "Open repository", + icon: , onClick: () => { if (props.repo) globalThis.open(props.repo, "_blank"); }, @@ -31,7 +34,6 @@ export const ProjectCard = function ProjectCard(props: ProjectProps) { } onClick={() => { // clicking the card (not the link) opens the modal - console.log("opened portal"); setOpen(true); }} > @@ -69,6 +71,9 @@ export const ProjectCard = function ProjectCard(props: ProjectProps) { actions={modalButtons} >
+ {props.images && props.images.length > 0 && ( + + )}

{props.description}

@@ -90,4 +95,5 @@ type ProjectProps = { description?: string; tech: string; wip?: boolean; + images?: string[]; }; diff --git a/frontend/islands/ProjectModal.tsx b/frontend/islands/ProjectModal.tsx index c64788c..62c31aa 100644 --- a/frontend/islands/ProjectModal.tsx +++ b/frontend/islands/ProjectModal.tsx @@ -1,8 +1,11 @@ import { useEffect, useRef, useState } from "preact/hooks"; import type { ComponentChildren } from "preact"; +import * as hi from "jsr:@preact-icons/hi2"; export type ModalAction = { label: string; + // deno-lint-ignore no-explicit-any + icon?: any; onClick: () => void; variant?: "primary" | "secondary" | "link"; }; @@ -62,6 +65,7 @@ export const ProjectModal = function ProjectModal({ ...actions, { label: "Close", + icon: , onClick: startCloseFlow, variant: "secondary", }, @@ -89,7 +93,7 @@ export const ProjectModal = function ProjectModal({ const renderActionButton = (action: ModalAction) => { const base = - "flex-1 w-full px-4 py-2 rounded-md font-semibold focus:outline-none"; + "flex-1 w-full px-4 py-2 rounded-md font-semibold focus:outline-none flex items-center justify-center gap-2"; const styles = action.variant === "primary" ? "bg-[#94e2d5] text-black hover:brightness-95 border-b-4 border-b-[#364153] shadow-xl" @@ -104,7 +108,7 @@ export const ProjectModal = function ProjectModal({ class={`${base} ${styles}`} type="button" > - {action.label} + {action.icon} {action.label} ); }; diff --git a/frontend/routes/projects/index.tsx b/frontend/routes/projects/index.tsx index 1f2f12f..50eb249 100644 --- a/frontend/routes/projects/index.tsx +++ b/frontend/routes/projects/index.tsx @@ -10,23 +10,22 @@ interface ProjectData { tech: string; wip?: boolean; created_at: string; + images: Array; } -export const handler: Handlers = { +export const handler: Handlers> = { async GET(_req: Request, ctx: FreshContext) { const projectResult = await fetch( `${Deno.env.get("BASE_URI_API")}/projects`, ); const projectData = await projectResult.json(); - return ctx.render({ - projectData, - }); + return ctx.render(projectData); }, }; -export default function Projects({ data }: PageProps) { - const { projectData: projects } = data; +export default function Projects(props: PageProps>) { + const projects = props.data; return (
@@ -56,6 +55,7 @@ export default function Projects({ data }: PageProps) { } tech={project.tech} wip={project.wip ?? true} + images={project.images} /> ); })}