atomic-design-templates

安装量: 55
排名: #13390

安装

npx skills add https://github.com/thebushidocollective/han --skill atomic-design-templates

Atomic Design: Templates

Master the creation of templates - page-level layouts that define content structure without actual content. Templates establish the skeletal structure that pages will use.

What Are Templates?

Templates are the page-level objects that place components into a layout and articulate the design's underlying content structure. They are:

Composed of organisms: Arrange organisms into page layouts Content-agnostic: Use placeholder content, not real data Structural: Define where content types will appear Reusable: Same template can be used by multiple pages Responsive: Handle all viewport sizes Common Template Types Marketing Templates Landing page layouts Homepage layouts Product showcase layouts About/Company layouts Application Templates Dashboard layouts Settings page layouts Profile page layouts List/Detail page layouts Content Templates Blog post layouts Article layouts Documentation layouts Help center layouts E-commerce Templates Product listing layouts Product detail layouts Checkout layouts Order confirmation layouts MainLayout Template Example Complete Implementation // templates/MainLayout/MainLayout.tsx import React from 'react'; import { Header, type HeaderProps } from '@/components/organisms/Header'; import { Footer, type FooterProps } from '@/components/organisms/Footer'; import styles from './MainLayout.module.css';

export interface MainLayoutProps { / Header configuration */ headerProps: HeaderProps; / Footer configuration / footerProps: FooterProps; / Main content / children: React.ReactNode; / Show breadcrumbs */ showBreadcrumbs?: boolean; / Breadcrumb component / breadcrumbs?: React.ReactNode; / Maximum content width / maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; /* Page background color / background?: 'white' | 'gray' | 'primary'; }

export const MainLayout: React.FC = ({ headerProps, footerProps, children, showBreadcrumbs = false, breadcrumbs, maxWidth = 'lg', background = 'white', }) => { return (

${styles.layout} ${styles[bg-${background}]}}>

  <main className={styles.main}>
    {showBreadcrumbs && breadcrumbs && (
      <div className={styles.breadcrumbs}>{breadcrumbs}</div>
    )}

    <div className={`${styles.content} ${styles[`max-${maxWidth}`]}`}>
      {children}
    </div>
  </main>

  <Footer {...footerProps} />
</div>

); };

MainLayout.displayName = 'MainLayout';

/ templates/MainLayout/MainLayout.module.css / .layout { display: flex; flex-direction: column; min-height: 100vh; }

.main { flex: 1; display: flex; flex-direction: column; }

.breadcrumbs { padding: 16px 24px; background-color: var(--color-neutral-50); border-bottom: 1px solid var(--color-neutral-200); }

.content { flex: 1; margin: 0 auto; padding: 24px; width: 100%; }

/ Max Width Variants / .max-sm { max-width: 640px; }

.max-md { max-width: 768px; }

.max-lg { max-width: 1024px; }

.max-xl { max-width: 1280px; }

.max-full { max-width: 100%; }

/ Background Variants / .bg-white { background-color: var(--color-white); }

.bg-gray { background-color: var(--color-neutral-50); }

.bg-primary { background-color: var(--color-primary-50); }

/ Responsive adjustments / @media (max-width: 768px) { .content { padding: 16px; } }

DashboardLayout Template Example // templates/DashboardLayout/DashboardLayout.tsx import React, { useState } from 'react'; import { Header } from '@/components/organisms/Header'; import { Sidebar, type SidebarProps } from '@/components/organisms/Sidebar'; import styles from './DashboardLayout.module.css';

export interface DashboardLayoutProps { / Header props */ headerProps: { logo: React.ReactNode; user?: { name: string; email: string; avatar?: string }; onLogout?: () => void; }; / Sidebar props / sidebarProps: SidebarProps; / Main content / children: React.ReactNode; / Page title */ pageTitle?: string; / Page description / pageDescription?: string; / Page actions (buttons, etc.) / pageActions?: React.ReactNode; /* Sidebar initially collapsed / sidebarCollapsed?: boolean; }

export const DashboardLayout: React.FC = ({ headerProps, sidebarProps, children, pageTitle, pageDescription, pageActions, sidebarCollapsed = false, }) => { const [isCollapsed, setIsCollapsed] = useState(sidebarCollapsed);

return (

{/ Top Header /}

  <div className={styles.body}>
    {/* Sidebar */}
    <Sidebar
      {...sidebarProps}
      isCollapsed={isCollapsed}
      onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
    />

    {/* Main Content Area */}
    <main className={styles.main}>
      {/* Page Header */}
      {(pageTitle || pageActions) && (
        <header className={styles.pageHeader}>
          <div className={styles.titleSection}>
            {pageTitle && <h1 className={styles.pageTitle}>{pageTitle}</h1>}
            {pageDescription && (
              <p className={styles.pageDescription}>{pageDescription}</p>
            )}
          </div>
          {pageActions && (
            <div className={styles.pageActions}>{pageActions}</div>
          )}
        </header>
      )}

      {/* Page Content */}
      <div className={styles.content}>{children}</div>
    </main>
  </div>
