phase 6 ,7,9

This commit is contained in:
alorig
2025-11-18 05:52:10 +05:00
parent 9a6d47b91b
commit a6a80ad005
28 changed files with 2173 additions and 9 deletions

View File

@@ -15,10 +15,41 @@ These components are designed to be framework-agnostic where possible, but curre
```
shared/
├── blocks/ # Content blocks (HeroBlock, FeatureGridBlock, StatsPanel)
├── layouts/ # Page layouts (DefaultLayout, MinimalLayout)
├── templates/ # Full page templates (MarketingTemplate, LandingTemplate)
└── index.ts # Barrel exports
├── blocks/ # Content blocks (12 total)
│ ├── HeroBlock.tsx
│ ├── FeatureGridBlock.tsx
│ ├── StatsPanel.tsx
│ ├── ServicesBlock.tsx
│ ├── ProductsBlock.tsx
│ ├── TestimonialsBlock.tsx
│ ├── ContactFormBlock.tsx
│ ├── CTABlock.tsx
│ ├── ImageGalleryBlock.tsx
│ ├── VideoBlock.tsx
│ ├── TextBlock.tsx
│ ├── QuoteBlock.tsx
│ ├── blocks.css
│ └── index.ts
├── layouts/ # Page layouts (7 total)
│ ├── DefaultLayout.tsx
│ ├── MinimalLayout.tsx
│ ├── MagazineLayout.tsx
│ ├── EcommerceLayout.tsx
│ ├── PortfolioLayout.tsx
│ ├── BlogLayout.tsx
│ ├── CorporateLayout.tsx
│ ├── layouts.css
│ └── index.ts
├── templates/ # Full page templates (6 total)
│ ├── MarketingTemplate.tsx
│ ├── LandingTemplate.tsx
│ ├── BlogTemplate.tsx
│ ├── BusinessTemplate.tsx
│ ├── PortfolioTemplate.tsx
│ ├── EcommerceTemplate.tsx
│ └── index.ts
├── index.ts # Barrel exports
└── README.md # This file
```
## Usage
@@ -102,6 +133,108 @@ Statistics display component.
/>
```
#### `ServicesBlock`
Display services or offerings in a grid layout.
**Props:**
- `title?: string` - Section title
- `subtitle?: string` - Section subtitle
- `services: Array<{ title: string; description: string; icon?: ReactNode; imageUrl?: string }>` - Service items
- `columns?: 2 | 3 | 4` - Number of columns
- `variant?: 'default' | 'card' | 'minimal'` - Display variant
#### `ProductsBlock`
Display products in a grid or list layout.
**Props:**
- `title?: string` - Section title
- `subtitle?: string` - Section subtitle
- `products: Array<{ name: string; description?: string; price?: string; imageUrl?: string; ctaLabel?: string }>` - Product items
- `columns?: 2 | 3 | 4` - Number of columns
- `variant?: 'grid' | 'list' | 'carousel'` - Display variant
#### `TestimonialsBlock`
Display customer testimonials with ratings and author info.
**Props:**
- `title?: string` - Section title
- `subtitle?: string` - Section subtitle
- `testimonials: Array<{ quote: string; author: string; role?: string; company?: string; avatarUrl?: string; rating?: number }>` - Testimonial items
- `columns?: 1 | 2 | 3` - Number of columns
- `variant?: 'default' | 'card' | 'minimal'` - Display variant
#### `ContactFormBlock`
Contact form with customizable fields.
**Props:**
- `title?: string` - Form title
- `subtitle?: string` - Form subtitle
- `fields?: Array<{ name: string; label: string; type: 'text' | 'email' | 'tel' | 'textarea'; required?: boolean; placeholder?: string }>` - Form fields
- `submitLabel?: string` - Submit button label
- `onSubmit?: (data: Record<string, string>) => void` - Submit handler
#### `CTABlock`
Call-to-action section with primary and secondary actions.
**Props:**
- `title: string` - CTA title
- `subtitle?: string` - CTA subtitle
- `primaryCtaLabel?: string` - Primary button label
- `primaryCtaLink?: string` - Primary button link
- `onPrimaryCtaClick?: () => void` - Primary button click handler
- `secondaryCtaLabel?: string` - Secondary button label
- `secondaryCtaLink?: string` - Secondary button link
- `onSecondaryCtaClick?: () => void` - Secondary button click handler
- `backgroundImage?: string` - Background image URL
- `variant?: 'default' | 'centered' | 'split'` - Layout variant
#### `ImageGalleryBlock`
Image gallery with grid, masonry, or carousel layout.
**Props:**
- `title?: string` - Gallery title
- `subtitle?: string` - Gallery subtitle
- `images: Array<{ url: string; alt?: string; caption?: string; thumbnailUrl?: string }>` - Image items
- `columns?: 2 | 3 | 4` - Number of columns
- `variant?: 'grid' | 'masonry' | 'carousel'` - Display variant
- `lightbox?: boolean` - Enable lightbox on click
#### `VideoBlock`
Video player component supporting both video URLs and embed codes.
**Props:**
- `title?: string` - Video title
- `subtitle?: string` - Video subtitle
- `videoUrl?: string` - Video file URL
- `embedCode?: string` - HTML embed code (e.g., YouTube iframe)
- `thumbnailUrl?: string` - Video thumbnail/poster
- `autoplay?: boolean` - Autoplay video
- `controls?: boolean` - Show video controls
- `loop?: boolean` - Loop video
- `muted?: boolean` - Mute video
- `variant?: 'default' | 'fullwidth' | 'centered'` - Layout variant
#### `TextBlock`
Simple text content block with customizable alignment and width.
**Props:**
- `title?: string` - Block title
- `content: string | ReactNode` - Text content (HTML string or React nodes)
- `align?: 'left' | 'center' | 'right' | 'justify'` - Text alignment
- `variant?: 'default' | 'narrow' | 'wide' | 'fullwidth'` - Width variant
#### `QuoteBlock`
Quote/testimonial block with author information.
**Props:**
- `quote: string` - Quote text
- `author?: string` - Author name
- `role?: string` - Author role
- `company?: string` - Author company
- `avatarUrl?: string` - Author avatar image
- `variant?: 'default' | 'large' | 'minimal' | 'card'` - Display variant
- `align?: 'left' | 'center' | 'right'` - Text alignment
### Layouts
#### `DefaultLayout`
@@ -125,15 +258,64 @@ Minimal layout for focused content pages.
**Props:**
- `children: ReactNode` - Page content
- `maxWidth?: string` - Maximum content width
- `background?: 'light' | 'dark'` - Background color
**Example:**
```tsx
<MinimalLayout maxWidth="800px">
<MinimalLayout background="light">
<ArticleContent />
</MinimalLayout>
```
#### `MagazineLayout`
Magazine-style layout with featured section and sidebar.
**Props:**
- `header?: ReactNode` - Page header
- `featured?: ReactNode` - Featured content section
- `sidebar?: ReactNode` - Sidebar content
- `mainContent: ReactNode` - Main content area
- `footer?: ReactNode` - Page footer
#### `EcommerceLayout`
E-commerce layout with navigation and product sidebar.
**Props:**
- `header?: ReactNode` - Page header
- `navigation?: ReactNode` - Navigation menu
- `sidebar?: ReactNode` - Sidebar (filters, categories)
- `mainContent: ReactNode` - Main content (products)
- `footer?: ReactNode` - Page footer
#### `PortfolioLayout`
Portfolio layout optimized for showcasing work.
**Props:**
- `header?: ReactNode` - Page header
- `mainContent: ReactNode` - Main content (gallery, projects)
- `footer?: ReactNode` - Page footer
- `fullWidth?: boolean` - Full-width layout option
#### `BlogLayout`
Blog layout with optional sidebar (left or right).
**Props:**
- `header?: ReactNode` - Page header
- `sidebar?: ReactNode` - Sidebar content
- `mainContent: ReactNode` - Main content (blog posts)
- `footer?: ReactNode` - Page footer
- `sidebarPosition?: 'left' | 'right'` - Sidebar position
#### `CorporateLayout`
Corporate/business layout with navigation and structured sections.
**Props:**
- `header?: ReactNode` - Page header
- `navigation?: ReactNode` - Navigation menu
- `mainContent: ReactNode` - Main content
- `footer?: ReactNode` - Page footer
- `sidebar?: ReactNode` - Optional sidebar
### Templates
#### `MarketingTemplate`
@@ -169,6 +351,51 @@ Landing page template optimized for conversions.
**Props:**
- Similar to `MarketingTemplate` with additional conversion-focused sections
#### `BlogTemplate`
Blog page template with posts and sidebar.
**Props:**
- `header?: ReactNode` - Page header
- `sidebar?: ReactNode` - Sidebar content
- `posts: ReactNode` - Blog posts content
- `footer?: ReactNode` - Page footer
- `sidebarPosition?: 'left' | 'right'` - Sidebar position
#### `BusinessTemplate`
Business/corporate page template.
**Props:**
- `header?: ReactNode` - Page header
- `navigation?: ReactNode` - Navigation menu
- `hero?: ReactNode` - Hero section
- `features?: ReactNode` - Features section
- `services?: ReactNode` - Services section
- `testimonials?: ReactNode` - Testimonials section
- `cta?: ReactNode` - Call-to-action section
- `footer?: ReactNode` - Page footer
#### `PortfolioTemplate`
Portfolio showcase template.
**Props:**
- `header?: ReactNode` - Page header
- `gallery?: ReactNode` - Portfolio gallery
- `about?: ReactNode` - About section
- `contact?: ReactNode` - Contact section
- `footer?: ReactNode` - Page footer
- `fullWidth?: boolean` - Full-width layout
#### `EcommerceTemplate`
E-commerce store template.
**Props:**
- `header?: ReactNode` - Page header
- `navigation?: ReactNode` - Navigation menu
- `sidebar?: ReactNode` - Sidebar (filters)
- `products?: ReactNode` - Products grid
- `featured?: ReactNode` - Featured products
- `footer?: ReactNode` - Page footer
## Styling
Components use CSS modules and shared CSS files:

View File

@@ -0,0 +1,76 @@
import type { ReactNode } from 'react';
import './blocks.css';
export interface CTABlockProps {
title: string;
subtitle?: string;
primaryCtaLabel?: string;
primaryCtaLink?: string;
onPrimaryCtaClick?: () => void;
secondaryCtaLabel?: string;
secondaryCtaLink?: string;
onSecondaryCtaClick?: () => void;
backgroundImage?: string;
variant?: 'default' | 'centered' | 'split';
children?: ReactNode;
}
export function CTABlock({
title,
subtitle,
primaryCtaLabel,
primaryCtaLink,
onPrimaryCtaClick,
secondaryCtaLabel,
secondaryCtaLink,
onSecondaryCtaClick,
backgroundImage,
variant = 'default',
children
}: CTABlockProps) {
return (
<section
className={`shared-cta shared-cta--${variant}`}
style={backgroundImage ? { backgroundImage: `url(${backgroundImage})` } : undefined}
>
<div className="shared-cta__content">
<h2 className="shared-cta__title">{title}</h2>
{subtitle && <p className="shared-cta__subtitle">{subtitle}</p>}
{children && <div className="shared-cta__children">{children}</div>}
<div className="shared-cta__actions">
{primaryCtaLabel && (
primaryCtaLink ? (
<a href={primaryCtaLink} className="shared-button shared-button--primary">
{primaryCtaLabel}
</a>
) : (
<button
type="button"
className="shared-button shared-button--primary"
onClick={onPrimaryCtaClick}
>
{primaryCtaLabel}
</button>
)
)}
{secondaryCtaLabel && (
secondaryCtaLink ? (
<a href={secondaryCtaLink} className="shared-button shared-button--secondary">
{secondaryCtaLabel}
</a>
) : (
<button
type="button"
className="shared-button shared-button--secondary"
onClick={onSecondaryCtaClick}
>
{secondaryCtaLabel}
</button>
)
)}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,83 @@
import { useState } from 'react';
import './blocks.css';
export interface ContactFormBlockProps {
title?: string;
subtitle?: string;
fields?: Array<{
name: string;
label: string;
type: 'text' | 'email' | 'tel' | 'textarea';
required?: boolean;
placeholder?: string;
}>;
submitLabel?: string;
onSubmit?: (data: Record<string, string>) => void;
}
export function ContactFormBlock({
title,
subtitle,
fields = [
{ name: 'name', label: 'Name', type: 'text', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'message', label: 'Message', type: 'textarea', required: true },
],
submitLabel = 'Submit',
onSubmit
}: ContactFormBlockProps) {
const [formData, setFormData] = useState<Record<string, string>>({});
const handleChange = (name: string, value: string) => {
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit?.(formData);
};
return (
<section className="shared-contact-form">
{title && <h2 className="shared-contact-form__title">{title}</h2>}
{subtitle && <p className="shared-contact-form__subtitle">{subtitle}</p>}
<form onSubmit={handleSubmit} className="shared-contact-form__form">
{fields.map((field) => (
<div key={field.name} className="shared-contact-form__field">
<label htmlFor={field.name} className="shared-contact-form__label">
{field.label}
{field.required && <span className="shared-contact-form__required">*</span>}
</label>
{field.type === 'textarea' ? (
<textarea
id={field.name}
name={field.name}
required={field.required}
placeholder={field.placeholder}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
className="shared-contact-form__input"
rows={4}
/>
) : (
<input
id={field.name}
name={field.name}
type={field.type}
required={field.required}
placeholder={field.placeholder}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
className="shared-contact-form__input"
/>
)}
</div>
))}
<button type="submit" className="shared-button shared-button--primary">
{submitLabel}
</button>
</form>
</section>
);
}

View File

@@ -0,0 +1,66 @@
import { useState } from 'react';
import './blocks.css';
export interface ImageGalleryBlockProps {
title?: string;
subtitle?: string;
images: Array<{
url: string;
alt?: string;
caption?: string;
thumbnailUrl?: string;
}>;
columns?: 2 | 3 | 4;
variant?: 'grid' | 'masonry' | 'carousel';
lightbox?: boolean;
}
export function ImageGalleryBlock({
title,
subtitle,
images,
columns = 3,
variant = 'grid',
lightbox = false
}: ImageGalleryBlockProps) {
const [selectedImage, setSelectedImage] = useState<number | null>(null);
return (
<section className={`shared-image-gallery shared-image-gallery--${variant}`}>
{title && <h2 className="shared-image-gallery__title">{title}</h2>}
{subtitle && <p className="shared-image-gallery__subtitle">{subtitle}</p>}
<div className={`shared-image-gallery__grid shared-image-gallery__grid--${columns}`}>
{images.map((image, index) => (
<div
key={index}
className="shared-image-gallery__item"
onClick={() => lightbox && setSelectedImage(index)}
>
<img
src={image.thumbnailUrl || image.url}
alt={image.alt || image.caption || `Image ${index + 1}`}
className="shared-image-gallery__image"
loading="lazy"
/>
{image.caption && (
<p className="shared-image-gallery__caption">{image.caption}</p>
)}
</div>
))}
</div>
{lightbox && selectedImage !== null && (
<div
className="shared-image-gallery__lightbox"
onClick={() => setSelectedImage(null)}
>
<img
src={images[selectedImage].url}
alt={images[selectedImage].alt || images[selectedImage].caption || 'Lightbox image'}
className="shared-image-gallery__lightbox-image"
/>
</div>
)}
</section>
);
}

View File

@@ -0,0 +1,58 @@
import type { ReactNode } from 'react';
import './blocks.css';
export interface ProductItem {
name: string;
description?: string;
price?: string;
imageUrl?: string;
ctaLabel?: string;
onCtaClick?: () => void;
}
export interface ProductsBlockProps {
title?: string;
subtitle?: string;
products: ProductItem[];
columns?: 2 | 3 | 4;
variant?: 'grid' | 'list' | 'carousel';
}
export function ProductsBlock({
title,
subtitle,
products,
columns = 3,
variant = 'grid'
}: ProductsBlockProps) {
return (
<section className={`shared-products shared-products--${variant}`}>
{title && <h2 className="shared-products__title">{title}</h2>}
{subtitle && <p className="shared-products__subtitle">{subtitle}</p>}
<div className={`shared-products__grid shared-products__grid--${columns}`}>
{products.map((product, index) => (
<div key={index} className="shared-products__item">
{product.imageUrl && (
<img src={product.imageUrl} alt={product.name} className="shared-products__image" />
)}
<h3 className="shared-products__name">{product.name}</h3>
{product.description && (
<p className="shared-products__description">{product.description}</p>
)}
{product.price && <p className="shared-products__price">{product.price}</p>}
{product.ctaLabel && (
<button
type="button"
className="shared-button"
onClick={product.onCtaClick}
>
{product.ctaLabel}
</button>
)}
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,53 @@
import './blocks.css';
export interface QuoteBlockProps {
quote: string;
author?: string;
role?: string;
company?: string;
avatarUrl?: string;
variant?: 'default' | 'large' | 'minimal' | 'card';
align?: 'left' | 'center' | 'right';
}
export function QuoteBlock({
quote,
author,
role,
company,
avatarUrl,
variant = 'default',
align = 'left'
}: QuoteBlockProps) {
return (
<section className={`shared-quote-block shared-quote-block--${variant}`}>
<blockquote
className="shared-quote-block__quote"
style={{ textAlign: align }}
>
{quote}
</blockquote>
{author && (
<div className="shared-quote-block__author">
{avatarUrl && (
<img
src={avatarUrl}
alt={author}
className="shared-quote-block__avatar"
/>
)}
<div>
<p className="shared-quote-block__author-name">{author}</p>
{role && (
<p className="shared-quote-block__author-role">
{role}
{company && ` at ${company}`}
</p>
)}
</div>
</div>
)}
</section>
);
}

View File

@@ -0,0 +1,45 @@
import type { ReactNode } from 'react';
import './blocks.css';
export interface ServiceItem {
title: string;
description: string;
icon?: ReactNode;
imageUrl?: string;
}
export interface ServicesBlockProps {
title?: string;
subtitle?: string;
services: ServiceItem[];
columns?: 2 | 3 | 4;
variant?: 'default' | 'card' | 'minimal';
}
export function ServicesBlock({
title,
subtitle,
services,
columns = 3,
variant = 'default'
}: ServicesBlockProps) {
return (
<section className={`shared-services shared-services--${variant}`}>
{title && <h2 className="shared-services__title">{title}</h2>}
{subtitle && <p className="shared-services__subtitle">{subtitle}</p>}
<div className={`shared-services__grid shared-services__grid--${columns}`}>
{services.map((service, index) => (
<div key={index} className="shared-services__item">
{service.icon && <div className="shared-services__icon">{service.icon}</div>}
{service.imageUrl && (
<img src={service.imageUrl} alt={service.title} className="shared-services__image" />
)}
<h3 className="shared-services__item-title">{service.title}</h3>
<p className="shared-services__item-description">{service.description}</p>
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,67 @@
import type { ReactNode } from 'react';
import './blocks.css';
export interface TestimonialItem {
quote: string;
author: string;
role?: string;
company?: string;
avatarUrl?: string;
rating?: number;
}
export interface TestimonialsBlockProps {
title?: string;
subtitle?: string;
testimonials: TestimonialItem[];
columns?: 1 | 2 | 3;
variant?: 'default' | 'card' | 'minimal';
}
export function TestimonialsBlock({
title,
subtitle,
testimonials,
columns = 3,
variant = 'default'
}: TestimonialsBlockProps) {
return (
<section className={`shared-testimonials shared-testimonials--${variant}`}>
{title && <h2 className="shared-testimonials__title">{title}</h2>}
{subtitle && <p className="shared-testimonials__subtitle">{subtitle}</p>}
<div className={`shared-testimonials__grid shared-testimonials__grid--${columns}`}>
{testimonials.map((testimonial, index) => (
<div key={index} className="shared-testimonials__item">
{testimonial.rating && (
<div className="shared-testimonials__rating">
{'★'.repeat(testimonial.rating)}
</div>
)}
<blockquote className="shared-testimonials__quote">
{testimonial.quote}
</blockquote>
<div className="shared-testimonials__author">
{testimonial.avatarUrl && (
<img
src={testimonial.avatarUrl}
alt={testimonial.author}
className="shared-testimonials__avatar"
/>
)}
<div>
<p className="shared-testimonials__author-name">{testimonial.author}</p>
{testimonial.role && (
<p className="shared-testimonials__author-role">
{testimonial.role}
{testimonial.company && ` at ${testimonial.company}`}
</p>
)}
</div>
</div>
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,35 @@
import type { ReactNode } from 'react';
import './blocks.css';
export interface TextBlockProps {
title?: string;
content: string | ReactNode;
align?: 'left' | 'center' | 'right' | 'justify';
variant?: 'default' | 'narrow' | 'wide' | 'fullwidth';
className?: string;
}
export function TextBlock({
title,
content,
align = 'left',
variant = 'default',
className = ''
}: TextBlockProps) {
return (
<section className={`shared-text-block shared-text-block--${variant} ${className}`}>
{title && <h2 className="shared-text-block__title">{title}</h2>}
<div
className="shared-text-block__content"
style={{ textAlign: align }}
>
{typeof content === 'string' ? (
<div dangerouslySetInnerHTML={{ __html: content }} />
) : (
content
)}
</div>
</section>
);
}

View File

@@ -0,0 +1,55 @@
import './blocks.css';
export interface VideoBlockProps {
title?: string;
subtitle?: string;
videoUrl?: string;
embedCode?: string;
thumbnailUrl?: string;
autoplay?: boolean;
controls?: boolean;
loop?: boolean;
muted?: boolean;
variant?: 'default' | 'fullwidth' | 'centered';
}
export function VideoBlock({
title,
subtitle,
videoUrl,
embedCode,
thumbnailUrl,
autoplay = false,
controls = true,
loop = false,
muted = false,
variant = 'default'
}: VideoBlockProps) {
return (
<section className={`shared-video shared-video--${variant}`}>
{title && <h2 className="shared-video__title">{title}</h2>}
{subtitle && <p className="shared-video__subtitle">{subtitle}</p>}
<div className="shared-video__container">
{embedCode ? (
<div
className="shared-video__embed"
dangerouslySetInnerHTML={{ __html: embedCode }}
/>
) : videoUrl ? (
<video
src={videoUrl}
controls={controls}
autoPlay={autoplay}
loop={loop}
muted={muted}
poster={thumbnailUrl}
className="shared-video__player"
>
Your browser does not support the video tag.
</video>
) : null}
</div>
</section>
);
}

View File

@@ -4,5 +4,22 @@ export { FeatureGridBlock } from './FeatureGridBlock';
export type { FeatureGridBlockProps } from './FeatureGridBlock';
export { StatsPanel } from './StatsPanel';
export type { StatsPanelProps, StatItem } from './StatsPanel';
export { ServicesBlock } from './ServicesBlock';
export type { ServicesBlockProps, ServiceItem } from './ServicesBlock';
export { ProductsBlock } from './ProductsBlock';
export type { ProductsBlockProps, ProductItem } from './ProductsBlock';
export { TestimonialsBlock } from './TestimonialsBlock';
export type { TestimonialsBlockProps, TestimonialItem } from './TestimonialsBlock';
export { ContactFormBlock } from './ContactFormBlock';
export type { ContactFormBlockProps } from './ContactFormBlock';
export { CTABlock } from './CTABlock';
export type { CTABlockProps } from './CTABlock';
export { ImageGalleryBlock } from './ImageGalleryBlock';
export type { ImageGalleryBlockProps } from './ImageGalleryBlock';
export { VideoBlock } from './VideoBlock';
export type { VideoBlockProps } from './VideoBlock';
export { TextBlock } from './TextBlock';
export type { TextBlockProps } from './TextBlock';
export { QuoteBlock } from './QuoteBlock';
export type { QuoteBlockProps } from './QuoteBlock';

View File

@@ -0,0 +1,35 @@
import type { ReactNode } from 'react';
import './layouts.css';
export interface BlogLayoutProps {
header?: ReactNode;
sidebar?: ReactNode;
mainContent: ReactNode;
footer?: ReactNode;
sidebarPosition?: 'left' | 'right';
}
export function BlogLayout({
header,
sidebar,
mainContent,
footer,
sidebarPosition = 'right'
}: BlogLayoutProps) {
return (
<div className={`shared-layout shared-layout--blog shared-layout--sidebar-${sidebarPosition}`}>
{header && <header className="shared-layout__header">{header}</header>}
<div className="shared-layout__content">
{sidebar && sidebarPosition === 'left' && (
<aside className="shared-layout__sidebar">{sidebar}</aside>
)}
<main className="shared-layout__main">{mainContent}</main>
{sidebar && sidebarPosition === 'right' && (
<aside className="shared-layout__sidebar">{sidebar}</aside>
)}
</div>
{footer && <footer className="shared-layout__footer">{footer}</footer>}
</div>
);
}

View File

@@ -0,0 +1,31 @@
import type { ReactNode } from 'react';
import './layouts.css';
export interface CorporateLayoutProps {
header?: ReactNode;
navigation?: ReactNode;
mainContent: ReactNode;
footer?: ReactNode;
sidebar?: ReactNode;
}
export function CorporateLayout({
header,
navigation,
mainContent,
footer,
sidebar
}: CorporateLayoutProps) {
return (
<div className="shared-layout shared-layout--corporate">
{header && <header className="shared-layout__header">{header}</header>}
{navigation && <nav className="shared-layout__navigation">{navigation}</nav>}
<div className="shared-layout__content">
<main className="shared-layout__main">{mainContent}</main>
{sidebar && <aside className="shared-layout__sidebar">{sidebar}</aside>}
</div>
{footer && <footer className="shared-layout__footer">{footer}</footer>}
</div>
);
}

View File

@@ -0,0 +1,31 @@
import type { ReactNode } from 'react';
import './layouts.css';
export interface EcommerceLayoutProps {
header?: ReactNode;
navigation?: ReactNode;
sidebar?: ReactNode;
mainContent: ReactNode;
footer?: ReactNode;
}
export function EcommerceLayout({
header,
navigation,
sidebar,
mainContent,
footer
}: EcommerceLayoutProps) {
return (
<div className="shared-layout shared-layout--ecommerce">
{header && <header className="shared-layout__header">{header}</header>}
{navigation && <nav className="shared-layout__navigation">{navigation}</nav>}
<div className="shared-layout__content">
{sidebar && <aside className="shared-layout__sidebar">{sidebar}</aside>}
<main className="shared-layout__main">{mainContent}</main>
</div>
{footer && <footer className="shared-layout__footer">{footer}</footer>}
</div>
);
}

View File

@@ -0,0 +1,31 @@
import type { ReactNode } from 'react';
import './layouts.css';
export interface MagazineLayoutProps {
header?: ReactNode;
featured?: ReactNode;
sidebar?: ReactNode;
mainContent: ReactNode;
footer?: ReactNode;
}
export function MagazineLayout({
header,
featured,
sidebar,
mainContent,
footer
}: MagazineLayoutProps) {
return (
<div className="shared-layout shared-layout--magazine">
{header && <header className="shared-layout__header">{header}</header>}
{featured && <section className="shared-layout__featured">{featured}</section>}
<div className="shared-layout__content">
<main className="shared-layout__main">{mainContent}</main>
{sidebar && <aside className="shared-layout__sidebar">{sidebar}</aside>}
</div>
{footer && <footer className="shared-layout__footer">{footer}</footer>}
</div>
);
}

View File

@@ -0,0 +1,25 @@
import type { ReactNode } from 'react';
import './layouts.css';
export interface PortfolioLayoutProps {
header?: ReactNode;
mainContent: ReactNode;
footer?: ReactNode;
fullWidth?: boolean;
}
export function PortfolioLayout({
header,
mainContent,
footer,
fullWidth = false
}: PortfolioLayoutProps) {
return (
<div className={`shared-layout shared-layout--portfolio ${fullWidth ? 'shared-layout--fullwidth' : ''}`}>
{header && <header className="shared-layout__header">{header}</header>}
<main className="shared-layout__main">{mainContent}</main>
{footer && <footer className="shared-layout__footer">{footer}</footer>}
</div>
);
}

View File

@@ -2,5 +2,14 @@ export { DefaultLayout } from './DefaultLayout';
export type { DefaultLayoutProps } from './DefaultLayout';
export { MinimalLayout } from './MinimalLayout';
export type { MinimalLayoutProps } from './MinimalLayout';
export { MagazineLayout } from './MagazineLayout';
export type { MagazineLayoutProps } from './MagazineLayout';
export { EcommerceLayout } from './EcommerceLayout';
export type { EcommerceLayoutProps } from './EcommerceLayout';
export { PortfolioLayout } from './PortfolioLayout';
export type { PortfolioLayoutProps } from './PortfolioLayout';
export { BlogLayout } from './BlogLayout';
export type { BlogLayoutProps } from './BlogLayout';
export { CorporateLayout } from './CorporateLayout';
export type { CorporateLayoutProps } from './CorporateLayout';

View File

@@ -0,0 +1,29 @@
import type { ReactNode } from 'react';
import { BlogLayout } from '../layouts';
export interface BlogTemplateProps {
header?: ReactNode;
sidebar?: ReactNode;
posts: ReactNode;
footer?: ReactNode;
sidebarPosition?: 'left' | 'right';
}
export function BlogTemplate({
header,
sidebar,
posts,
footer,
sidebarPosition = 'right'
}: BlogTemplateProps) {
return (
<BlogLayout
header={header}
sidebar={sidebar}
mainContent={posts}
footer={footer}
sidebarPosition={sidebarPosition}
/>
);
}

View File

@@ -0,0 +1,42 @@
import type { ReactNode } from 'react';
import { CorporateLayout } from '../layouts';
export interface BusinessTemplateProps {
header?: ReactNode;
navigation?: ReactNode;
hero?: ReactNode;
features?: ReactNode;
services?: ReactNode;
testimonials?: ReactNode;
cta?: ReactNode;
footer?: ReactNode;
}
export function BusinessTemplate({
header,
navigation,
hero,
features,
services,
testimonials,
cta,
footer
}: BusinessTemplateProps) {
return (
<CorporateLayout
header={header}
navigation={navigation}
mainContent={
<>
{hero}
{features}
{services}
{testimonials}
{cta}
</>
}
footer={footer}
/>
);
}

View File

@@ -0,0 +1,36 @@
import type { ReactNode } from 'react';
import { EcommerceLayout } from '../layouts';
export interface EcommerceTemplateProps {
header?: ReactNode;
navigation?: ReactNode;
sidebar?: ReactNode;
products?: ReactNode;
featured?: ReactNode;
footer?: ReactNode;
}
export function EcommerceTemplate({
header,
navigation,
sidebar,
products,
featured,
footer
}: EcommerceTemplateProps) {
return (
<EcommerceLayout
header={header}
navigation={navigation}
sidebar={sidebar}
mainContent={
<>
{featured}
{products}
</>
}
footer={footer}
/>
);
}

View File

@@ -0,0 +1,36 @@
import type { ReactNode } from 'react';
import { PortfolioLayout } from '../layouts';
export interface PortfolioTemplateProps {
header?: ReactNode;
gallery?: ReactNode;
about?: ReactNode;
contact?: ReactNode;
footer?: ReactNode;
fullWidth?: boolean;
}
export function PortfolioTemplate({
header,
gallery,
about,
contact,
footer,
fullWidth = false
}: PortfolioTemplateProps) {
return (
<PortfolioLayout
header={header}
mainContent={
<>
{gallery}
{about}
{contact}
</>
}
footer={footer}
fullWidth={fullWidth}
/>
);
}

View File

@@ -2,5 +2,12 @@ export { MarketingTemplate } from './MarketingTemplate';
export type { MarketingTemplateProps } from './MarketingTemplate';
export { LandingTemplate } from './LandingTemplate';
export type { LandingTemplateProps } from './LandingTemplate';
export { BlogTemplate } from './BlogTemplate';
export type { BlogTemplateProps } from './BlogTemplate';
export { BusinessTemplate } from './BusinessTemplate';
export type { BusinessTemplateProps } from './BusinessTemplate';
export { PortfolioTemplate } from './PortfolioTemplate';
export type { PortfolioTemplateProps } from './PortfolioTemplate';
export { EcommerceTemplate } from './EcommerceTemplate';
export type { EcommerceTemplateProps } from './EcommerceTemplate';