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,27 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: [
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/addon-a11y",
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
typescript: {
|
||||
reactDocgen: "react-docgen-typescript",
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
propFilter: (prop) =>
|
||||
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
|
||||
},
|
||||
},
|
||||
staticDirs: ["../public"],
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,3 @@
|
||||
/* Override le fond Storybook pour utiliser nos tokens */
|
||||
body { font-family: var(--mmg-font-sans); }
|
||||
.docs-story { background: var(--mmg-color-bg-page); }
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
import "@managemate/css";
|
||||
import "@managemate/icons";
|
||||
import "./preview.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
layout: "padded",
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
backgrounds: {
|
||||
default: "page",
|
||||
values: [
|
||||
{ name: "page", value: "var(--mmg-color-bg-page)" },
|
||||
{ name: "surface", value: "var(--mmg-color-bg-surface)" },
|
||||
{ name: "white", value: "#FFFFFF" },
|
||||
{ name: "dark", value: "#0B0D14" },
|
||||
],
|
||||
},
|
||||
a11y: {
|
||||
// axe-core configuration. WCAG 2.1 AA + RGAA tags.
|
||||
config: {
|
||||
rules: [
|
||||
{ id: "color-contrast", enabled: true },
|
||||
{ id: "label", enabled: true },
|
||||
{ id: "button-name", enabled: true },
|
||||
],
|
||||
},
|
||||
options: {
|
||||
runOnly: {
|
||||
type: "tag",
|
||||
values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "best-practice"],
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
storySort: {
|
||||
order: [
|
||||
"Intro",
|
||||
"Tokens",
|
||||
["Colors", "Spacing", "Typography", "Shadows", "Motion"],
|
||||
"Layout",
|
||||
"Forms",
|
||||
["Button", "Input", "Select", "Checkbox", "Radio", "Switch"],
|
||||
"Feedback",
|
||||
"Overlays",
|
||||
"Navigation",
|
||||
"Data display",
|
||||
"Advanced",
|
||||
"Theming",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
globalTypes: {
|
||||
theme: {
|
||||
description: "Thème global",
|
||||
defaultValue: "light",
|
||||
toolbar: {
|
||||
title: "Thème",
|
||||
icon: "circlehollow",
|
||||
items: [
|
||||
{ value: "light", title: "Light" },
|
||||
{ value: "dark", title: "Dark" },
|
||||
{ value: "system", title: "Système" },
|
||||
],
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
accent: {
|
||||
description: "Couleur d'accent (preset DSMMG)",
|
||||
defaultValue: "synapse",
|
||||
toolbar: {
|
||||
title: "Accent",
|
||||
icon: "paintbrush",
|
||||
items: [
|
||||
{ value: "synapse", title: "Synapse (rose)" },
|
||||
{ value: "rose", title: "Rose vif" },
|
||||
{ value: "blue", title: "Bleu" },
|
||||
{ value: "violet", title: "Violet" },
|
||||
{ value: "green", title: "Vert" },
|
||||
{ value: "amber", title: "Ambre" },
|
||||
{ value: "red", title: "Rouge" },
|
||||
{ value: "cyan", title: "Cyan" },
|
||||
{ value: "slate", title: "Ardoise" },
|
||||
],
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
density: {
|
||||
description: "Densité",
|
||||
defaultValue: "comfortable",
|
||||
toolbar: {
|
||||
title: "Densité",
|
||||
icon: "component",
|
||||
items: [
|
||||
{ value: "comfortable", title: "Comfortable" },
|
||||
{ value: "cozy", title: "Cozy" },
|
||||
{ value: "compact", title: "Compact" },
|
||||
],
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story, ctx) => {
|
||||
const root = document.documentElement;
|
||||
// Theme
|
||||
if (ctx.globals.theme === "system") root.removeAttribute("data-mmg-theme");
|
||||
else root.setAttribute("data-mmg-theme", ctx.globals.theme);
|
||||
// Accent
|
||||
if (ctx.globals.accent === "synapse") root.removeAttribute("data-mmg-accent");
|
||||
else root.setAttribute("data-mmg-accent", ctx.globals.accent);
|
||||
// Density
|
||||
if (ctx.globals.density === "comfortable") root.removeAttribute("data-mmg-density");
|
||||
else root.setAttribute("data-mmg-density", ctx.globals.density);
|
||||
return Story();
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "storybook",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "storybook dev -p 6006",
|
||||
"build": "storybook build",
|
||||
"preview": "vite preview --outDir storybook-static"
|
||||
},
|
||||
"dependencies": {
|
||||
"@managemate/css": "workspace:*",
|
||||
"@managemate/icons": "workspace:*",
|
||||
"@managemate/react": "workspace:*",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-a11y": "^8.5.0",
|
||||
"@storybook/addon-essentials": "^8.5.0",
|
||||
"@storybook/addon-interactions": "^8.5.0",
|
||||
"@storybook/blocks": "^8.5.0",
|
||||
"@storybook/react": "^8.5.0",
|
||||
"@storybook/react-vite": "^8.5.0",
|
||||
"@storybook/test": "^8.5.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"storybook": "^8.5.0",
|
||||
"typescript": "~5.7.0",
|
||||
"vite": "^5.4.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Forms/Button",
|
||||
component: Button,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Bouton DSMMG avec variants Material 3 / Fluent. Pill par défaut. Tous les états (default, hover, active, focus-visible, disabled, loading) sont stylés. Touch target 44×44 garanti pour `icon-only`.",
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["primary", "tonal", "secondary", "tertiary", "ghost", "elevated", "danger", "success"],
|
||||
},
|
||||
size: { control: "select", options: ["xs", "sm", "md", "lg", "xl"] },
|
||||
loading: { control: "boolean" },
|
||||
disabled: { control: "boolean" },
|
||||
block: { control: "boolean" },
|
||||
},
|
||||
args: { children: "Continuer", variant: "primary", size: "md" },
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary: Story = {};
|
||||
|
||||
export const Tonal: Story = { args: { variant: "tonal" } };
|
||||
export const Secondary: Story = { args: { variant: "secondary" } };
|
||||
export const Tertiary: Story = { args: { variant: "tertiary" } };
|
||||
export const Ghost: Story = { args: { variant: "ghost" } };
|
||||
export const Elevated: Story = { args: { variant: "elevated" } };
|
||||
export const Danger: Story = { args: { variant: "danger", children: "Supprimer" } };
|
||||
export const Success: Story = { args: { variant: "success", children: "Valider" } };
|
||||
|
||||
export const Loading: Story = { args: { loading: true } };
|
||||
export const Disabled: Story = { args: { disabled: true } };
|
||||
export const Block: Story = { args: { block: true } };
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: { icon: "arrow-right-line", iconPosition: "right" },
|
||||
};
|
||||
|
||||
export const IconOnly: Story = {
|
||||
args: { icon: "settings-3-line", "aria-label": "Paramètres", children: undefined },
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render: (args) => (
|
||||
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
||||
<Button {...args} size="xs">XS</Button>
|
||||
<Button {...args} size="sm">Small</Button>
|
||||
<Button {...args} size="md">Medium</Button>
|
||||
<Button {...args} size="lg">Large</Button>
|
||||
<Button {...args} size="xl">Extra</Button>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="tonal">Tonal</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="elevated">Elevated</Button>
|
||||
<Button variant="danger">Danger</Button>
|
||||
<Button variant="success">Success</Button>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Combobox } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Forms/Combobox",
|
||||
component: Combobox,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Combobox WAI-ARIA 1.2 (combobox + listbox). Floating UI pour positionnement, aria-activedescendant pour le focus virtuel sur l'option courante.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Combobox>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const COUNTRIES = [
|
||||
{ value: "fr", label: "France" },
|
||||
{ value: "be", label: "Belgique" },
|
||||
{ value: "ch", label: "Suisse" },
|
||||
{ value: "ca", label: "Canada" },
|
||||
{ value: "lu", label: "Luxembourg" },
|
||||
{ value: "ma", label: "Maroc" },
|
||||
{ value: "tn", label: "Tunisie" },
|
||||
{ value: "sn", label: "Sénégal" },
|
||||
{ value: "ci", label: "Côte d'Ivoire" },
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [val, setVal] = useState<string | undefined>();
|
||||
return (
|
||||
<div style={{ width: 320 }}>
|
||||
<Combobox label="Pays" options={COUNTRIES} value={val} onChange={setVal} />
|
||||
<p style={{ marginTop: 12, fontSize: 13, color: "var(--mmg-color-text-tertiary)" }}>
|
||||
Sélectionné : <strong>{val ?? "—"}</strong>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Dialog, ConfirmDialog, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Overlays/Dialog",
|
||||
component: Dialog,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Dialog>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Ouvrir le dialog</Button>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title="Modifier l'utilisateur"
|
||||
description="Mettez à jour les informations de cet utilisateur. Les changements sont sauvegardés immédiatement."
|
||||
footer={
|
||||
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||
<Button variant="ghost" onClick={() => setOpen(false)}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => setOpen(false)}>
|
||||
Sauvegarder
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>Contenu du dialog…</p>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Confirm: Story = {
|
||||
name: "ConfirmDialog (destructive)",
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button variant="danger" onClick={() => setOpen(true)}>
|
||||
Supprimer le compte
|
||||
</Button>
|
||||
<ConfirmDialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
destructive
|
||||
title="Supprimer définitivement ?"
|
||||
description="Cette action est irréversible. Toutes les données associées à ce compte seront perdues."
|
||||
confirmLabel="Supprimer définitivement"
|
||||
onConfirm={() => alert("Confirmé")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
|
||||
<Meta title="Intro" />
|
||||
|
||||
# DSMMG — Design System ManageMate Group
|
||||
|
||||
Référence pour designers et développeurs des produits ManageMate (Synapse, HRTime, Forge, Orbit, MSLM, Espace-Client, sites publics).
|
||||
|
||||
## Principes
|
||||
|
||||
1. **Accessibilité non-négociable** — RGAA 4.1 / WCAG 2.2 AA, validé via axe-core en CI sur chaque composant.
|
||||
2. **Headless-first** — comportement (focus, keyboard, ARIA) découplé du style. Primitives Radix UI / Floating UI dessous.
|
||||
3. **Tokens partout** — aucun hex en dur, aucun `--mmg-color-synapse` direct. Tout passe par les sémantiques (`--mmg-color-text-primary`, `--mmg-color-bg-surface`) et l'accent (`--mmg-color-accent`).
|
||||
4. **Tree-shakable** — un composant = un fichier. `sideEffects: false`. Les imports inutilisés sont droppés par les bundlers consommateurs.
|
||||
|
||||
## Theming utilisateur
|
||||
|
||||
Chaque utilisateur peut choisir son accent (rose Synapse par défaut). 9 presets validés AA :
|
||||
- `synapse` (rose corporate, défaut)
|
||||
- `rose`, `blue`, `violet`, `green`, `amber`, `red`, `cyan`, `slate`
|
||||
|
||||
Le picker change en live ici (toolbar Storybook → "Accent"). Voir aussi le composant **`<ThemePicker />`** dans `Theming`.
|
||||
|
||||
## Density
|
||||
|
||||
3 modes adaptables via `[data-mmg-density]` :
|
||||
- `comfortable` (défaut, public)
|
||||
- `cozy` (apps métier)
|
||||
- `compact` (power users)
|
||||
|
||||
## Standards
|
||||
|
||||
- shadcn/ui + Radix UI (architecture, primitives a11y)
|
||||
- Linear, Notion, Vercel, Stripe (références SaaS dense)
|
||||
- DSFR (méthodologie a11y)
|
||||
- Atlassian DS, IBM Carbon (apps enterprise)
|
||||
|
||||
## Conventions de naming
|
||||
|
||||
| Catégorie | Préfixe |
|
||||
|---|---|
|
||||
| Tokens couleur | `--mmg-color-*` |
|
||||
| Tokens taille | `--mmg-space-*`, `--mmg-radius-*`, `--mmg-font-size-*` |
|
||||
| Tokens motion | `--mmg-duration-*`, `--mmg-ease-*` |
|
||||
| Classes CSS | `mmg-<comp>` |
|
||||
| Classes utilitaires | `mmg-u-*` |
|
||||
| Variantes BEM | `mmg-<comp>--<variant>` |
|
||||
| Sous-éléments BEM | `mmg-<comp>__<part>` |
|
||||
|
||||
## Navigation
|
||||
|
||||
- **Tokens** — primitives, sémantiques, accent, motion
|
||||
- **Forms** — Button, Input, Select, Checkbox, Radio, Switch, Combobox
|
||||
- **Layout** — Container, Stack, Grid, Card, Tile
|
||||
- **Overlays** — Tooltip, Popover, Menu, Dialog (tous Radix-backed)
|
||||
- **Navigation** — Header, Footer, Breadcrumb, Tabs, Pagination, AppShell
|
||||
- **Data display** — DataTable, Stat, Avatar, Tag, Badge, Progress
|
||||
- **Theming** — ThemePicker, dark mode toggle
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ThemePicker, Button, Card } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Theming/ThemePicker",
|
||||
component: ThemePicker,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Sélecteur de couleur d'accent utilisateur. Pattern radiogroup avec navigation flèches, persistance localStorage. 9 presets validés AA.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof ThemePicker>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <ThemePicker />,
|
||||
};
|
||||
|
||||
export const InCard: Story = {
|
||||
render: () => (
|
||||
<Card style={{ maxWidth: 420 }}>
|
||||
<h3>Préférences d'apparence</h3>
|
||||
<p style={{ color: "var(--mmg-color-text-tertiary)", fontSize: "var(--mmg-font-size-sm)", marginBottom: 16 }}>
|
||||
Choisissez la couleur d'accent qui vous convient. Cette préférence est sauvegardée localement.
|
||||
</p>
|
||||
<ThemePicker legend="Couleur d'accent" />
|
||||
<div style={{ marginTop: 24, display: "flex", gap: 8 }}>
|
||||
<Button variant="primary">Bouton primary</Button>
|
||||
<Button variant="tonal">Bouton tonal</Button>
|
||||
</div>
|
||||
</Card>
|
||||
),
|
||||
};
|
||||
|
||||
export const HiddenReset: Story = {
|
||||
render: () => <ThemePicker hideReset />,
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Tokens/Colors",
|
||||
parameters: {
|
||||
layout: "padded",
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Tokens couleur DSMMG. Les sémantiques se réfèrent aux primitives — c'est ce que les composants consomment. L'accent est user-themable via `[data-mmg-accent]`.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj;
|
||||
|
||||
const Swatch = ({ name, css }: { name: string; css: string }) => (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 56,
|
||||
borderRadius: 8,
|
||||
background: `var(${css})`,
|
||||
border: "1px solid var(--mmg-color-border)",
|
||||
}}
|
||||
/>
|
||||
<div style={{ fontSize: 11, fontFamily: "var(--mmg-font-mono)", color: "var(--mmg-color-text-tertiary)" }}>
|
||||
{css}
|
||||
</div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Grid = ({ children }: { children: React.ReactNode }) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
|
||||
gap: 16,
|
||||
marginBottom: 32,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Surfaces: Story = {
|
||||
render: () => (
|
||||
<Grid>
|
||||
<Swatch name="page" css="--mmg-color-bg-page" />
|
||||
<Swatch name="surface" css="--mmg-color-bg-surface" />
|
||||
<Swatch name="raised" css="--mmg-color-bg-raised" />
|
||||
<Swatch name="muted" css="--mmg-color-bg-muted" />
|
||||
<Swatch name="subtle" css="--mmg-color-bg-subtle" />
|
||||
</Grid>
|
||||
),
|
||||
};
|
||||
|
||||
export const Borders: Story = {
|
||||
render: () => (
|
||||
<Grid>
|
||||
<Swatch name="border" css="--mmg-color-border" />
|
||||
<Swatch name="border-soft" css="--mmg-color-border-soft" />
|
||||
<Swatch name="border-strong" css="--mmg-color-border-strong" />
|
||||
</Grid>
|
||||
),
|
||||
};
|
||||
|
||||
export const Text: Story = {
|
||||
render: () => (
|
||||
<Grid>
|
||||
<Swatch name="text-primary" css="--mmg-color-text-primary" />
|
||||
<Swatch name="text-secondary" css="--mmg-color-text-secondary" />
|
||||
<Swatch name="text-tertiary" css="--mmg-color-text-tertiary" />
|
||||
<Swatch name="text-quaternary" css="--mmg-color-text-quaternary" />
|
||||
</Grid>
|
||||
),
|
||||
};
|
||||
|
||||
export const Accent: Story = {
|
||||
render: () => (
|
||||
<Grid>
|
||||
<Swatch name="accent" css="--mmg-color-accent" />
|
||||
<Swatch name="accent-hover" css="--mmg-color-accent-hover" />
|
||||
<Swatch name="accent-active" css="--mmg-color-accent-active" />
|
||||
<Swatch name="accent-soft" css="--mmg-color-accent-soft" />
|
||||
<Swatch name="accent-border" css="--mmg-color-accent-border" />
|
||||
<Swatch name="accent-strong" css="--mmg-color-accent-strong" />
|
||||
<Swatch name="accent-on" css="--mmg-color-accent-on" />
|
||||
</Grid>
|
||||
),
|
||||
};
|
||||
|
||||
export const Semantic: Story = {
|
||||
render: () => (
|
||||
<>
|
||||
<h3>Success</h3>
|
||||
<Grid>
|
||||
<Swatch name="success" css="--mmg-color-success" />
|
||||
<Swatch name="success-soft" css="--mmg-color-success-soft" />
|
||||
<Swatch name="success-border" css="--mmg-color-success-border" />
|
||||
<Swatch name="success-strong" css="--mmg-color-success-strong" />
|
||||
</Grid>
|
||||
<h3>Warning</h3>
|
||||
<Grid>
|
||||
<Swatch name="warning" css="--mmg-color-warning" />
|
||||
<Swatch name="warning-soft" css="--mmg-color-warning-soft" />
|
||||
<Swatch name="warning-border" css="--mmg-color-warning-border" />
|
||||
<Swatch name="warning-strong" css="--mmg-color-warning-strong" />
|
||||
</Grid>
|
||||
<h3>Danger</h3>
|
||||
<Grid>
|
||||
<Swatch name="danger" css="--mmg-color-danger" />
|
||||
<Swatch name="danger-soft" css="--mmg-color-danger-soft" />
|
||||
<Swatch name="danger-border" css="--mmg-color-danger-border" />
|
||||
<Swatch name="danger-strong" css="--mmg-color-danger-strong" />
|
||||
</Grid>
|
||||
<h3>Info</h3>
|
||||
<Grid>
|
||||
<Swatch name="info" css="--mmg-color-info" />
|
||||
<Swatch name="info-soft" css="--mmg-color-info-soft" />
|
||||
<Swatch name="info-border" css="--mmg-color-info-border" />
|
||||
<Swatch name="info-strong" css="--mmg-color-info-strong" />
|
||||
</Grid>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
const palette = (name: string, label: string) => {
|
||||
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
return (
|
||||
<>
|
||||
<h3>{label}</h3>
|
||||
<Grid>
|
||||
{shades.map((s) => (
|
||||
<Swatch key={s} name={`${name}-${s}`} css={`--mmg-color-${name}-${s}`} />
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Palettes: Story = {
|
||||
render: () => (
|
||||
<>
|
||||
{palette("synapse", "Synapse (default)")}
|
||||
{palette("rose", "Rose")}
|
||||
{palette("blue", "Blue")}
|
||||
{palette("violet", "Violet")}
|
||||
{palette("green", "Green")}
|
||||
{palette("amber", "Amber")}
|
||||
{palette("red", "Red")}
|
||||
{palette("cyan", "Cyan")}
|
||||
{palette("slate", "Slate")}
|
||||
</>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Tooltip, TooltipProvider, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Overlays/Tooltip",
|
||||
component: Tooltip,
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<TooltipProvider>
|
||||
<Story />
|
||||
</TooltipProvider>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Tooltip basé sur Radix UI. Auto-flip, focus management, Escape, prefers-reduced-motion. Exige `<TooltipProvider>` à la racine.",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Tooltip>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: { content: "Action sécurisée" },
|
||||
render: (args) => (
|
||||
<Tooltip {...args}>
|
||||
<Button variant="ghost">Survoler ou focus</Button>
|
||||
</Tooltip>
|
||||
),
|
||||
};
|
||||
|
||||
export const Placements: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 24, justifyContent: "center", padding: 80 }}>
|
||||
{(["top", "right", "bottom", "left"] as const).map((p) => (
|
||||
<Tooltip key={p} content={`Position ${p}`} placement={p}>
|
||||
<Button variant="ghost">{p}</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user