</div>

); };

DashboardLayout.displayName = 'DashboardLayout';

/ templates/DashboardLayout/DashboardLayout.module.css / .layout { display: flex; flex-direction: column; min-height: 100vh; }

.body { display: flex; flex: 1; }

.main { flex: 1; display: flex; flex-direction: column; overflow-x: hidden; background-color: var(--color-neutral-50); }

.pageHeader { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; padding: 24px; background-color: var(--color-white); border-bottom: 1px solid var(--color-neutral-200); }

.titleSection { flex: 1; }

.pageTitle { margin: 0; font-size: 24px; font-weight: 600; color: var(--color-neutral-900); }

.pageDescription { margin: 4px 0 0; font-size: 14px; color: var(--color-neutral-500); }

.pageActions { display: flex; gap: 12px; flex-shrink: 0; }

.content { flex: 1; padding: 24px; overflow-y: auto; }

@media (max-width: 768px) { .pageHeader { flex-direction: column; align-items: stretch; }

.pageActions { margin-top: 16px; }

.content { padding: 16px; } }

AuthLayout Template Example // templates/AuthLayout/AuthLayout.tsx import React from 'react'; import styles from './AuthLayout.module.css';

export interface AuthLayoutProps { / Logo element */ logo: React.ReactNode; / Page title / title: string; / Page subtitle / subtitle?: string; / Form content */ children: React.ReactNode; / Footer content (links, etc.) / footer?: React.ReactNode; / Background image URL / backgroundImage?: string; / Show decorative side panel */ showSidePanel?: boolean; / Side panel content */ sidePanelContent?: React.ReactNode; }

export const AuthLayout: React.FC = ({ logo, title, subtitle, children, footer, backgroundImage, showSidePanel = false, sidePanelContent, }) => { return (

{/ Side Panel (optional) /} {showSidePanel && (
url(${backgroundImage}) } : undefined } >
{sidePanelContent}
)}

  {/* Main Content */}
  <div className={styles.main}>
    <div className={styles.container}>
      {/* Logo */}
      <div className={styles.logo}>{logo}</div>

      {/* Header */}
      <header className={styles.header}>
        <h1 className={styles.title}>{title}</h1>
        {subtitle && <p className={styles.subtitle}>{subtitle}</p>}
      </header>

      {/* Form Content */}
      <div className={styles.content}>{children}</div>

      {/* Footer */}
      {footer && <footer className={styles.footer}>{footer}</footer>}
    </div>
  </div>
</div>

); };

AuthLayout.displayName = 'AuthLayout';

/ templates/AuthLayout/AuthLayout.module.css / .layout { display: flex; min-height: 100vh; }

.sidePanel { display: none; width: 50%; background-color: var(--color-primary-600); background-size: cover; background-position: center; position: relative; }

