From 44a26cb8ea06b13b3cec20f98d357b78968545a9 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:52:39 -0600 Subject: [PATCH 1/8] Quote Block default breakpoint added --- app/components/QuoteBlock.js | 49 ++++++++++++++++++++++++++++++++++ app/components/QuoteDecor.js | 42 +++++++++++++++++++++++++++++ app/components/RuleStack.js | 32 +++++++++++++--------- app/page.js | 2 ++ public/assets/Quote_Avatar.svg | 9 +++++++ 5 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 app/components/QuoteBlock.js create mode 100644 app/components/QuoteDecor.js create mode 100644 public/assets/Quote_Avatar.svg diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js new file mode 100644 index 0000000..50149af --- /dev/null +++ b/app/components/QuoteBlock.js @@ -0,0 +1,49 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import QuoteDecor from "./QuoteDecor"; + +const QuoteBlock = ({ className = "" }) => { + return ( +
+ {/* DECORATIONS (behind content) */} + + +
+
+ Quote Avatar +
+

+ "The rules of decision-making must be open and + available to everyone, and this can happen only if they are + formalized." +

+
+
+
+

+ Jo Freeman +

+

+ "The Tyranny of Structurelessness" +

+
+
+
+ ); +}; + +export default QuoteBlock; diff --git a/app/components/QuoteDecor.js b/app/components/QuoteDecor.js new file mode 100644 index 0000000..6d7b9e9 --- /dev/null +++ b/app/components/QuoteDecor.js @@ -0,0 +1,42 @@ +"use client"; + +const QuoteDecor = ({ className = "" }) => { + return ( + + ); +}; + +export default QuoteDecor; diff --git a/app/components/RuleStack.js b/app/components/RuleStack.js index 0e43a6b..f5b9733 100644 --- a/app/components/RuleStack.js +++ b/app/components/RuleStack.js @@ -1,26 +1,32 @@ "use client"; -import SectionHeader from "./SectionHeader"; +import React from "react"; +import Image from "next/image"; import RuleCard from "./RuleCard"; import Button from "./Button"; -import Image from "next/image"; -const RuleStack = ({ children, className = "" }) => { +const RuleStack = ({ className = "" }) => { const handleTemplateClick = (templateName) => { - console.log(`Template selected: ${templateName}`); - // This would typically navigate to template details or open a modal - // For now, we'll just log the selection + // Basic analytics tracking + if (typeof window !== "undefined") { + if (window.gtag) { + window.gtag("event", "template_click", { + template_name: templateName, + }); + } + if (window.analytics) { + window.analytics.track("Template Clicked", { + templateName: templateName, + }); + } + } + console.log(`${templateName} template clicked`); }; return ( -
-
{ See all templates
-
+ ); }; diff --git a/app/page.js b/app/page.js index e6a48ad..ac8d1fd 100644 --- a/app/page.js +++ b/app/page.js @@ -2,6 +2,7 @@ import NumberedCards from "./components/NumberedCards"; import HeroBanner from "./components/HeroBanner"; import LogoWall from "./components/LogoWall"; import RuleStack from "./components/RuleStack"; +import QuoteBlock from "./components/QuoteBlock"; export default function Page() { const heroBannerData = { @@ -41,6 +42,7 @@ export default function Page() { + ); } diff --git a/public/assets/Quote_Avatar.svg b/public/assets/Quote_Avatar.svg new file mode 100644 index 0000000..50c8704 --- /dev/null +++ b/public/assets/Quote_Avatar.svg @@ -0,0 +1,9 @@ + + + + + + + + + -- 2.43.0 From ccee34f5c866bc3634b67cf6b4aecde56e261e28 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:45:13 -0600 Subject: [PATCH 2/8] Quote Block md breakpoint --- app/components/QuoteBlock.js | 64 ++++++++++++++------------- app/components/QuoteDecor.js | 85 ++++++++++++++++++++++++------------ 2 files changed, 92 insertions(+), 57 deletions(-) diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js index 50149af..04c57dd 100644 --- a/app/components/QuoteBlock.js +++ b/app/components/QuoteBlock.js @@ -7,39 +7,43 @@ import QuoteDecor from "./QuoteDecor"; const QuoteBlock = ({ className = "" }) => { return (
- {/* DECORATIONS (behind content) */} - +
+ {/* DECORATIONS (behind content) */} + -
-
- Quote Avatar -
-

- "The rules of decision-making must be open and - available to everyone, and this can happen only if they are - formalized." +

+
+ Quote Avatar +
+

+ "The rules of decision-making must be open and + available to everyone, and this can happen only if they are + formalized." +

+
+
+
+

+ Jo Freeman

-
-
-
-

- Jo Freeman -

-

- "The Tyranny of Structurelessness" -

+

+ "The Tyranny of Structurelessness" +

+
diff --git a/app/components/QuoteDecor.js b/app/components/QuoteDecor.js index 6d7b9e9..e1b8ccb 100644 --- a/app/components/QuoteDecor.js +++ b/app/components/QuoteDecor.js @@ -3,37 +3,68 @@ const QuoteDecor = ({ className = "" }) => { return ( ); -- 2.43.0 From f1dbfbfdacd5d9880a99e755087f7c18beeca479 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Mon, 25 Aug 2025 19:17:41 -0600 Subject: [PATCH 3/8] Quote Block lg breakpoint --- app/components/QuoteBlock.js | 16 ++++++++-------- app/components/QuoteDecor.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js index 04c57dd..5ec7549 100644 --- a/app/components/QuoteBlock.js +++ b/app/components/QuoteBlock.js @@ -7,10 +7,10 @@ import QuoteDecor from "./QuoteDecor"; const QuoteBlock = ({ className = "" }) => { return (
{/* DECORATIONS (behind content) */} { w-full h-full" /> -
-
+
+
Quote Avatar
-

+

"The rules of decision-making must be open and available to everyone, and this can happen only if they are formalized." @@ -37,10 +37,10 @@ const QuoteBlock = ({ className = "" }) => {

-

+

Jo Freeman

-

+

"The Tyranny of Structurelessness"

diff --git a/app/components/QuoteDecor.js b/app/components/QuoteDecor.js index e1b8ccb..04cec01 100644 --- a/app/components/QuoteDecor.js +++ b/app/components/QuoteDecor.js @@ -3,7 +3,7 @@ const QuoteDecor = ({ className = "" }) => { return (
-
+
+
Quote Avatar
-

- "The rules of decision-making must be open and - available to everyone, and this can happen only if they are - formalized." +

+ The rules of decision-making must be open and available to + everyone, and this can happen only if they are formalized.

-
-

+

+

Jo Freeman

-

- "The Tyranny of Structurelessness" +

+ The Tyranny of Structurelessness

diff --git a/app/components/QuoteDecor.js b/app/components/QuoteDecor.js index 04cec01..cd2a5f4 100644 --- a/app/components/QuoteDecor.js +++ b/app/components/QuoteDecor.js @@ -3,7 +3,7 @@ const QuoteDecor = ({ className = "" }) => { return (
+
{/* DECORATIONS (behind content) */} - + {config.showDecor && ( + + )} -
-
+
+
Quote Avatar
-

- The rules of decision-making must be open and available to - everyone, and this can happen only if they are formalized. +

+ {quote}

-

- Jo Freeman +

+ {author}

-

- The Tyranny of Structurelessness +

+ {source}

-- 2.43.0 From 28b788c584470598bf342cab406b4b70a56ad936 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:14:05 -0600 Subject: [PATCH 6/8] Quote Block accessibility and error handling --- app/components/QuoteBlock.js | 141 +++++++++++++++++++++++++++++------ 1 file changed, 120 insertions(+), 21 deletions(-) diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js index ac373fe..8341a6e 100644 --- a/app/components/QuoteBlock.js +++ b/app/components/QuoteBlock.js @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import Image from "next/image"; import QuoteDecor from "./QuoteDecor"; @@ -11,7 +11,13 @@ const QuoteBlock = ({ author = "Jo Freeman", source = "The Tyranny of Structurelessness", avatarSrc = "assets/Quote_Avatar.svg", + id, + fallbackAvatarSrc = "assets/Quote_Avatar.svg", // Fallback avatar + onError, // Error callback }) => { + const [imageError, setImageError] = useState(false); + const [imageLoading, setImageLoading] = useState(true); + // Variant configurations const variants = { compact: { @@ -63,8 +69,60 @@ const QuoteBlock = ({ const config = variants[variant] || variants.standard; + // Use provided ID or generate a stable one based on content + const baseId = id || `quote-${author.toLowerCase().replace(/\s+/g, "-")}`; + const quoteId = `${baseId}-content`; + const authorId = `${baseId}-author`; + + // Error handling functions + const handleImageError = (error) => { + console.warn( + `QuoteBlock: Failed to load avatar image for ${author}:`, + error + ); + setImageError(true); + setImageLoading(false); + + // Call error callback if provided + if (onError) { + onError({ + type: "image_load_error", + message: `Failed to load avatar for ${author}`, + author, + avatarSrc, + error, + }); + } + }; + + const handleImageLoad = () => { + setImageLoading(false); + setImageError(false); + }; + + // Validate required props + if (!quote || !author) { + console.error("QuoteBlock: Missing required props (quote or author)"); + if (onError) { + onError({ + type: "missing_props", + message: "QuoteBlock requires quote and author props", + quote: !!quote, + author: !!author, + }); + } + return null; // Don't render if missing required props + } + + // Determine which avatar to use + const currentAvatarSrc = imageError ? fallbackAvatarSrc : avatarSrc; + return ( -
+
@@ -74,19 +132,57 @@ const QuoteBlock = ({ className="pointer-events-none absolute z-0 left-0 top-0 w-full h-full" + aria-hidden="true" /> )}
- {`${author} -
+ {/* Avatar with error handling */} +
+ {`Portrait + + {/* Loading state */} + {imageLoading && ( +
+ )} + + {/* Error state - show initials */} + {imageError && !imageLoading && ( +
+ + {author + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase()} + +
+ )} +
+ +

@@ -94,21 +190,24 @@ const QuoteBlock = ({

-
-

+ {author} -

-

- {source} -

-
+ + {source && ( +

+ {source} +

+ )} +
-
+
); }; -- 2.43.0 From ac05f33705e8f4502e6c3402e328588b3aaacc9e Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:40:51 -0600 Subject: [PATCH 7/8] Quote Block storybook implemented --- .storybook/preview.js | 19 ++++- .storybook/preview.local.js | 19 ++++- app/components/QuoteBlock.js | 60 +++++++++----- stories/QuoteBlock.stories.js | 146 ++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 stories/QuoteBlock.stories.js diff --git a/.storybook/preview.js b/.storybook/preview.js index 59394fb..8b6689a 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,18 +1,27 @@ import "../app/globals.css"; // Import Google Fonts for Storybook -import { Inter, Bricolage_Grotesque } from "next/font/google"; +import { Inter, Bricolage_Grotesque, Space_Grotesk } from "next/font/google"; const inter = Inter({ subsets: ["latin"], - weight: ["400", "500"], + weight: ["400", "500", "600", "700"], variable: "--font-inter", + display: "swap", }); const bricolageGrotesque = Bricolage_Grotesque({ subsets: ["latin"], - weight: ["400", "500"], + weight: ["400", "500", "700", "800"], variable: "--font-bricolage-grotesque", + display: "swap", +}); + +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + weight: ["400", "500", "700"], + variable: "--font-space-grotesk", + display: "swap", }); /** @type { import('@storybook/react').Preview } */ @@ -28,7 +37,9 @@ const preview = { }, decorators: [ (Story) => ( -
+
), diff --git a/.storybook/preview.local.js b/.storybook/preview.local.js index 59394fb..8b6689a 100644 --- a/.storybook/preview.local.js +++ b/.storybook/preview.local.js @@ -1,18 +1,27 @@ import "../app/globals.css"; // Import Google Fonts for Storybook -import { Inter, Bricolage_Grotesque } from "next/font/google"; +import { Inter, Bricolage_Grotesque, Space_Grotesk } from "next/font/google"; const inter = Inter({ subsets: ["latin"], - weight: ["400", "500"], + weight: ["400", "500", "600", "700"], variable: "--font-inter", + display: "swap", }); const bricolageGrotesque = Bricolage_Grotesque({ subsets: ["latin"], - weight: ["400", "500"], + weight: ["400", "500", "700", "800"], variable: "--font-bricolage-grotesque", + display: "swap", +}); + +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + weight: ["400", "500", "700"], + variable: "--font-space-grotesk", + display: "swap", }); /** @type { import('@storybook/react').Preview } */ @@ -28,7 +37,9 @@ const preview = { }, decorators: [ (Story) => ( -
+
), diff --git a/app/components/QuoteBlock.js b/app/components/QuoteBlock.js index 8341a6e..3598a2f 100644 --- a/app/components/QuoteBlock.js +++ b/app/components/QuoteBlock.js @@ -140,30 +140,32 @@ const QuoteBlock = ({
{/* Avatar with error handling */}
- {`Portrait + {!imageError ? ( + {`Portrait + ) : null} {/* Loading state */} - {imageLoading && ( + {imageLoading && !imageError && (
)} {/* Error state - show initials */} - {imageError && !imageLoading && ( + {imageError && (
@@ -184,7 +186,20 @@ const QuoteBlock = ({ className="relative" >

{quote}

@@ -199,7 +214,16 @@ const QuoteBlock = ({ {source && (

{source}

diff --git a/stories/QuoteBlock.stories.js b/stories/QuoteBlock.stories.js new file mode 100644 index 0000000..3b4f0f6 --- /dev/null +++ b/stories/QuoteBlock.stories.js @@ -0,0 +1,146 @@ +import QuoteBlock from "../app/components/QuoteBlock"; + +export default { + title: "Components/QuoteBlock", + component: QuoteBlock, + parameters: { + layout: "fullscreen", + docs: { + description: { + component: ` +A responsive quote section component that displays inspirational governance quotes with author attribution and decorative geometric elements. + +## Features +- **Three variants**: compact, standard, and extended layouts +- **Responsive design**: Adapts across all breakpoints +- **Error handling**: Graceful fallbacks for image loading failures +- **Accessibility**: WCAG 2.1 AA compliant with proper ARIA labels +- **Design system integration**: Uses design tokens for consistent styling + +## Usage +\`\`\`jsx + +\`\`\` + `, + }, + }, + }, + argTypes: { + variant: { + control: { type: "select" }, + options: ["compact", "standard", "extended"], + description: "Layout variant for different use cases", + }, + quote: { + control: { type: "text" }, + description: "The quote text to display", + }, + author: { + control: { type: "text" }, + description: "Author name for attribution", + }, + source: { + control: { type: "text" }, + description: "Source title (book, article, etc.)", + }, + avatarSrc: { + control: { type: "text" }, + description: "Path to author avatar image", + }, + fallbackAvatarSrc: { + control: { type: "text" }, + description: "Fallback avatar image path", + }, + onError: { + action: "error", + description: "Error callback function", + }, + }, +}; + +// Default story +export const Default = { + args: { + variant: "standard", + quote: + "The rules of decision-making must be open and available to everyone, and this can happen only if they are formalized.", + author: "Jo Freeman", + source: "The Tyranny of Structurelessness", + avatarSrc: "assets/Quote_Avatar.svg", + }, +}; + +// All variants comparison +export const AllVariants = { + render: () => ( +
+
+

Compact Variant

+ +
+ +
+

Standard Variant

+ +
+ +
+

Extended Variant

+ +
+
+ ), + parameters: { + docs: { + description: { + story: + "Side-by-side comparison of all three variants to show the differences in layout, typography, and spacing.", + }, + }, + }, +}; + +// Error state simulation +export const ErrorState = { + args: { + variant: "standard", + quote: + "The rules of decision-making must be open and available to everyone, and this can happen only if they are formalized.", + author: "Jo Freeman", + source: "The Tyranny of Structurelessness", + avatarSrc: "invalid-image-path.jpg", // This will trigger error state + onError: (error) => console.log("QuoteBlock error:", error), + }, + parameters: { + docs: { + description: { + story: + "Error state when avatar image fails to load. Shows initials fallback and error handling.", + }, + }, + }, +}; -- 2.43.0 From 3609501fea3d4146f730b6368febfa0e256ff345 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:45:20 -0600 Subject: [PATCH 8/8] Fix github pages storybook fonts --- .storybook/preview.github.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.storybook/preview.github.js b/.storybook/preview.github.js index 1fd25df..8b6689a 100644 --- a/.storybook/preview.github.js +++ b/.storybook/preview.github.js @@ -1,5 +1,29 @@ import "../app/globals.css"; +// Import Google Fonts for Storybook +import { Inter, Bricolage_Grotesque, Space_Grotesk } from "next/font/google"; + +const inter = Inter({ + subsets: ["latin"], + weight: ["400", "500", "600", "700"], + variable: "--font-inter", + display: "swap", +}); + +const bricolageGrotesque = Bricolage_Grotesque({ + subsets: ["latin"], + weight: ["400", "500", "700", "800"], + variable: "--font-bricolage-grotesque", + display: "swap", +}); + +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + weight: ["400", "500", "700"], + variable: "--font-space-grotesk", + display: "swap", +}); + /** @type { import('@storybook/react').Preview } */ const preview = { parameters: { @@ -11,6 +35,15 @@ const preview = { }, }, }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], }; export default preview; -- 2.43.0