Skip to content

Commit eb52474

Browse files
committed
2 parents a2034bf + 04c2ae9 commit eb52474

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "0.0.77",
3+
"version": "0.0.78",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",
@@ -128,6 +128,7 @@
128128
"./Notice": "./dist/Notice.js",
129129
"./Header": "./dist/Header/index.js",
130130
"./DarkModeSwitch": "./dist/DarkModeSwitch.js",
131+
"./Badge": "./dist/Badge.js",
131132
"./Alert": "./dist/Alert.js",
132133
"./Accordion": "./dist/Accordion.js"
133134
}

src/Badge.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { memo, forwardRef, ReactNode } from "react";
2+
import { symToStr } from "tsafe/symToStr";
3+
import { assert } from "tsafe/assert";
4+
import type { Equals } from "tsafe";
5+
import { fr } from "./lib";
6+
import { cx } from "./lib/tools/cx";
7+
8+
// We make users import dsfr.css, so we don't need to import the scoped CSS
9+
// but in the future if we have a complete component coverage it
10+
// we could stop requiring users to import the hole CSS and only import on a
11+
// per component basis.
12+
import "./dsfr/component/badge/badge.css";
13+
14+
export type BadgeProps = {
15+
className?: string;
16+
severity?: BadgeProps.Severity;
17+
isSmall?: boolean;
18+
noIcon?: boolean;
19+
label: NonNullable<ReactNode>;
20+
};
21+
22+
export namespace BadgeProps {
23+
export type Severity = "info" | "success" | "error" | "warning" | "new";
24+
}
25+
26+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-badge> */
27+
export const Badge = memo(
28+
forwardRef<HTMLDivElement, BadgeProps>(props => {
29+
const { className, severity, label, isSmall, noIcon, ...rest } = props;
30+
31+
assert<Equals<keyof typeof rest, never>>();
32+
33+
return (
34+
<p
35+
className={cx(
36+
fr.cx(
37+
"fr-badge",
38+
{ [`fr-badge--${severity}`]: severity != null },
39+
{ "fr-badge--sm": isSmall },
40+
{ "fr-badge--no-icon": noIcon || severity == null }
41+
),
42+
className
43+
)}
44+
{...rest}
45+
>
46+
{label}
47+
</p>
48+
);
49+
})
50+
);
51+
52+
Badge.displayName = symToStr({ Badge });
53+
54+
export default Badge;

stories/Badge.stories.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Badge } from "../dist/Badge";
2+
import type { BadgeProps } from "../dist/Badge";
3+
import { sectionName } from "./sectionName";
4+
import { getStoryFactory } from "./getStory";
5+
import { assert } from "tsafe/assert";
6+
import type { Equals } from "tsafe";
7+
8+
const { meta, getStory } = getStoryFactory<BadgeProps>({
9+
sectionName,
10+
wrappedComponent: { Badge },
11+
description: `
12+
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/badge)
13+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Badge.tsx)`,
14+
argTypes: {
15+
severity: {
16+
options: (() => {
17+
const severities = ["success", "warning", "info", "error", "new"] as const;
18+
19+
assert<Equals<BadgeProps.Severity, BadgeProps.Severity>>();
20+
21+
return [null, ...severities];
22+
})(),
23+
control: { type: "select", labels: { null: "no severity" } }
24+
},
25+
noIcon: {
26+
type: { name: "boolean" },
27+
description: "Remove badge icon when true"
28+
},
29+
isSmall: {
30+
type: { name: "boolean" },
31+
description: "Set small badge size (`sm`) when true"
32+
},
33+
label: {
34+
type: { name: "string", required: true },
35+
description: "Label to display on tne badge"
36+
}
37+
},
38+
disabledProps: ["lang"]
39+
});
40+
41+
export default meta;
42+
43+
export const Default = getStory({
44+
severity: "success",
45+
label: "Label badge"
46+
});
47+
48+
export const BadgeWithoutSeverity = getStory(
49+
{
50+
label: "Label"
51+
},
52+
{
53+
description: "Medium info `Badge` with icon"
54+
}
55+
);
56+
57+
export const InfoBadge = getStory(
58+
{
59+
severity: "info",
60+
label: "Label info"
61+
},
62+
{
63+
description: "Medium info `Badge` with icon"
64+
}
65+
);
66+
67+
export const WarningBadge = getStory(
68+
{
69+
severity: "warning",
70+
noIcon: false,
71+
label: 'Label "warning"'
72+
},
73+
{
74+
description: "Medium warning `Badge` with icon"
75+
}
76+
);
77+
78+
export const SuccessBadge = getStory(
79+
{
80+
severity: "success",
81+
noIcon: true,
82+
label: "Label success"
83+
},
84+
{
85+
description: "Medium success `Badge` without icon"
86+
}
87+
);
88+
89+
export const ErrorBadge = getStory(
90+
{
91+
severity: "error",
92+
noIcon: true,
93+
label: "Label error"
94+
},
95+
{
96+
description: "Medium error `Badge` without icon"
97+
}
98+
);
99+
100+
export const NewBadge = getStory(
101+
{
102+
severity: "new",
103+
isSmall: true,
104+
label: "Label new"
105+
},
106+
{
107+
description: "Small new `Badge` with icon"
108+
}
109+
);

0 commit comments

Comments
 (0)