@media (min-width: 1024px) { .sidePanel { display: flex; align-items: center; justify-content: center; } }

.sidePanelContent { padding: 48px; color: var(--color-white); text-align: center; }

.main { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px; background-color: var(--color-neutral-50); }

.container { width: 100%; max-width: 400px; }

.logo { text-align: center; margin-bottom: 32px; }

.header { text-align: center; margin-bottom: 32px; }

.title { margin: 0; font-size: 28px; font-weight: 700; color: var(--color-neutral-900); }

.subtitle { margin: 8px 0 0; font-size: 16px; color: var(--color-neutral-500); }

.content { background-color: var(--color-white); padding: 32px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); }

.footer { margin-top: 24px; text-align: center; font-size: 14px; color: var(--color-neutral-500); }

.footer a { color: var(--color-primary-500); text-decoration: none; }

.footer a:hover { text-decoration: underline; }

ProductListingLayout Template Example // templates/ProductListingLayout/ProductListingLayout.tsx import React from 'react'; import { MainLayout, type MainLayoutProps } from '../MainLayout'; import styles from './ProductListingLayout.module.css';

export interface ProductListingLayoutProps { / Main layout props */ layoutProps: Omit; / Category title / categoryTitle: string; / Category description / categoryDescription?: string; / Product count */ productCount: number; / Filter sidebar content / filters: React.ReactNode; / Sort/view controls / controls: React.ReactNode; / Product grid content */ products: React.ReactNode; / Pagination content / pagination?: React.ReactNode; / Show filters on mobile / mobileFiltersOpen?: boolean; /* Toggle mobile filters / onToggleMobileFilters?: () => void; }

export const ProductListingLayout: React.FC = ({ layoutProps, categoryTitle, categoryDescription, productCount, filters, controls, products, pagination, mobileFiltersOpen = false, onToggleMobileFilters, }) => { return ( {/ Category Header /}

{categoryTitle}

{categoryDescription && (

{categoryDescription}

)} {productCount} products

  <div className={styles.body}>
    {/* Desktop Filters */}
    <aside className={styles.sidebar}>
      <div className={styles.sidebarContent}>{filters}</div>
    </aside>

    {/* Mobile Filters Overlay */}
    {mobileFiltersOpen && (
      <div className={styles.mobileFilters}>
        <div className={styles.mobileFiltersHeader}>
          <h2>Filters</h2>
          <button onClick={onToggleMobileFilters}>Close</button>
        </div>
        <div className={styles.mobileFiltersContent}>{filters}</div>
      </div>
    )}

    {/* Main Content */}
    <div className={styles.main}>
      {/* Controls Bar */}
      <div className={styles.controls}>
        <button
          className={styles.mobileFilterButton}
          onClick={onToggleMobileFilters}
        >
          Filters
        </button>
        {controls}
      </div>

      {/* Product Grid */}
      <div className={styles.products}>{products}</div>

      {/* Pagination */}
      {pagination && (
        <div className={styles.pagination}>{pagination}</div>
      )}
    </div>
  </div>
</MainLayout>

); };

ProductListingLayout.displayName = 'ProductListingLayout';

BlogPostLayout Template Example // templates/BlogPostLayout/BlogPostLayout.tsx import React from 'react'; import { MainLayout, type MainLayoutProps } from '../MainLayout'; import { Avatar } from '@/components/atoms/Avatar'; import { Text } from '@/components/atoms/Typography'; import styles from './BlogPostLayout.module.css';

export interface Author { name: string; avatar?: string; bio?: string; }

export interface BlogPostLayoutProps { / Main layout props */ layoutProps: Omit; / Post title / title: string; / Post subtitle / subtitle?: string; / Featured image */ featuredImage?: string; / Author information / author: Author; / Publication date / publishedAt: string; / Reading time */ readingTime?: string; / Post categories/tags / tags?: React.ReactNode; / Main article content / children: React.ReactNode; / Table of contents */ tableOfContents?: React.ReactNode; / Author bio card / showAuthorBio?: boolean; / Related posts / relatedPosts?: React.ReactNode; / Comments section */ comments?: React.ReactNode; / Social share buttons */ shareButtons?: React.ReactNode; }

