chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export * from "./page-templates.tsx";
@@ -0,0 +1,268 @@
import type { ReactNode } from "react";
import { PageHeader } from "../components/page-header.tsx";
import { Button } from "../components/ui/button.ts";
import { useLocale } from "../i18n/index.ts";
export type PageHeading = Readonly<{
title: string;
description?: string;
eyebrow?: string;
}>;
export type PageActionDefinition =
| Readonly<{
kind: "button";
label: string;
onAction(): void;
disabled?: boolean;
variant?: "primary" | "secondary" | "danger" | "ghost";
}>
| Readonly<{
kind: "link";
label: string;
href: string;
variant?: "primary" | "secondary" | "danger" | "ghost";
}>;
export type PageTemplateSlots = Readonly<{
heading: PageHeading;
breadcrumb?: ReactNode;
status?: ReactNode;
actions?: readonly PageActionDefinition[];
notices?: ReactNode;
children?: ReactNode;
aside?: ReactNode;
feedback?: ReactNode;
}>;
export function StandardPage(props: PageTemplateSlots) {
const { message } = useLocale();
return (
<article className="ui-page page-template page-template--standard">
{props.breadcrumb ? (
<nav
className="page-template__breadcrumb"
aria-label={message("template.breadcrumb")}
>
{props.breadcrumb}
</nav>
) : null}
<div className="page-template__heading">
<PageHeader {...props.heading} />
{props.status ? (
<div className="page-template__status">{props.status}</div>
) : null}
{props.actions?.length ? (
<PageActionBar actions={props.actions} />
) : null}
</div>
{props.notices ? (
<div className="page-template__notices">{props.notices}</div>
) : null}
{props.feedback ? (
<div className="page-template__feedback">{props.feedback}</div>
) : null}
<div
className="page-template__layout"
data-has-aside={props.aside ? "true" : "false"}
>
<div className="page-template__content">{props.children}</div>
{props.aside ? (
<aside
className="page-template__aside"
aria-label={message("template.relatedInformation")}
>
{props.aside}
</aside>
) : null}
</div>
</article>
);
}
export function CollectionPage(
props: PageTemplateSlots &
Readonly<{
toolbar?: ReactNode;
activeFilters?: ReactNode;
resultCount?: ReactNode;
bulkActions?: ReactNode;
pagination?: ReactNode;
}>,
) {
const { message } = useLocale();
return (
<StandardPage
{...props}
notices={
<>
{props.notices}
{props.toolbar ? (
<section
className="collection-page__toolbar"
aria-label={message("template.searchAndFilter")}
>
{props.toolbar}
</section>
) : null}
{props.activeFilters ? (
<div className="collection-page__active-filters">
{props.activeFilters}
</div>
) : null}
{props.resultCount ? (
<div className="collection-page__result-count" role="status">
{props.resultCount}
</div>
) : null}
{props.bulkActions ? (
<div className="collection-page__bulk-actions">
{props.bulkActions}
</div>
) : null}
</>
}
>
<div className="collection-page__results">{props.children}</div>
{props.pagination ? (
<nav
className="collection-page__pagination"
aria-label={message("template.pagination")}
>
{props.pagination}
</nav>
) : null}
</StandardPage>
);
}
export function DetailPage(
props: PageTemplateSlots &
Readonly<{
metadata?: ReactNode;
destructiveAction?: ReactNode;
}>,
) {
const { message } = useLocale();
return (
<StandardPage {...props}>
{props.metadata ? (
<section
className="detail-page__metadata"
aria-label={message("template.summary")}
>
{props.metadata}
</section>
) : null}
<div className="detail-page__sections">{props.children}</div>
{props.destructiveAction ? (
<section
className="detail-page__danger"
aria-label={message("template.dangerActions")}
>
{props.destructiveAction}
</section>
) : null}
</StandardPage>
);
}
export function FormPage(
props: PageTemplateSlots &
Readonly<{
errorSummary?: ReactNode;
fields?: ReactNode;
formActions?: ReactNode;
guard?: ReactNode;
}>,
) {
return (
<StandardPage {...props}>
{props.errorSummary ? (
<div className="form-page__error-summary">{props.errorSummary}</div>
) : null}
<div className="form-page__fields">{props.fields ?? props.children}</div>
{props.formActions ? (
<div className="form-page__actions">{props.formActions}</div>
) : null}
{props.guard}
</StandardPage>
);
}
export type StatusPageVariant =
| "unauthenticated"
| "forbidden"
| "not-found"
| "unavailable"
| "offline"
| "maintenance"
| "unexpected";
export function StatusPage(
props: Readonly<{
variant: StatusPageVariant;
heading: PageHeading;
primaryAction?: PageActionDefinition;
secondaryAction?: PageActionDefinition;
supportReference?: string;
}>,
) {
const { message } = useLocale();
return (
<section
className={`ui-page page-template status-page status-page--${props.variant}`}
data-status-variant={props.variant}
>
<PageHeader {...props.heading} />
{props.primaryAction || props.secondaryAction ? (
<PageActionBar
className="status-page__actions"
actions={
[props.primaryAction, props.secondaryAction].filter(
Boolean,
) as PageActionDefinition[]
}
/>
) : null}
{props.supportReference ? (
<p className="status-page__support">
{message("template.supportReference", {
reference: props.supportReference,
})}
</p>
) : null}
</section>
);
}
function PageActionBar(props: Readonly<{
actions: readonly PageActionDefinition[];
className?: string;
}>) {
return (
<div className={props.className ?? "page-template__actions"}>
{props.actions.map((action) =>
action.kind === "link" ? (
<a
className={`ui-button ui-button--${action.variant ?? "primary"}`}
href={action.href}
key={`${action.kind}:${action.label}`}
>
{action.label}
</a>
) : (
<Button
disabled={action.disabled}
key={`${action.kind}:${action.label}`}
onClick={action.onAction}
variant={action.variant}
>
{action.label}
</Button>
),
)}
</div>
);
}