Files
DSMMG/packages/react/src/Layout.tsx
T
Dinawo 62317f2ad7
Release / Release / open changeset PR (push) Has been cancelled
CI / Build, typecheck, test, a11y (push) Has been cancelled
chore: initial DSMMG v0.2 — refonte architecturale complète
Mise en place du Design System ManageMate Group v0.2 — refonte du
système de tokens (préfixe --mmg-color-*), 9 presets accent
user-themable validés WCAG AA, overlays Radix UI + Floating UI,
Storybook 8 + Vitest + axe-core en CI, doc Astro Starlight,
DESIGN.md (format google-labs-code) et exports tokens DTCG/CSS/
TS/Figma/Tailwind v3 et v4.

- 4 packages monorepo pnpm : @managemate/{tokens,css,react,icons}
- 62 composants React headless-first (Sheet, HoverCard, ContextMenu,
  Slider, ToggleGroup, AvatarGroup, UserCard, ProfileHeader,
  MetricCard, PricingCard, FeatureCard, Text/Display/Eyebrow/Lead…)
- Lint contraste WCAG : 37/37 paires AA, branché CI
- Toast pile Sonner-style avec ResizeObserver
- Theming user (9 presets) sans casser sémantique fixe
- Identité Synapse (rose #D12B6A) préservée

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:08:38 +02:00

226 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { HTMLAttributes, ReactNode, AnchorHTMLAttributes, ElementType } from "react";
import { cx } from "./utils";
import { Icon, type IconName } from "./Icon";
import { IconBlock, type IconBlockProps } from "./IconBlock";
import { Pictogram, type PictogramName } from "./Pictogram";
/* — Container ————————————————————————————— */
export type ContainerProps = HTMLAttributes<HTMLDivElement> & {
variant?: "default" | "narrow" | "wide" | "fluid";
as?: ElementType;
};
export function Container({
variant = "default",
as: As = "div",
className,
children,
...rest
}: ContainerProps) {
const Tag = As as ElementType;
return (
<Tag
className={cx(
"mmg-container",
variant !== "default" && `mmg-container--${variant}`,
className,
)}
{...rest}
>
{children}
</Tag>
);
}
/* — Section ————————————————————————————— */
export type SectionProps = HTMLAttributes<HTMLElement> & {
size?: "sm" | "md" | "lg";
variant?: "default" | "surface" | "muted" | "brand-soft";
};
export function Section({
size = "md",
variant = "default",
className,
children,
...rest
}: SectionProps) {
return (
<section
className={cx(
"mmg-section",
size !== "md" && `mmg-section--${size}`,
variant !== "default" && `mmg-section--${variant}`,
className,
)}
{...rest}
>
{children}
</section>
);
}
/* — Stack & Inline ————————————————————————————— */
export function Stack({
gap = "md",
className,
children,
...rest
}: HTMLAttributes<HTMLDivElement> & { gap?: "xs" | "sm" | "md" | "lg" | "xl" }) {
return (
<div className={cx("mmg-stack", `mmg-stack--${gap}`, className)} {...rest}>
{children}
</div>
);
}
export function Inline({
align,
className,
children,
...rest
}: HTMLAttributes<HTMLDivElement> & { align?: "end" | "between" | "center" }) {
return (
<div
className={cx("mmg-inline", align && `mmg-inline--${align}`, className)}
{...rest}
>
{children}
</div>
);
}
/* — Card ————————————————————————————— */
export function Card({
raised,
flat,
noPadding,
className,
children,
...rest
}: HTMLAttributes<HTMLDivElement> & {
raised?: boolean;
flat?: boolean;
noPadding?: boolean;
}) {
return (
<div
className={cx(
"mmg-card",
raised && "mmg-card--raised",
flat && "mmg-card--flat",
noPadding && "mmg-card--no-padding",
className,
)}
{...rest}
>
{children}
</div>
);
}
Card.Header = function CardHeader({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>) {
return <div className={cx("mmg-card__header", className)} {...rest}>{children}</div>;
};
Card.Title = function CardTitle({ children, className, ...rest }: HTMLAttributes<HTMLHeadingElement>) {
return <h3 className={cx("mmg-card__title", className)} {...rest}>{children}</h3>;
};
Card.Desc = function CardDesc({ children, className, ...rest }: HTMLAttributes<HTMLParagraphElement>) {
return <p className={cx("mmg-card__desc", className)} {...rest}>{children}</p>;
};
Card.Footer = function CardFooter({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>) {
return <div className={cx("mmg-card__footer", className)} {...rest}>{children}</div>;
};
/* — Tile ————————————————————————————— */
export type TileProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
title: ReactNode;
desc?: ReactNode;
/** Petite icône carrée (style basique, 48×48). */
icon?: IconName;
/** IconBlock — pattern recommandé : icône fill colorée sur fond rond. */
iconBlock?: IconName;
/** Couleur sémantique de l'IconBlock. */
iconColor?: IconBlockProps["color"];
/** Variante visuelle de l'IconBlock. */
iconVariant?: IconBlockProps["variant"];
/** Pictogramme illustré (legacy v0.2). */
pictogram?: PictogramName;
/** Disposition horizontale (icône à gauche). */
horizontal?: boolean;
/** Cache la flèche d'invitation. */
noArrow?: boolean;
/** Taille — sm (compact), md (défaut), lg (hero). */
size?: "sm" | "md" | "lg";
};
export function Tile({
title,
desc,
icon,
iconBlock,
iconColor = "brand",
iconVariant = "soft",
pictogram,
horizontal,
noArrow,
size = "md",
className,
children,
...rest
}: TileProps) {
const blockSize = size === "sm" ? "sm" : size === "lg" ? "lg" : "md";
return (
<a
className={cx(
"mmg-tile",
size !== "md" && `mmg-tile--${size}`,
horizontal && "mmg-tile--horizontal",
className,
)}
{...rest}
>
{iconBlock ? (
<IconBlock icon={iconBlock} color={iconColor} variant={iconVariant} size={blockSize} />
) : pictogram ? (
<Pictogram name={pictogram} className="mmg-tile__pictogram" />
) : icon ? (
<span className="mmg-tile__icon">
<Icon name={icon} />
</span>
) : null}
<div className="mmg-stack mmg-stack--xs" style={{ flex: 1 }}>
<span className="mmg-tile__title">
{title}
{!noArrow && (
<span className="mmg-tile__arrow" aria-hidden>
<Icon name="arrow-right-line" size="md" />
</span>
)}
</span>
{desc && <span className="mmg-tile__desc">{desc}</span>}
{children}
</div>
</a>
);
}
/* — Hero ————————————————————————————— */
export function Hero({
title,
lead,
actions,
className,
...rest
}: HTMLAttributes<HTMLElement> & {
title: ReactNode;
lead?: ReactNode;
actions?: ReactNode;
}) {
return (
<section className={cx("mmg-hero", className)} {...rest}>
<div className="mmg-container">
<h1 className="mmg-hero__title">{title}</h1>
{lead && <p className="mmg-hero__lead">{lead}</p>}
{actions && <div className="mmg-inline">{actions}</div>}
</div>
</section>
);
}