diff --git a/app/demo/container/page.tsx b/app/demo/container/page.tsx new file mode 100644 index 0000000..1f3d9dc --- /dev/null +++ b/app/demo/container/page.tsx @@ -0,0 +1,230 @@ +"use client"; + +import { Container } from "@/components/ui/container"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +export default function ContainerDemo() { + return ( +
+ {/* Hero Section */} +
+ +

+ Container Component Demo +

+

+ Explore the different container variants and their responsive + behavior. +

+
+
+ + {/* Default Container */} +
+ + + + Default Container (max-width: 1280px) + + The standard container width for most content. Provides optimal + reading experience on large screens while maintaining + consistency. + + + +
+

+ Resize your browser to see responsive padding changes: +
• Mobile: 16px padding +
• Tablet (768px+): 32px padding +
• Desktop (1024px+): 48px padding +

+
+
+
+
+
+ + {/* Narrow Container */} +
+ + + + Narrow Container (max-width: 768px) + + Perfect for text-heavy content like documentation or blog posts. + + + +
+

+ This container is narrower, making it ideal for long-form text + content where line length should be limited for better + readability. +

+
+
+
+
+
+ + {/* Wide Container */} +
+ + + + Wide Container (max-width: 1536px) + + For content that needs more horizontal space, like dashboards or + data tables. + + + +
+

+ This wider container provides more space for complex layouts + and side-by-side content arrangements. +

+
+
+
+
+
+ + {/* Full Width Container */} +
+ + + + Full Width Container (max-width: 100%) + + Takes up the entire viewport width with consistent padding. + + + +
+

+ This container spans the full width of the viewport while + maintaining the responsive padding scale. Useful for hero + sections or full-bleed designs. +

+
+
+
+
+
+ + {/* Prose Container */} +
+ +

Container with Prose Typography

+

+ When the prose prop is enabled, the container applies + optimized typography styles for text content. This includes proper + line heights, spacing between elements, and font sizes that enhance + readability. +

+

Why Use Prose?

+

+ The prose variant is perfect for documentation, blog posts, or any + content where typography and readability are paramount. It + automatically styles: +

+
    +
  • Headings with appropriate sizes and spacing
  • +
  • Paragraphs with optimal line height
  • +
  • Lists with proper indentation
  • +
  • Code blocks and inline code
  • +
  • Links with consistent styling
  • +
+
+

+ “Good typography is invisible. Bad typography is + everywhere.” +
+ — Oliver Reichenstein +

+
+

Implementation

+

+ Simply add the prose prop to any container variant: +

+
+            {`
+  

Your Article Title

+

Your content here...

+
`}
+
+
+
+ + {/* Custom Styling Example */} +
+ + + + Custom Styling + + The Container component accepts custom classes via the className + prop. + + + +

+ This container has additional vertical padding applied through + custom classes, demonstrating how you can extend the base styles + while maintaining the responsive behavior. +

+
+
+
+
+ + {/* Nested Containers */} +
+ +

Nested Containers

+ +

+ Containers can be nested when you need different max-widths within + a section. This inner container has a narrow variant while the + outer uses the default width. +

+
+
+
+ + {/* Semantic HTML Example */} + +

Semantic HTML Support

+

+ The Container component supports the as prop, allowing + you to render it as any HTML element. This example renders as an{" "} + <article> + element, improving semantic structure and accessibility. +

+

Common use cases include:

+ +
+
+ ); +} diff --git a/components/ui/container.test.tsx b/components/ui/container.test.tsx new file mode 100644 index 0000000..237ae6e --- /dev/null +++ b/components/ui/container.test.tsx @@ -0,0 +1,128 @@ +import { render } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { Container } from "./container"; + +describe("Container", () => { + it("renders with default variant", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("max-w-screen-xl"); + expect(element).toHaveClass("mx-auto"); + expect(element).toHaveClass("px-4"); + expect(element).toHaveClass("md:px-8"); + expect(element).toHaveClass("lg:px-12"); + }); + + it("renders with narrow variant", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("max-w-3xl"); + }); + + it("renders with wide variant", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("max-w-screen-2xl"); + }); + + it("renders with full variant", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("max-w-full"); + }); + + it("applies prose classes when prose prop is true", () => { + const { container } = render( + +

Title

+

Content

+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("prose"); + expect(element).toHaveClass("prose-slate"); + expect(element).toHaveClass("dark:prose-invert"); + }); + + it("accepts custom className", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("custom-class"); + }); + + it("renders with custom element type", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild; + expect(element?.nodeName).toBe("SECTION"); + }); + + it("passes through additional props", () => { + const { container } = render( + +
Test content
+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveAttribute("data-testid", "test-container"); + expect(element).toHaveAttribute("id", "my-container"); + }); + + it("renders children correctly", () => { + const { getByText } = render( + +

Test Title

+

Test paragraph

+
+ ); + + expect(getByText("Test Title")).toBeInTheDocument(); + expect(getByText("Test paragraph")).toBeInTheDocument(); + }); + + it("combines variant and prose classes correctly", () => { + const { container } = render( + +

Test content

+
+ ); + + const element = container.firstChild as HTMLElement; + expect(element).toHaveClass("max-w-3xl"); + expect(element).toHaveClass("prose"); + expect(element).toHaveClass("additional-class"); + expect(element).toHaveClass("mx-auto"); + expect(element).toHaveClass("px-4"); + }); +}); diff --git a/components/ui/container.tsx b/components/ui/container.tsx new file mode 100644 index 0000000..c84fb3b --- /dev/null +++ b/components/ui/container.tsx @@ -0,0 +1,70 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const containerVariants = cva("mx-auto px-4 md:px-8 lg:px-12", { + variants: { + variant: { + default: "max-w-screen-xl", // 1280px + narrow: "max-w-3xl", // 768px + wide: "max-w-screen-2xl", // 1536px + full: "max-w-full", // 100% + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +export interface ContainerProps + extends React.HTMLAttributes, + VariantProps { + /** + * Whether to apply prose typography styles for text content + */ + prose?: boolean; + /** + * The HTML element to render as the container + * @default "div" + */ + as?: React.ElementType; +} + +/** + * Container component for consistent page layout with responsive padding + * and max-width constraints. + * + * @example + * ```tsx + * // Default container + * + * + * + * + * // Narrow container with prose styling + * + *

Title

+ *

Text content with optimized typography...

+ *
+ * ``` + */ +export function Container({ + className, + variant, + prose = false, + as: Component = "div", + ...props +}: ContainerProps) { + return ( + + ); +}