export const BlogPostLayout: React.FC = ({ layoutProps, title, subtitle, featuredImage, author, publishedAt, readingTime, tags, children, tableOfContents, showAuthorBio = true, relatedPosts, comments, shareButtons, }) => { const formattedDate = new Date(publishedAt).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', });

return (

{/ Article Header /}
{tags &&
{tags}
}

      <h1 className={styles.title}>{title}</h1>

      {subtitle && <p className={styles.subtitle}>{subtitle}</p>}

      {/* Author & Meta */}
      <div className={styles.meta}>
        <div className={styles.author}>
          <Avatar
            src={author.avatar}
            alt={author.name}
            initials={author.name.slice(0, 2).toUpperCase()}
            size="md"
          />
          <div className={styles.authorInfo}>
            <Text weight="semibold">{author.name}</Text>
            <Text size="sm" color="muted">
              {formattedDate}
              {readingTime && ` · ${readingTime}`}
            </Text>
          </div>
        </div>

        {shareButtons && (
          <div className={styles.share}>{shareButtons}</div>
        )}
      </div>
    </header>

    {/* Featured Image */}
    {featuredImage && (
      <figure className={styles.featuredImage}>
        <img src={featuredImage} alt={title} />
      </figure>
    )}

    {/* Content with Optional TOC */}
    <div className={styles.contentWrapper}>
      {/* Table of Contents (Desktop) */}
      {tableOfContents && (
        <aside className={styles.toc}>
          <div className={styles.tocContent}>{tableOfContents}</div>
        </aside>
      )}

      {/* Main Content */}
      <div className={styles.content}>{children}</div>
    </div>

    {/* Author Bio */}
    {showAuthorBio && (
      <footer className={styles.authorBio}>
        <Avatar
          src={author.avatar}
          alt={author.name}
          initials={author.name.slice(0, 2).toUpperCase()}
          size="lg"
        />
        <div>
          <Text weight="semibold" size="lg">
            {author.name}
          </Text>
          {author.bio && <Text color="muted">{author.bio}</Text>}
        </div>
      </footer>
    )}

    {/* Share (Bottom) */}
    {shareButtons && (
      <div className={styles.bottomShare}>{shareButtons}</div>
    )}
  </article>

  {/* Related Posts */}
  {relatedPosts && (
    <section className={styles.relatedPosts}>
      <h2>Related Posts</h2>
      {relatedPosts}
    </section>
  )}

  {/* Comments */}
  {comments && (
    <section className={styles.comments}>{comments}</section>
  )}
</MainLayout>

); };

BlogPostLayout.displayName = 'BlogPostLayout';

TwoColumnLayout Template Example // templates/TwoColumnLayout/TwoColumnLayout.tsx import React from 'react'; import styles from './TwoColumnLayout.module.css';

export interface TwoColumnLayoutProps { / Left column (usually main content) */ main: React.ReactNode; / Right column (usually sidebar) / sidebar: React.ReactNode; / Sidebar position / sidebarPosition?: 'left' | 'right'; / Sidebar width */ sidebarWidth?: 'narrow' | 'medium' | 'wide'; / Sticky sidebar / stickySidebar?: boolean; / Reverse on mobile (show sidebar first) / reverseMobile?: boolean; /* Gap between columns / gap?: 'sm' | 'md' | 'lg'; }

export const TwoColumnLayout: React.FC = ({ main, sidebar, sidebarPosition = 'right', sidebarWidth = 'medium', stickySidebar = false, reverseMobile = false, gap = 'md', }) => { const layoutClass = [ styles.layout, styles[sidebar-${sidebarPosition}], styles[width-${sidebarWidth}], styles[gap-${gap}], reverseMobile && styles.reverseMobile, ] .filter(Boolean) .join(' ');

const sidebarClass = [ styles.sidebar, stickySidebar && styles.sticky, ] .filter(Boolean) .join(' ');

return (

{main}
); };

TwoColumnLayout.displayName = 'TwoColumnLayout';

Best Practices 1. Use Placeholder Content // GOOD: Template with placeholder content slots const ProductDetailLayout = ({ productGallery, // Placeholder for gallery component productInfo, // Placeholder for product details productTabs, // Placeholder for tabs relatedProducts, // Placeholder for recommendations }) => (

{productGallery}
{productInfo}
{productTabs}
{relatedProducts}

);

// BAD: Template with hardcoded content const ProductDetailLayout = ({ product }) => (

{/* Too specific */}

