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>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user