Skip to content

Commit f7e66d7

Browse files
authored
Merge pull request #26 from codegouvfr/quote
feat: add Quote component
2 parents 8411216 + 57b52ff commit f7e66d7

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"./Tabs": "./dist/Tabs.js",
127127
"./Stepper": "./dist/Stepper.js",
128128
"./SkipLinks": "./dist/SkipLinks.js",
129+
"./Quote": "./dist/Quote.js",
129130
"./Notice": "./dist/Notice.js",
130131
"./Highlight": "./dist/Highlight.js",
131132
"./Header": "./dist/Header/index.js",

src/Quote.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
6+
import { FrClassName } from "./lib/generatedFromCss/classNames";
7+
import { cx } from "./lib/tools/cx";
8+
import { fr } from "./lib";
9+
10+
import "./dsfr/component/quote/quote.css";
11+
12+
export type QuoteProps = {
13+
className?: string;
14+
text: ReactNode;
15+
author?: ReactNode;
16+
source?: ReactNode;
17+
sourceUrl?: string;
18+
imageUrl?: string;
19+
size?: "medium" | "large" | "xlarge";
20+
accentColor?: QuoteProps.AccentColor;
21+
classes?: Partial<Record<"root" | "author" | "source" | "image" | "imageTag" | "text", string>>;
22+
};
23+
24+
export namespace QuoteProps {
25+
type ExtractAccentColor<FrClassName> = FrClassName extends `fr-quote--${infer AccentColor}`
26+
? AccentColor
27+
: never;
28+
29+
export type AccentColor = ExtractAccentColor<FrClassName>;
30+
}
31+
32+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-quote> */
33+
export const Quote = memo(
34+
forwardRef<HTMLDivElement, QuoteProps>((props, ref) => {
35+
const {
36+
className,
37+
text,
38+
author,
39+
source,
40+
sourceUrl,
41+
imageUrl,
42+
size = "xlarge",
43+
accentColor,
44+
classes = {},
45+
...rest
46+
} = props;
47+
48+
assert<Equals<keyof typeof rest, never>>();
49+
50+
return (
51+
<figure
52+
className={cx(
53+
fr.cx("fr-quote"),
54+
imageUrl && fr.cx("fr-quote--column"),
55+
accentColor && `fr-quote--${accentColor}`,
56+
classes.root,
57+
className
58+
)}
59+
ref={ref}
60+
>
61+
<blockquote cite={sourceUrl}>
62+
<p
63+
className={cx(
64+
size === "large" && fr.cx("fr-text--lg"),
65+
size === "medium" && fr.cx("fr-text--md"),
66+
classes.text
67+
)}
68+
>
69+
« {text} »
70+
</p>
71+
</blockquote>
72+
<figcaption>
73+
{author !== undefined && (
74+
<p className={cx(fr.cx("fr-quote__author"), classes.author)}>{author}</p>
75+
)}
76+
{source !== undefined && (
77+
<ul className={cx(fr.cx("fr-quote__source"), classes.source)}>{source}</ul>
78+
)}
79+
{imageUrl !== undefined && (
80+
<div className={cx("fr-quote__image", classes.image)}>
81+
<img
82+
src={imageUrl}
83+
className={cx(fr.cx("fr-responsive-img"), classes.imageTag)}
84+
alt=""
85+
/>
86+
</div>
87+
)}
88+
</figcaption>
89+
</figure>
90+
);
91+
})
92+
);
93+
94+
Quote.displayName = symToStr({ Quote });
95+
96+
export default Quote;

stories/Quote.stories.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import React from "react";
2+
import { Quote } from "../dist/Quote";
3+
import { sectionName } from "./sectionName";
4+
import { getStoryFactory } from "./getStory";
5+
6+
const { meta, getStory } = getStoryFactory({
7+
sectionName,
8+
"wrappedComponent": { Quote },
9+
"description": `
10+
- [See DSFR documentation](//www.systeme-de-design.gouv.fr/elements-d-interface/composants/citation)
11+
- [See source code](//github.com/codegouvfr/react-dsfr/blob/main/src/Quote.tsx)`,
12+
"disabledProps": ["lang"]
13+
});
14+
15+
export default meta;
16+
17+
export const Default = getStory({
18+
text: "Lorem [...] elit ut. ",
19+
author: "Auteur",
20+
source: (
21+
<>
22+
<li>
23+
<cite>Ouvrage</cite>
24+
</li>
25+
<li>Détail 1</li>
26+
<li>Détail 2</li>
27+
<li>Détail 3</li>
28+
<li>
29+
<a
30+
target="_blank"
31+
href="[À MODIFIER | Lien vers la sources ou des infos complémentaires]"
32+
>
33+
Détail 4
34+
</a>
35+
</li>
36+
</>
37+
),
38+
imageUrl: "//www.systeme-de-design.gouv.fr/img/placeholder.1x1.png",
39+
size: "xlarge",
40+
className: ""
41+
});
42+
43+
export const QuoteMediumAndAccent = getStory({
44+
text: "Lorem [...] elit ut. ",
45+
author: "Auteur",
46+
source: (
47+
<>
48+
<li>
49+
<cite>Ouvrage</cite>
50+
</li>
51+
<li>Détail 1</li>
52+
<li>Détail 2</li>
53+
<li>Détail 3</li>
54+
<li>
55+
<a
56+
target="_blank"
57+
href="[À MODIFIER | Lien vers la sources ou des infos complémentaires]"
58+
>
59+
Détail 4
60+
</a>
61+
</li>
62+
</>
63+
),
64+
imageUrl: "//www.systeme-de-design.gouv.fr/img/placeholder.1x1.png",
65+
size: "medium",
66+
accentColor: "pink-macaron"
67+
});
68+
69+
export const QuoteWithoutDetails = getStory({
70+
text: "Lorem [...] elit ut. ",
71+
author: "Auteur",
72+
imageUrl: "//www.systeme-de-design.gouv.fr/img/placeholder.1x1.png"
73+
});
74+
75+
export const QuoteWithoutSource = getStory({
76+
text: "Lorem [...] elit ut. ",
77+
imageUrl: "//www.systeme-de-design.gouv.fr/img/placeholder.1x1.png"
78+
});
79+
80+
export const QuoteWithoutIllustration = getStory({
81+
text: "Lorem [...] elit ut. ",
82+
author: "Auteur",
83+
source: (
84+
<>
85+
<li>
86+
<cite>Ouvrage</cite>
87+
</li>
88+
<li>Détail 1</li>
89+
<li>Détail 2</li>
90+
<li>Détail 3</li>
91+
<li>
92+
<a
93+
target="_blank"
94+
href="[À MODIFIER | Lien vers la sources ou des infos complémentaires]"
95+
>
96+
Détail 4
97+
</a>
98+
</li>
99+
</>
100+
)
101+
});
102+
103+
export const QuoteWithAccent = getStory({
104+
text: "Lorem [...] elit ut. ",
105+
imageUrl: "//www.systeme-de-design.gouv.fr/img/placeholder.1x1.png",
106+
accentColor: "yellow-moutarde",
107+
author: "Someone"
108+
});

0 commit comments

Comments
 (0)