Skip to content

Commit 2f4b2bb

Browse files
committed
Built the contact form.
1 parent ee47d3e commit 2f4b2bb

6 files changed

Lines changed: 224 additions & 11 deletions

File tree

SESSION_HISTORY.md

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,12 +552,147 @@ This file tracks significant changes, design decisions, and implementation patte
552552

553553
---
554554

555+
## Session: 2026-02-06 (Part 2)
556+
557+
### BlogPostGrid Module — Removed
558+
- **Reason:** Replaced by the new BlogList module which uses the Skills-style plain card layout
559+
- **Files Deleted:**
560+
- `src/stories/modules/BlogPostGrid.tsx`
561+
- `src/stories/modules/BlogPostGrid.stories.tsx`
562+
- `src/stories/assets/css/blog-posts.scss`
563+
- **Files Modified:**
564+
- `src/stories/assets/css/main.scss` — Removed `@use 'blog-posts'`
565+
566+
### BlogFeatured Module — New Composite Section
567+
- **Purpose:** Hero-style featured blog post with image/text side-by-side layout, used at the top of the Blog page
568+
- **Component:** `BlogFeatured` in `modules/` — accepts `heading` (`React.ReactNode`) and `post` (`FeaturedPost`)
569+
- **Layout:** Single row — image left (`col-12 col-md-6`), content right (`col-12 col-md-6`), matching the WorkingOn layout pattern
570+
- **FeaturedPost interface:** `title`, `excerpt`, `date`, `imageSrc`, `imageAlt?`, `labels?: CardLabel[]`, `buttonLabel?`, `buttonHref?`
571+
- **Content Per Item:**
572+
- Date — uppercase, letterspaced, `$pewter`, `0.75rem` (same style as WorkingOn's type label)
573+
- Title — `$sky-blue`, Manrope, `1.35rem`, `font-weight: 600`
574+
- Labels — rendered using Label component with auto-mapped tech colours
575+
- Excerpt — `$quicksilver`, Noto Sans, `0.95rem`, `line-height: 1.6`
576+
- Button — optional `<a>` using `btn btn-azure-bolt` classes
577+
- **Heading:** Two-tone `<h2>` — "Latest" (`$deep-azure`) + "Post." (`$frost`)
578+
- **Divider Animation:** Animated expand from 0% to 25% width (`blog-featured-divider-expand` keyframes, 1.2s ease-out, 0.4s delay). Bootstrap `w-25` class removed to avoid `!important` conflict blocking the animation.
579+
- **Background:** `$obsidian`
580+
- **Stories:** `Default` (with two-tone heading) and `NoHeading`
581+
- **Files Created:**
582+
- `src/stories/modules/BlogFeatured.tsx`
583+
- `src/stories/modules/BlogFeatured.stories.tsx`
584+
- `src/stories/assets/css/_blog-featured.scss`
585+
- **Files Modified:**
586+
- `src/stories/assets/css/main.scss` — Added `@use 'blog-featured'`
587+
588+
### BlogList Module — New Composite Section
589+
- **Purpose:** 2-column grid of blog post listings below the featured post on the Blog page
590+
- **Component:** `BlogList` in `modules/` — accepts `heading` (`React.ReactNode`) and `posts` array
591+
- **Layout:** Skills-style 2-column grid (`row g-0`, `col-12 col-md-6`) using plain Card variant
592+
- **BlogListPost interface:** `icon`, `title`, `description`, `imageSrc?`, `imageAlt?`, `href?`
593+
- **Thumbnail Support:** Each post has an optional 80×80px rounded thumbnail image to the left of the plain card
594+
- **Clickable Posts:** When `href` is provided, the entire post block (thumbnail + card) is wrapped in an `<a>` tag (`.blog-list__link`)
595+
- **Hover Effect:** Background transitions to `$obsidian` on hover (section background is `$charcoal`)
596+
- **Text Colour Preservation:** Inside the link wrapper, explicit colours set on `.card__title` (`$quicksilver`), `.card__text` (`$pewter`), `.card__icon` (`$pewter`) to prevent `<a>` tag colour inheritance
597+
- **Heading:** Two-tone `<h2>` — "More" (`$deep-azure`) + "Posts." (`$frost`)
598+
- **Background:** `$charcoal`
599+
- **Design Iterations:**
600+
- Started using BlogPostGrid (3-column card grid with images) — user rejected
601+
- Switched to Skills-style plain cards (no images) — user wanted images
602+
- Added thumbnail images alongside plain cards — user wanted button links
603+
- Added button links in a separate row — user wanted whole block clickable instead
604+
- Removed buttons, made entire post block an `<a>` tag with obsidian hover background
605+
- **Stories:** `Default` (with two-tone heading) and `NoHeading`
606+
- **Files Created:**
607+
- `src/stories/modules/BlogList.tsx`
608+
- `src/stories/modules/BlogList.stories.tsx`
609+
- `src/stories/assets/css/_blog-list.scss`
610+
- **Files Modified:**
611+
- `src/stories/assets/css/main.scss` — Added `@use 'blog-list'`
612+
613+
### Blog Page — New Page Composition
614+
- **Purpose:** Blog landing page with featured post hero and post listing grid
615+
- **Component:** `Blog` in `pages/` — accepts `activeMenuItem` (default `'Blog'`)
616+
- **Page sections (top to bottom):** Header → BlogFeatured → BlogList → Footer
617+
- **BlogFeatured content:** "Designing with AI in Storybook" (February 2026), labels: AI, React, Sass
618+
- **BlogList content:** 4 posts with thumbnails and links — Drupal Publishing Options, DevOps Pipelines, Component-Driven Design, Migrating to Drupal 11
619+
- **Footer:** Same configuration as Home page (GitHub, LinkedIn, Drupal.org socials)
620+
- **Stories:** `Default` at `jorgecalderon.codes/Pages/Blog`
621+
- **Files Created:**
622+
- `src/stories/pages/Blog.tsx`
623+
- `src/stories/pages/Blog.stories.tsx`
624+
- **Files Modified:**
625+
- `src/stories/assets/css/pages.scss` — Added `.page-blog` class (`$obsidian` background, `min-height: 100vh`)
626+
627+
### PortfolioFeatured Module — New Composite Section
628+
- **Purpose:** Hero-style featured project with image/text side-by-side layout, used at the top of the Portfolio page
629+
- **Component:** `PortfolioFeatured` in `modules/` — accepts `heading` (`React.ReactNode`) and `project` (`FeaturedProject`)
630+
- **Layout:** Mirrors BlogFeatured — single row, image left, content right
631+
- **FeaturedProject interface:** `title`, `description`, `type`, `imageSrc`, `imageAlt?`, `labels?: CardLabel[]`, `buttonLabel?`, `buttonHref?`
632+
- **Content Per Item:**
633+
- Type label — uppercase, letterspaced, `$pewter` (e.g. "Contributed Drupal Module")
634+
- Title — `$sky-blue`, Manrope, `1.35rem`, `font-weight: 600`
635+
- Labels — rendered using Label component
636+
- Description — `$quicksilver`, Noto Sans, `0.95rem`
637+
- Button — optional `<a>` using `btn btn-azure-bolt`
638+
- **Heading:** Two-tone `<h2>` — "Featured" (`$deep-azure`) + "Project." (`$frost`)
639+
- **Divider Animation:** Same expand pattern as BlogFeatured (`portfolio-featured-divider-expand`, 0→25%, 1.2s ease-out, 0.4s delay)
640+
- **Background:** `$obsidian`
641+
- **Stories:** `Default` and `NoHeading`
642+
- **Files Created:**
643+
- `src/stories/modules/PortfolioFeatured.tsx`
644+
- `src/stories/modules/PortfolioFeatured.stories.tsx`
645+
- `src/stories/assets/css/_portfolio-featured.scss`
646+
- **Files Modified:**
647+
- `src/stories/assets/css/main.scss` — Added `@use 'portfolio-featured'`
648+
649+
### PortfolioList Module — New Composite Section
650+
- **Purpose:** Alternating image/text rows showcasing portfolio projects, used below the featured project on the Portfolio page
651+
- **Component:** `PortfolioList` in `modules/` — accepts `heading` (`React.ReactNode`) and `projects` array
652+
- **Layout:** Mirrors WorkingOn module — full-width rows with screenshot on one side and text on the other, alternating sides via `flex-row-reverse` on odd-indexed items
653+
- **PortfolioProject interface:** `title`, `description`, `type`, `imageSrc`, `imageAlt?`, `buttonLabel?`, `buttonHref?`
654+
- **Content:** Same typography and spacing as WorkingOn (type label, sky-blue title, quicksilver description, optional azure-bolt button)
655+
- **Heading:** Two-tone `<h2>` — "More" (`$deep-azure`) + "Projects." (`$frost`)
656+
- **Background:** `$charcoal`
657+
- **Stories:** `Default` and `NoHeading`
658+
- **Sample Data:** Blood Cancer United (full-time role), jorgecalderon.codes (personal project), Enterprise CMS Platform (client project) — using project placeholder SVGs
659+
- **Files Created:**
660+
- `src/stories/modules/PortfolioList.tsx`
661+
- `src/stories/modules/PortfolioList.stories.tsx`
662+
- `src/stories/assets/css/_portfolio-list.scss`
663+
- **Files Modified:**
664+
- `src/stories/assets/css/main.scss` — Added `@use 'portfolio-list'`
665+
666+
### Portfolio Page — New Page Composition
667+
- **Purpose:** Portfolio landing page with featured project hero and project listing
668+
- **Component:** `Portfolio` in `pages/` — accepts `activeMenuItem` (default `'Portfolio'`)
669+
- **Page sections (top to bottom):** Header → PortfolioFeatured → PortfolioList → Footer
670+
- **PortfolioFeatured content:** Publishing Options (Contributed Drupal Module), labels: Drupal, PHP, button: "View Module" → drupal.org
671+
- **PortfolioList content:** 3 projects with alternating layout — BCU, jorgecalderon.codes, Enterprise CMS
672+
- **Footer:** Same configuration as Home and Blog pages
673+
- **Stories:** `Default` at `jorgecalderon.codes/Pages/Portfolio`
674+
- **Files Created:**
675+
- `src/stories/pages/Portfolio.tsx`
676+
- `src/stories/pages/Portfolio.stories.tsx`
677+
- **Files Modified:**
678+
- `src/stories/assets/css/pages.scss` — Added `.page-portfolio` class (`$obsidian` background, `min-height: 100vh`)
679+
680+
### Divider Animation Gotcha — Bootstrap `w-25` Conflict
681+
- **Issue:** Divider expand animation did not play on the BlogFeatured section
682+
- **Root Cause:** Bootstrap's `w-25` utility class sets `width: 25% !important`, overriding the `@keyframes` animation starting at `width: 0`
683+
- **Fix:** Removed `w-25` class from the Divider in BlogFeatured (and PortfolioFeatured), letting the `@keyframes` animation control width from 0% to 25%
684+
- **Pattern:** Any section that needs an animated divider must NOT use Bootstrap width utilities — use `@keyframes` directly
685+
686+
### Build Verification
687+
- **Verified:** `npm run build-storybook` completed successfully after all Blog page, Portfolio page, and module changes
688+
689+
---
690+
555691
## Next Session Considerations
556692
- Replace WorkingOn placeholder SVGs with actual screenshots of drupal.org/project/pub_options and bloodcancerunited.org
557693
- Add Onyx color to Design Specs ColorPalette component and stories
558-
- Portfolio page design/implementation
559-
- Blog page layout
560-
- Contact page with form
694+
- Contact page with form (reuse ContactForm module)
561695
- Responsive behavior verification across breakpoints
562696
- Accessibility audit (ARIA labels, keyboard nav, color contrast)
563697
- Performance: consider reducing animation complexity on mobile
698+
- Update CLAUDE.md project layout to include new modules and pages

src/stories/assets/css/pages.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@
2727
background-color: $obsidian;
2828
min-height: 100vh;
2929
}
30+
31+
// Contact Page
32+
.page-contact {
33+
background-color: $obsidian;
34+
min-height: 100vh;
35+
display: flex;
36+
flex-direction: column;
37+
38+
.footer {
39+
margin-top: auto;
40+
}
41+
}

