mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 18:19:39 +02:00
Add optional 'centered' prop that centers the title with back button on left and actions on right. Useful for mobile-style headers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
220 lines
5 KiB
Svelte
220 lines
5 KiB
Svelte
<script lang="ts">
|
|
/**
|
|
* PageHeader - Standardized page header layout
|
|
*
|
|
* Provides consistent page title, description, and action buttons layout.
|
|
*
|
|
* @example Basic usage
|
|
* ```svelte
|
|
* <PageHeader title="My Memos" />
|
|
* ```
|
|
*
|
|
* @example With description and actions
|
|
* ```svelte
|
|
* <PageHeader
|
|
* title="Flashcard Decks"
|
|
* description="Manage your study decks"
|
|
* >
|
|
* {#snippet actions()}
|
|
* <Button onclick={createDeck}>New Deck</Button>
|
|
* {/snippet}
|
|
* </PageHeader>
|
|
* ```
|
|
*
|
|
* @example With back navigation
|
|
* ```svelte
|
|
* <PageHeader title="Space Details" backHref="/spaces" />
|
|
* ```
|
|
*
|
|
* @example With breadcrumb and icon
|
|
* ```svelte
|
|
* <PageHeader title="Edit Profile">
|
|
* {#snippet breadcrumb()}
|
|
* <a href="/settings">Settings</a> / Profile
|
|
* {/snippet}
|
|
* {#snippet icon()}
|
|
* <UserIcon />
|
|
* {/snippet}
|
|
* </PageHeader>
|
|
* ```
|
|
*/
|
|
|
|
import type { Snippet } from 'svelte';
|
|
import { Text } from '../atoms';
|
|
|
|
type HeaderSize = 'sm' | 'md' | 'lg';
|
|
|
|
interface Props {
|
|
/** Page title */
|
|
title: string;
|
|
/** Page description/subtitle */
|
|
description?: string;
|
|
/** Header size variant */
|
|
size?: HeaderSize;
|
|
/** Whether to show bottom border */
|
|
bordered?: boolean;
|
|
/** Center the title (with actions on the right, back on the left) */
|
|
centered?: boolean;
|
|
/** Back navigation href (shows back arrow button) */
|
|
backHref?: string;
|
|
/** Icon snippet (before title) */
|
|
icon?: Snippet;
|
|
/** Breadcrumb snippet (above title) */
|
|
breadcrumb?: Snippet;
|
|
/** Actions snippet (right side) */
|
|
actions?: Snippet;
|
|
/** Tabs or navigation snippet (below header) */
|
|
tabs?: Snippet;
|
|
/** Additional CSS classes */
|
|
class?: string;
|
|
}
|
|
|
|
let {
|
|
title,
|
|
description,
|
|
size = 'md',
|
|
bordered = false,
|
|
centered = false,
|
|
backHref,
|
|
icon,
|
|
breadcrumb,
|
|
actions,
|
|
tabs,
|
|
class: className = '',
|
|
}: Props = $props();
|
|
|
|
const sizeClasses: Record<HeaderSize, { container: string; title: string }> = {
|
|
sm: {
|
|
container: 'py-3',
|
|
title: 'text-lg',
|
|
},
|
|
md: {
|
|
container: 'py-4',
|
|
title: 'text-xl',
|
|
},
|
|
lg: {
|
|
container: 'py-6',
|
|
title: 'text-2xl',
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<header
|
|
class="page-header {sizeClasses[size].container} {bordered
|
|
? 'border-b border-theme'
|
|
: ''} {className}"
|
|
>
|
|
<!-- Breadcrumb -->
|
|
{#if breadcrumb}
|
|
<div
|
|
class="page-header__breadcrumb mb-2 text-sm text-theme-secondary {centered
|
|
? 'text-center'
|
|
: ''}"
|
|
>
|
|
{@render breadcrumb()}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if centered}
|
|
<!-- Centered Layout -->
|
|
<div class="relative flex items-center justify-center min-h-[2.5rem]">
|
|
<!-- Back Button (left) -->
|
|
{#if backHref}
|
|
<a
|
|
href={backHref}
|
|
class="absolute left-0 p-1.5 rounded-lg text-theme-secondary hover:text-theme hover:bg-theme-secondary/10 transition-colors"
|
|
aria-label="Zurück"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 19l-7-7 7-7"
|
|
/>
|
|
</svg>
|
|
</a>
|
|
{/if}
|
|
|
|
<!-- Centered Title & Description -->
|
|
<div class="text-center">
|
|
{#if icon}
|
|
<div class="page-header__icon inline-block text-theme-secondary mb-1">
|
|
{@render icon()}
|
|
</div>
|
|
{/if}
|
|
<h1 class="font-semibold text-theme {sizeClasses[size].title}">
|
|
{title}
|
|
</h1>
|
|
{#if description}
|
|
<Text variant="muted" class="mt-1">
|
|
{description}
|
|
</Text>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Actions (right) -->
|
|
{#if actions}
|
|
<div class="absolute right-0 flex items-center gap-2">
|
|
{@render actions()}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<!-- Default Layout -->
|
|
<div class="flex items-center justify-between gap-4">
|
|
<div class="flex items-center gap-3 min-w-0">
|
|
<!-- Back Button -->
|
|
{#if backHref}
|
|
<a
|
|
href={backHref}
|
|
class="page-header__back flex-shrink-0 p-1.5 -ml-1.5 rounded-lg text-theme-secondary hover:text-theme hover:bg-theme-secondary/10 transition-colors"
|
|
aria-label="Zurück"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 19l-7-7 7-7"
|
|
/>
|
|
</svg>
|
|
</a>
|
|
{/if}
|
|
|
|
<!-- Icon -->
|
|
{#if icon}
|
|
<div class="page-header__icon flex-shrink-0 text-theme-secondary">
|
|
{@render icon()}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Title & Description -->
|
|
<div class="min-w-0">
|
|
<h1 class="font-semibold text-theme {sizeClasses[size].title} truncate">
|
|
{title}
|
|
</h1>
|
|
{#if description}
|
|
<Text variant="muted" class="mt-1">
|
|
{description}
|
|
</Text>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
{#if actions}
|
|
<div class="page-header__actions flex-shrink-0 flex items-center gap-2">
|
|
{@render actions()}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Tabs/Navigation -->
|
|
{#if tabs}
|
|
<div class="page-header__tabs mt-4">
|
|
{@render tabs()}
|
|
</div>
|
|
{/if}
|
|
</header>
|