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] 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.", + }, + }, + }, +};