src/stories/modules/PortfolioList.stories.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
33
import { PortfolioList } from './PortfolioList';
44

55
import bcuImg from '../assets/bcu-screenshot.svg';
6-
import imgStyling from '../assets/styling.png';
7-
import imgTheming from '../assets/theming.png';
6+
import pubOptionsImg from '../assets/pub-options-screenshot.svg';
87

98
const meta = {
109
title: 'jorgecalderon.codes/Modules/Portfolio List',
@@ -38,7 +37,7 @@ const sampleProjects = [
3837
type: 'Personal Project',
3938
description:
4039
'A component-driven personal website built with React, TypeScript, and Storybook. Every component is designed, documented, and tested inside Storybook before integration.',
41-
imageSrc: imgStyling,
40+
imageSrc: pubOptionsImg,
4241
imageAlt: 'jorgecalderon.codes in Storybook',
4342
buttonLabel: 'View Source',
4443
buttonHref: 'https://github.com/jorgecalderon',
@@ -48,7 +47,7 @@ const sampleProjects = [
4847
type: 'Client Project',
4948
description:
5049
'A headless Drupal architecture powering a multi-site content platform with custom modules, automated deployments, and performance-tuned infrastructure on AWS.',
51-
imageSrc: imgTheming,
50+
imageSrc: bcuImg,
5251
imageAlt: 'Enterprise CMS dashboard',
5352
},
5453
];
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import { Contact } from './Contact';
4+
5+
const meta = {
6+
title: 'jorgecalderon.codes/Pages/Contact',
7+
component: Contact,
8+
parameters: {
9+
layout: 'fullscreen',
10+
},
11+
tags: ['autodocs'],
12+
} satisfies Meta<typeof Contact>;
13+
14+
export default meta;
15+
type Story = StoryObj<typeof meta>;
16+
17+
export const Default: Story = {
18+
args: {
19+
activeMenuItem: 'Contact',
20+
},
21+
};

src/stories/pages/Contact.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { Header } from '../layout/Header';
3+
import { ContactForm } from '../modules/ContactForm';
4+
import { Footer } from '../layout/Footer';
5+
6+
export interface ContactProps {
7+
/** Active menu item */
8+
activeMenuItem?: string;
9+
}
10+
11+
export const Contact = ({ activeMenuItem = 'Contact' }: ContactProps) => {
12+
return (
13+
<div className="page-contact">
14+
<Header menuItems={['Home', 'Blog', 'Portfolio', 'Contact']} activeItem={activeMenuItem} />
15+
16+
<ContactForm
17+
heading={
18+
<>
19+
<span className="contact-form__heading-accent">Get in</span>{' '}
20+
<span className="contact-form__heading-light">Touch.</span>
21+
</>
22+
}
23+
introItems={[
24+
{ icon: 'fa-solid fa-lightbulb', text: 'Need help with a project?' },
25+
{ icon: 'fa-solid fa-rocket', text: 'Got ideas?' },
26+
{ icon: 'fa-brands fa-drupal', text: 'Want to chat Drupal?' },
27+
]}
28+
introClosing="Or just want to say hello? Drop me a message and let's get started."
29+
details={[
30+
{ icon: 'fa-solid fa-envelope', text: 'hello@jorgecalderon.codes', href: 'mailto:hello@jorgecalderon.codes' },
31+
{ icon: 'fa-brands fa-github', text: 'github.com/jorgecalderon', href: 'https://github.com/jorgecalderon' },
32+
{ icon: 'fa-brands fa-linkedin', text: 'linkedin.com/in/jorgecalderon', href: 'https://linkedin.com/in/jorgecalderon' },
33+
{ icon: 'fa-solid fa-location-dot', text: 'Florida, USA' },
34+
]}
35+
/>
36+
37+
<Footer
38+
socials={[
39+
{ icon: 'fa-brands fa-github', href: 'https://github.com/jorgecalderon', label: 'GitHub' },
40+
{ icon: 'fa-brands fa-linkedin', href: 'https://linkedin.com/in/jorgecalderon', label: 'LinkedIn' },
41+
{ icon: 'fa-brands fa-drupal', href: 'https://www.drupal.org/u/geocalleo', label: 'Drupal.org' },
42+
]}
43+
email="hello@jorgecalderon.codes"
44+
builtWith="React, Storybook & Drupal"
45+
/>
46+
</div>
47+
);
48+
};

src/stories/pages/Portfolio.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import { Footer } from '../layout/Footer';
66

77
import pubOptionsImg from '../assets/pub-options-screenshot.svg';
88
import bcuImg from '../assets/bcu-screenshot.svg';
9-
import imgStyling from '../assets/styling.png';
10-
import imgTheming from '../assets/theming.png';
119

1210
export interface PortfolioProps {
1311
/** Active menu item */
@@ -62,7 +60,7 @@ export const Portfolio = ({ activeMenuItem = 'Portfolio' }: PortfolioProps) => {
6260
type: 'Personal Project',
6361
description:
6462
'A component-driven personal website built with React, TypeScript, and Storybook. Every component is designed, documented, and tested inside Storybook before integration.',
65-
imageSrc: imgStyling,
63+
imageSrc: pubOptionsImg,
6664
imageAlt: 'jorgecalderon.codes in Storybook',
6765
buttonLabel: 'View Source',
6866
buttonHref: 'https://github.com/jorgecalderon',
@@ -72,7 +70,7 @@ export const Portfolio = ({ activeMenuItem = 'Portfolio' }: PortfolioProps) => {
7270
type: 'Client Project',
7371
description:
7472
'A headless Drupal architecture powering a multi-site content platform with custom modules, automated deployments, and performance-tuned infrastructure on AWS.',
75-
imageSrc: imgTheming,
73+
imageSrc: bcuImg,
7674
imageAlt: 'Enterprise CMS dashboard',
7775
},
7876
]}

0 commit comments

Comments
 (0)