feat(v1): bloquants release v1 — tests, stories, visual regression, gouvernance, publishing
6 chantiers v1 sur 7 livrés (DataTable refonte reportée car nécessite 2-3j en propre — TanStack Table + virtualisation + filter builder). v1-A — Tests (4 → 22 fichiers) : - Avatar, AvatarGroup, UserCard, MetricCard, ProfileHeader, Tooltip, Sheet, Drawer, Slider, ToggleGroup, Tabs, Pagination, Accordion, Switch, Badge, ConfirmDialog, Popover, Menu, Text, PricingCard, FeatureCard, Toast — chacun avec render + clavier + axe-core. v1-B — Storybook (7 → 23 fichiers) : - Avatar, UserCard, ProfileHeader, MetricCard, PricingCard, FeatureCard, Sheet (4 sides), HoverCard, Slider, ToggleGroup, Menu+ContextMenu, Toast (avec démo "Empiler 5"), Tabs, Pagination, Accordion, Badge. v1-D — Visual regression Playwright : - playwright.config.ts (light + dark, threshold strict 0.2) - e2e/visual.spec.ts (20 stories critiques) - Step CI + upload report en cas de fail v1-E — Site doc Starlight rempli : - 11 pages composants détaillées (Button, Input, Tooltip, Dialog, Toast, Avatar, ThemePicker, MetricCard, PricingCard, ToggleGroup, Slider) avec API, anatomie, do/don't, A11y. v1-F — Publishing Verdaccio : - verdaccio/config.yaml, docker-compose.verdaccio.yml, .npmrc - README setup local + déploiement prod + backups + sécurité v1-G — Gouvernance : - LICENSE, CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md - CODEOWNERS, PR template, 3 issue templates (bug/feature/rfc) Bug fix bonus : tooltip dark mode (text-primary comme bg + text-inverse comme texte → blanc-sur-blanc invisible). Remplacé par neutral-900/0 en light + bg-raised/text-primary en dark. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Accordion, Stack } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Disclosure/Accordion",
|
||||
component: Accordion,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Accordion>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Stack>
|
||||
<Accordion label="Combien coûte un abonnement Pro ?">
|
||||
19 €/utilisateur/mois engagement annuel. 21 €/mois sans engagement.
|
||||
</Accordion>
|
||||
<Accordion label="Puis-je migrer depuis mon outil actuel ?">
|
||||
Oui, depuis Sage, Cegid, ADP, Lucca ou via fichier CSV. Compter 7 jours.
|
||||
</Accordion>
|
||||
<Accordion label="Êtes-vous conformes RGPD ?" defaultOpen>
|
||||
Hébergement France, chiffrement AES-256, audit ANSSI annuel. Détails sur notre page sécurité.
|
||||
</Accordion>
|
||||
</Stack>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Avatar, AvatarGroup } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Data display/Avatar",
|
||||
component: Avatar,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Avatar>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Initials: Story = { args: { initials: "MD", alt: "Marie Dupont" } };
|
||||
export const Image: Story = { args: { src: "https://i.pravatar.cc/96?u=md", alt: "Marie" } };
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
|
||||
<Avatar initials="XS" alt="x" size="xs" />
|
||||
<Avatar initials="SM" alt="x" size="sm" />
|
||||
<Avatar initials="MD" alt="x" size="md" />
|
||||
<Avatar initials="LG" alt="x" size="lg" />
|
||||
<Avatar initials="XL" alt="x" size="xl" />
|
||||
<Avatar initials="2X" alt="x" size="2xl" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
export const Status: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 16 }}>
|
||||
<Avatar initials="MD" alt="online" status="online" size="lg" />
|
||||
<Avatar initials="JM" alt="away" status="away" size="lg" />
|
||||
<Avatar initials="SB" alt="busy" status="busy" size="lg" />
|
||||
<Avatar initials="TL" alt="offline" status="offline" size="lg" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
export const Square: Story = { args: { initials: "MD", alt: "x", shape: "square", size: "lg" } };
|
||||
|
||||
export const Group: Story = {
|
||||
render: () => (
|
||||
<AvatarGroup
|
||||
avatars={[
|
||||
{ initials: "MD", alt: "Marie" },
|
||||
{ initials: "JM", alt: "Jean" },
|
||||
{ initials: "SB", alt: "Sophie" },
|
||||
{ initials: "TL", alt: "Thomas" },
|
||||
{ initials: "ER", alt: "Emma" },
|
||||
]}
|
||||
max={4}
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Badge, Icon } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Data display/Badge",
|
||||
component: Badge,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Badge>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Variants: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="brand">Brand</Badge>
|
||||
<Badge variant="success">Success</Badge>
|
||||
<Badge variant="warning">Warning</Badge>
|
||||
<Badge variant="danger">Danger</Badge>
|
||||
<Badge variant="info">Info</Badge>
|
||||
<Badge variant="solid">Solid</Badge>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithDot: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<span className="mmg-badge mmg-badge--success">
|
||||
<span className="mmg-badge__dot mmg-badge__dot--pulse" />
|
||||
En ligne
|
||||
</span>
|
||||
<span className="mmg-badge mmg-badge--warning">
|
||||
<span className="mmg-badge__dot" />
|
||||
En attente
|
||||
</span>
|
||||
<span className="mmg-badge mmg-badge--danger">
|
||||
<span className="mmg-badge__dot" />
|
||||
Erreur
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<Badge variant="brand"><Icon name="star-fill" size="xs" /> Nouveau</Badge>
|
||||
<Badge variant="success"><Icon name="checkbox-circle-fill" size="xs" /> Actif</Badge>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FeatureCard } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Marketing/FeatureCard",
|
||||
component: FeatureCard,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof FeatureCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
icon: "rocket-2-fill",
|
||||
iconColor: "brand",
|
||||
title: "Onboarding éclair",
|
||||
description: "Créez un collaborateur, attribuez ses accès, déclenchez son premier cycle de paie en moins de 90 secondes.",
|
||||
link: { label: "Voir le workflow", href: "#" },
|
||||
},
|
||||
};
|
||||
|
||||
export const WithGlow: Story = {
|
||||
args: { ...(Default.args as object), glowOnHover: true },
|
||||
};
|
||||
|
||||
export const Colors: Story = {
|
||||
render: () => (
|
||||
<div className="mmg-grid mmg-grid--gap-md">
|
||||
<div className="mmg-col-4"><FeatureCard icon="rocket-2-fill" iconColor="brand" title="Brand" description="Accent rose Synapse." glowOnHover /></div>
|
||||
<div className="mmg-col-4"><FeatureCard icon="shield-check-fill" iconColor="green" title="Green" description="Sécurité, RGPD." glowOnHover /></div>
|
||||
<div className="mmg-col-4"><FeatureCard icon="line-chart-fill" iconColor="violet" title="Violet" description="Analytics." glowOnHover /></div>
|
||||
<div className="mmg-col-4"><FeatureCard icon="bank-card-fill" iconColor="blue" title="Blue" description="Finance." /></div>
|
||||
<div className="mmg-col-4"><FeatureCard icon="alert-fill" iconColor="amber" title="Amber" description="Alertes." /></div>
|
||||
<div className="mmg-col-4"><FeatureCard icon="settings-3-fill" iconColor="neutral" title="Neutral" description="Configuration." /></div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { HoverCard, Avatar, Badge, Inline } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Overlays/HoverCard",
|
||||
component: HoverCard,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof HoverCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Profile: Story = {
|
||||
render: () => (
|
||||
<HoverCard
|
||||
trigger={
|
||||
<a href="#" style={{ color: "var(--mmg-color-accent)", fontWeight: 600 }}>
|
||||
@marie.dupont
|
||||
</a>
|
||||
}
|
||||
>
|
||||
<Inline gap="md">
|
||||
<Avatar initials="MD" alt="Marie" size="lg" />
|
||||
<div>
|
||||
<div style={{ fontWeight: 700 }}>Marie Dupont</div>
|
||||
<div style={{ color: "var(--mmg-color-text-tertiary)", fontSize: "var(--mmg-font-size-sm)" }}>Lead Dev · Synapse</div>
|
||||
<div style={{ marginTop: 8, fontSize: "var(--mmg-font-size-sm)" }}>
|
||||
Lead frontend, mainteneuse du DSMMG.
|
||||
</div>
|
||||
</div>
|
||||
</Inline>
|
||||
</HoverCard>
|
||||
),
|
||||
};
|
||||
|
||||
export const Badge_: Story = {
|
||||
name: "Badge metadata",
|
||||
render: () => (
|
||||
<HoverCard trigger={<Badge variant="brand">Synapse v4.2.1</Badge>}>
|
||||
<div style={{ fontWeight: 700, marginBottom: 4 }}>Synapse v4.2.1</div>
|
||||
<div style={{ color: "var(--mmg-color-text-tertiary)", fontSize: "var(--mmg-font-size-sm)" }}>Sortie le 24 avril 2026</div>
|
||||
</HoverCard>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Menu, ContextMenu, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Overlays/Menu",
|
||||
component: Menu,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Menu>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const items = [
|
||||
{ label: "Voir le détail", icon: "eye-line" as const, shortcut: "↵" },
|
||||
{ label: "Modifier", icon: "edit-line" as const, shortcut: "E" },
|
||||
{ label: "Dupliquer", icon: "file-copy-line" as const, shortcut: "⌘D" },
|
||||
{ type: "divider" } as const,
|
||||
{ type: "label", label: "Visibilité" } as const,
|
||||
{ label: "Partager", icon: "share-line" as const },
|
||||
{ label: "Archiver", icon: "inbox-line" as const },
|
||||
{ type: "divider" } as const,
|
||||
{ label: "Supprimer", icon: "delete-bin-line" as const, danger: true, shortcut: "⌫" },
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Menu trigger={<Button variant="tertiary" icon="more-2-line">Actions</Button>} items={items} />
|
||||
),
|
||||
};
|
||||
|
||||
export const Context: Story = {
|
||||
name: "ContextMenu — clic droit",
|
||||
render: () => (
|
||||
<ContextMenu items={items}>
|
||||
<div style={{
|
||||
display: "grid", placeItems: "center", height: 140,
|
||||
border: "2px dashed var(--mmg-color-border)", borderRadius: "var(--mmg-radius-md)",
|
||||
color: "var(--mmg-color-text-tertiary)", cursor: "context-menu",
|
||||
}}>
|
||||
Clic droit ici
|
||||
</div>
|
||||
</ContextMenu>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { MetricCard, Sparkline } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Data display/MetricCard",
|
||||
component: MetricCard,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof MetricCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: { label: "MRR", value: "84 320 €", delta: "+12.4%", trend: "up", period: "vs M-1", icon: "money-euro-circle-line" },
|
||||
};
|
||||
|
||||
export const TrendDown: Story = {
|
||||
args: { label: "Tickets ouverts", value: "32", delta: "-18%", trend: "down", invertTrend: true, period: "cette semaine", icon: "error-warning-line" },
|
||||
};
|
||||
|
||||
export const Flat: Story = {
|
||||
args: { label: "NPS", value: "62", delta: "0", trend: "flat", period: "stable", icon: "star-line" },
|
||||
};
|
||||
|
||||
export const WithSparkline: Story = {
|
||||
args: {
|
||||
label: "Sessions",
|
||||
value: "12 480",
|
||||
delta: "+8.2%",
|
||||
trend: "up",
|
||||
period: "30 derniers jours",
|
||||
icon: "line-chart-line",
|
||||
sparkline: <Sparkline data={[20, 28, 25, 32, 30, 38, 42, 48]} width={200} height={48} />,
|
||||
},
|
||||
};
|
||||
|
||||
export const Grid: Story = {
|
||||
render: () => (
|
||||
<div className="mmg-grid mmg-grid--gap-md">
|
||||
<div className="mmg-col-3"><MetricCard label="MRR" value="84 320 €" delta="+12.4%" trend="up" icon="money-euro-circle-line" /></div>
|
||||
<div className="mmg-col-3"><MetricCard label="Churn" value="2.3%" delta="+0.4 pts" trend="up" invertTrend icon="arrow-go-back-line" /></div>
|
||||
<div className="mmg-col-3"><MetricCard label="NPS" value="62" delta="+4 pts" trend="up" icon="star-line" /></div>
|
||||
<div className="mmg-col-3"><MetricCard label="Tickets" value="32" delta="-18%" trend="down" invertTrend icon="error-warning-line" /></div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Pagination } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Navigation/Pagination",
|
||||
component: Pagination,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Pagination>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [page, setPage] = useState(3);
|
||||
return <Pagination page={page} pageCount={10} onChange={setPage} />;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { PricingCard, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Marketing/PricingCard",
|
||||
component: PricingCard,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof PricingCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const features = [
|
||||
{ label: "Jusqu'à 10 collaborateurs" },
|
||||
{ label: "Gestion congés" },
|
||||
{ label: "Bulletins simplifiés" },
|
||||
{ label: "Support email" },
|
||||
{ label: "API & webhooks", included: false },
|
||||
{ label: "SSO", included: false },
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: "Starter",
|
||||
description: "Pour les TPE qui démarrent.",
|
||||
price: "9 €",
|
||||
pricePeriod: "/utilisateur/mois",
|
||||
features,
|
||||
cta: <Button variant="tertiary" block>Démarrer l'essai</Button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const Highlighted: Story = {
|
||||
args: {
|
||||
name: "Pro",
|
||||
description: "Pour les PME en croissance.",
|
||||
price: "19 €",
|
||||
pricePeriod: "/utilisateur/mois",
|
||||
highlighted: true,
|
||||
badge: "Populaire",
|
||||
features: features.map((f) => ({ ...f, included: true })),
|
||||
cta: <Button variant="primary" block icon="arrow-right-line" iconPosition="right">Choisir Pro</Button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const Trio: Story = {
|
||||
render: () => (
|
||||
<div className="mmg-grid mmg-grid--gap-md">
|
||||
<div className="mmg-col-4"><PricingCard {...Default.args!} /></div>
|
||||
<div className="mmg-col-4"><PricingCard {...Highlighted.args!} /></div>
|
||||
<div className="mmg-col-4">
|
||||
<PricingCard
|
||||
name="Enterprise"
|
||||
description="Pour les organisations matures."
|
||||
price="Sur devis"
|
||||
features={[
|
||||
{ label: "Tout Pro inclus" },
|
||||
{ label: "SLA 99.95%" },
|
||||
{ label: "SSO / SAML / SCIM" },
|
||||
{ label: "Audit logs" },
|
||||
{ label: "TAM dédié" },
|
||||
]}
|
||||
cta={<Button variant="tertiary" block>Contacter sales</Button>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ProfileHeader, Badge, Button, Icon } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Profile/ProfileHeader",
|
||||
component: ProfileHeader,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof ProfileHeader>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: "Marie Dupont",
|
||||
subtitle: "Lead Developer · Synapse",
|
||||
bio: "Lead frontend chez ManageMate depuis 2020. Mainteneuse du DSMMG, passionnée d'accessibilité et de motion design fonctionnel.",
|
||||
initials: "MD",
|
||||
status: "online",
|
||||
badges: (
|
||||
<>
|
||||
<span className="mmg-badge mmg-badge--success">
|
||||
<span className="mmg-badge__dot mmg-badge__dot--pulse" />
|
||||
Disponible
|
||||
</span>
|
||||
<span className="mmg-badge mmg-badge--brand">
|
||||
<Icon name="shield-check-fill" size="xs" /> Owner DSMMG
|
||||
</span>
|
||||
<span className="mmg-badge">Frontend · React</span>
|
||||
</>
|
||||
),
|
||||
actions: (
|
||||
<>
|
||||
<Button variant="tertiary" icon="message-2-line">Message</Button>
|
||||
<Button variant="primary" icon="user-add-line">Suivre</Button>
|
||||
</>
|
||||
),
|
||||
stats: [
|
||||
{ label: "Commits", value: "184" },
|
||||
{ label: "Reviews", value: "23" },
|
||||
{ label: "Tickets résolus", value: "412" },
|
||||
{ label: "Ancienneté", value: "5 ans" },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Sheet, Button, Input, Switch } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Overlays/Sheet",
|
||||
component: Sheet,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Sheet>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
function Demo({ side }: { side: "left" | "right" | "top" | "bottom" }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button variant="primary" onClick={() => setOpen(true)}>Ouvrir ({side})</Button>
|
||||
<Sheet
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
side={side}
|
||||
title="Édition rapide"
|
||||
description="Modifiez sans quitter la liste."
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => setOpen(false)}>Annuler</Button>
|
||||
<Button variant="primary" onClick={() => setOpen(false)}>Sauvegarder</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Input label="Nom" defaultValue="Marie Dupont" />
|
||||
<Switch label="Notifier l'équipe" defaultChecked />
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const Right: Story = { render: () => <Demo side="right" /> };
|
||||
export const Left: Story = { render: () => <Demo side="left" /> };
|
||||
export const Top: Story = { render: () => <Demo side="top" /> };
|
||||
export const Bottom: Story = { render: () => <Demo side="bottom" /> };
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Slider } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Forms/Slider",
|
||||
component: Slider,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Slider>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Single: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState([62]);
|
||||
return (
|
||||
<div style={{ width: 320 }}>
|
||||
<label style={{ display: "flex", justifyContent: "space-between", marginBottom: 8, fontSize: "var(--mmg-font-size-sm)", fontWeight: 600 }}>
|
||||
<span>Volume</span>
|
||||
<span style={{ fontVariantNumeric: "tabular-nums" }}>{v[0]}%</span>
|
||||
</label>
|
||||
<Slider label="Volume" value={v} onValueChange={setV} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Range: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState([800, 2400]);
|
||||
return (
|
||||
<div style={{ width: 320 }}>
|
||||
<label style={{ display: "flex", justifyContent: "space-between", marginBottom: 8, fontSize: "var(--mmg-font-size-sm)", fontWeight: 600 }}>
|
||||
<span>Prix</span>
|
||||
<span style={{ fontVariantNumeric: "tabular-nums" }}>{v[0]} € — {v[1]} €</span>
|
||||
</label>
|
||||
<Slider label="Prix" value={v} onValueChange={setV} min={0} max={5000} step={50} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: () => (
|
||||
<div style={{ width: 320 }}>
|
||||
<Slider label="x" value={[40]} onValueChange={() => {}} disabled />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { Tabs } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Navigation/Tabs",
|
||||
component: Tabs,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Tabs>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState("a");
|
||||
return (
|
||||
<Tabs
|
||||
value={v}
|
||||
onChange={setV}
|
||||
items={[
|
||||
{ id: "a", label: "Aperçu" },
|
||||
{ id: "b", label: "Détails" },
|
||||
{ id: "c", label: "Données" },
|
||||
{ id: "d", label: "Activité" },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ToastProvider, useToast, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Feedback/Toast",
|
||||
tags: ["autodocs"],
|
||||
decorators: [(Story) => <ToastProvider position="bottom-right" max={5}><Story /></ToastProvider>],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj;
|
||||
|
||||
function Trigger() {
|
||||
const { toast } = useToast();
|
||||
return (
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<Button variant="success" onClick={() => toast({ title: "Sauvegardé", description: "Vos modifs ont été enregistrées.", severity: "success" })}>Success</Button>
|
||||
<Button variant="tonal" onClick={() => toast({ title: "Maintenance prévue dimanche", severity: "info" })}>Info</Button>
|
||||
<Button variant="ghost" onClick={() => toast({ title: "Quota presque atteint", description: "85% du stockage utilisé.", severity: "warning" })}>Warning</Button>
|
||||
<Button variant="danger" onClick={() => toast({ title: "Échec de la synchronisation", severity: "danger", duration: 0 })}>Danger</Button>
|
||||
<Button variant="primary" icon="stack-line" onClick={() => {
|
||||
const msgs = [
|
||||
{ title: "Bulletin Marie", description: "2 850 €", severity: "success" as const },
|
||||
{ title: "Bulletin Jean", description: "2 200 €", severity: "success" as const },
|
||||
{ title: "Bulletin Sophie", description: "3 400 €", severity: "success" as const },
|
||||
{ title: "Bulletin Thomas", description: "2 900 €", severity: "success" as const },
|
||||
{ title: "Bulletin Emma", description: "1 950 €", severity: "success" as const },
|
||||
];
|
||||
msgs.forEach((m, i) => setTimeout(() => toast(m), i * 220));
|
||||
}}>Empiler 5 toasts</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Default: Story = { render: () => <Trigger /> };
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { ToggleGroup } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Forms/ToggleGroup",
|
||||
component: ToggleGroup,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof ToggleGroup>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const items = [
|
||||
{ value: "list", label: "Liste", icon: "menu-line" as const },
|
||||
{ value: "grid", label: "Grille", icon: "apps-2-line" as const },
|
||||
{ value: "kanban", label: "Kanban", icon: "stack-line" as const },
|
||||
];
|
||||
|
||||
export const Single: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState<string | undefined>("list");
|
||||
return <ToggleGroup type="single" ariaLabel="Vue" value={v} onValueChange={(x) => x && setV(x)} items={items} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const Multiple: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState<string[]>(["list"]);
|
||||
return <ToggleGroup type="multiple" ariaLabel="Vues" value={v} onValueChange={setV} items={items} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const Solid: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState<string | undefined>("grid");
|
||||
return <ToggleGroup type="single" variant="solid" ariaLabel="Vue" value={v} onValueChange={(x) => x && setV(x)} items={items} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => {
|
||||
const [v, setV] = useState("list");
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<ToggleGroup type="single" size="sm" ariaLabel="x" value={v} onValueChange={(x) => x && setV(x)} items={items} />
|
||||
<ToggleGroup type="single" size="md" ariaLabel="x" value={v} onValueChange={(x) => x && setV(x)} items={items} />
|
||||
<ToggleGroup type="single" size="lg" ariaLabel="x" value={v} onValueChange={(x) => x && setV(x)} items={items} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { UserCard, Button } from "@managemate/react";
|
||||
|
||||
const meta = {
|
||||
title: "Profile/UserCard",
|
||||
component: UserCard,
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof UserCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: { name: "Marie Dupont", role: "Lead Developer", initials: "MD", status: "online" },
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<UserCard size="sm" name="Sophie Bernard" role="PM" initials="SB" status="online" />
|
||||
<UserCard name="Jean Martin" role="Designer · Synapse" initials="JM" status="away" />
|
||||
<UserCard size="lg" name="Thomas Legrand" role="DevOps" initials="TL" status="busy" meta="Disponible après 16h" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Interactive: Story = {
|
||||
args: {
|
||||
name: "Lohann Bouveresse",
|
||||
role: "CEO",
|
||||
initials: "LB",
|
||||
status: "online",
|
||||
meta: "ceo@managemate.fr",
|
||||
href: "#",
|
||||
actions: <Button size="sm" variant="ghost" icon="more-2-line" iconOnly aria-label="Actions" />,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user