{product.name}

{/* Real content */}

{product.description}

);

  1. Define Clear Content Areas // GOOD: Clear, named content slots interface PageTemplateProps { header: React.ReactNode; hero?: React.ReactNode; main: React.ReactNode; sidebar?: React.ReactNode; footer: React.ReactNode; }

// BAD: Generic children only interface PageTemplateProps { children: React.ReactNode; }

  1. Handle Responsive Layouts // GOOD: Responsive considerations built-in const DashboardLayout = ({ sidebar, main }) => (
    {main}

);

// CSS handles responsive behavior // .sidebar { @media (max-width: 768px) { display: none; } }

  1. Keep Templates Thin // GOOD: Template just arranges organisms const MainLayout = ({ header, main, footer }) => (
    {header}
    {main}
    {footer}

);

// BAD: Template with business logic const MainLayout = ({ userId }) => { const user = useUser(userId); // Fetching data const isAdmin = user?.role === 'admin'; // Business logic

return (

{/ ... /}
); };

Anti-Patterns to Avoid 1. Templates with Real Content // BAD: Hardcoded real content const HomepageLayout = () => (

Welcome to Our Store

{/* Real content! */}

Shop our latest collection...

{/* Real content! */}

);

// GOOD: Content passed as props/children const HomepageLayout = ({ heroTitle, heroDescription }) => (

{heroTitle}

{heroDescription}

);

  1. Over-Nested Templates // BAD: Templates containing templates const AppLayout = () => ( {/ Too much nesting /} );

// GOOD: Choose appropriate template directly const AppPage = () => ( {/ Content /} );

  1. Templates with Too Many Props // BAD: Too many configuration options interface LayoutProps { showHeader: boolean; showFooter: boolean; showSidebar: boolean; sidebarPosition: 'left' | 'right'; headerVariant: 'default' | 'minimal' | 'transparent'; footerVariant: 'default' | 'minimal'; maxWidth: 'sm' | 'md' | 'lg' | 'xl'; // ... 20 more props }

// GOOD: Create separate templates const FullPageLayout = ({ ... }) => { ... }; const MinimalLayout = ({ ... }) => { ... }; const SidebarLayout = ({ ... }) => { ... };

Template Composition Patterns Nested Layouts // Base layout for all pages const BaseLayout = ({ children }) => (

{children}

);

// Marketing layout extending base const MarketingLayout = ({ children }) => (

{children}
);

// App layout extending base const AppLayout = ({ children }) => (

{children}
);

Slot-Based Layouts interface SlotLayoutProps { slots: { header?: React.ReactNode; sidebar?: React.ReactNode; main: React.ReactNode; footer?: React.ReactNode; }; }

const SlotLayout: React.FC = ({ slots }) => (

{slots.header &&
{slots.header}
}
{slots.sidebar && }
{slots.main}
{slots.footer &&
{slots.footer}
}

);

When to Use This Skill Creating page structure patterns Building reusable layout components Establishing consistent page architectures Setting up responsive frameworks Defining content slot patterns Related Skills atomic-design-fundamentals - Core methodology overview atomic-design-organisms - Building complex organisms atomic-design-integration - Framework-specific patterns

返回排行榜