Skip to content

Commit 320fd62

Browse files
authored
Pictograms documentation (#421)
* add pictogrammes page * picto tile * Update Pictogrammes.tsx * use em instead of rem to inherit size from direct parent * add modal * add dsfr link * Update Pictogrammes.tsx * styling * Update main.js * Update utils.css * Pictogrammes => Pictograms * better label * fix: format
1 parent b2243aa commit 320fd62

File tree

5 files changed

+301
-4
lines changed

5 files changed

+301
-4
lines changed

.storybook/main.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module.exports = {
33
"../stories/*.stories.mdx",
44
"../stories/*.stories.@(ts|tsx)",
55
"../stories/blocks/*.stories.@(ts|tsx)",
6-
"../stories/charts/*.stories.@(ts|tsx)"
6+
"../stories/charts/*.stories.@(ts|tsx)",
7+
"../stories/picto/*.stories.@(ts|tsx)",
78
],
89
"addons": [
910
"@storybook/addon-links",

src/picto/utils/IconWrapper.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export type IconProps = {
88
const getSize = (size: IconSize) => {
99
switch (size) {
1010
case "small":
11-
return "1.25rem";
11+
return "1.25em";
1212
case "medium":
13-
return "1.5rem";
13+
return "1.5em";
1414
case "large":
15-
return "2.5rem";
15+
return "2.5em";
1616
case "inherit":
1717
return "inherit";
1818
default:

stories/Pictograms.stories.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Meta } from "@storybook/addon-docs";
2+
import { Pictograms } from "./picto/Pictograms";
3+
4+
<Meta
5+
title="🖼️ Pictograms"
6+
parameters={{
7+
"viewMode": "docs",
8+
"previewTabs": {
9+
"canvas": { "hidden": true },
10+
"zoom": { "hidden": true },
11+
"storybook/background": { "hidden": true },
12+
"storybook/viewport": { "hidden": true },
13+
},
14+
}}
15+
/>
16+
17+
<div style={{ "margin": "0 auto", "maxWidth": 1000 }}>
18+
<Pictograms />
19+
</div>

stories/picto/Pictograms.tsx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { createElement, useState } from "react";
2+
import { Source } from "@storybook/components";
3+
import { Search } from "./Search";
4+
import { useConst } from "powerhooks/useConst";
5+
import { Evt } from "evt";
6+
import { tss } from "tss-react";
7+
import { fr } from "../../dist/fr";
8+
import * as Picto from "../../dist/picto";
9+
import { createModal } from "../../dist/Modal";
10+
import { Tooltip } from "../../dist/Tooltip";
11+
import CallOut from "../../dist/CallOut";
12+
import { getLink } from "../../dist/link";
13+
14+
const pictogrameEntries = Object.entries(Picto);
15+
16+
const modal = createModal({
17+
id: "foo-modal",
18+
isOpenedByDefault: false
19+
});
20+
21+
export function Pictograms() {
22+
const [search, setSearch] = useState("");
23+
24+
const { css, classes } = useStyles();
25+
26+
const filteredPictograms = pictogrameEntries.filter(([key]) =>
27+
key.toLowerCase().includes(search.toLowerCase())
28+
);
29+
const [selectedPicto, setSelectedPicto] = useState<{ key: string } | null>(null);
30+
31+
const evtSearchAction = useConst(() => Evt.create<"scroll to">());
32+
33+
const { Link } = getLink();
34+
35+
return (
36+
<div>
37+
<CallOut
38+
className={css({ "marginBottom": 0 })}
39+
title="Pictograms"
40+
iconId="fr-icon-search-line"
41+
buttonProps={{
42+
"onClick": () => evtSearchAction.post("scroll to"),
43+
"children": "Start searching"
44+
}}
45+
>
46+
This tool help you find the perfect DSFR compliant pictogram for your project.
47+
<br />
48+
<br />
49+
<Link
50+
target="_blank"
51+
href="https://www.systeme-de-design.gouv.fr/fondamentaux/pictogramme"
52+
>
53+
Learn more about pictograms
54+
</Link>
55+
</CallOut>
56+
<Search evtAction={evtSearchAction} onSearchChange={setSearch} search={search} />
57+
<h3 style={{ marginTop: fr.spacing("6v") }}>
58+
{search === ""
59+
? `${filteredPictograms.length} pictograms`
60+
: `Found ${filteredPictograms.length} pictogram${
61+
filteredPictograms.length > 1 ? "s" : ""
62+
}`}
63+
</h3>
64+
<div className={classes.pictogramsWrapper}>
65+
<div className={classes.pictogramsContainer}>
66+
{filteredPictograms.map(([key, PictoComponent]) => (
67+
<div
68+
key={key}
69+
className={classes.pictoTile}
70+
onClick={() => {
71+
setSelectedPicto({ key });
72+
modal.open();
73+
}}
74+
>
75+
{typeof PictoComponent === "function" && (
76+
<PictoComponent fontSize="inherit" />
77+
)}
78+
<div className={classes.pictoTileLabel}>{key}</div>
79+
</div>
80+
))}
81+
</div>
82+
</div>
83+
<modal.Component title={selectedPicto?.key ?? "Pictogramme"}>
84+
{selectedPicto !== null && (
85+
<>
86+
<div style={{ textAlign: "center" }}>
87+
<Source
88+
language="tsx"
89+
code={`import { ${selectedPicto.key} } from "@codegouvfr/react-dsfr/picto";`}
90+
/>
91+
<div
92+
style={{
93+
display: "flex",
94+
justifyContent: "space-around",
95+
alignItems: "center",
96+
marginTop: fr.spacing("2v")
97+
}}
98+
>
99+
<div>
100+
{selectedPicto &&
101+
createElement(
102+
Picto[selectedPicto.key] as React.ElementType,
103+
{
104+
style: {
105+
backgroundSize: "30px 30px",
106+
backgroundColor: "transparent",
107+
backgroundPosition:
108+
"0px 0px, 0px 15px, 15px -15px, -15px 0px",
109+
backgroundImage:
110+
"linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%), linear-gradient(-45deg, rgb(230, 230, 230) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgb(230, 230, 230) 75%), linear-gradient(-45deg, transparent 75%, rgb(230, 230, 230) 75%)"
111+
},
112+
fontSize: 210
113+
}
114+
)}
115+
</div>
116+
<div
117+
style={{
118+
display: "flex",
119+
justifyContent: "space-between",
120+
alignItems: "flex-end",
121+
marginTop: fr.spacing("2v"),
122+
fontSize: "1.5rem",
123+
gap: fr.spacing("4v")
124+
}}
125+
>
126+
<Tooltip kind="hover" title="fontSize small">
127+
<div
128+
style={{
129+
padding: fr.spacing("1w")
130+
}}
131+
>
132+
{createElement(
133+
Picto[selectedPicto.key] as React.ElementType,
134+
{ fontSize: "small" }
135+
)}
136+
</div>
137+
</Tooltip>
138+
<Tooltip kind="hover" title="fontSize medium">
139+
<div
140+
style={{
141+
padding: fr.spacing("1w")
142+
}}
143+
>
144+
{createElement(
145+
Picto[selectedPicto.key] as React.ElementType,
146+
{ fontSize: "medium" }
147+
)}
148+
</div>
149+
</Tooltip>
150+
<Tooltip kind="hover" title="fontSize large">
151+
<div
152+
style={{
153+
padding: fr.spacing("1w")
154+
}}
155+
>
156+
{createElement(
157+
Picto[selectedPicto.key] as React.ElementType,
158+
{ fontSize: "large" }
159+
)}
160+
</div>
161+
</Tooltip>
162+
</div>
163+
</div>
164+
</div>
165+
</>
166+
)}
167+
</modal.Component>
168+
</div>
169+
);
170+
}
171+
172+
const useStyles = tss.withName({ Pictograms }).create(() => ({
173+
pictoTile: {
174+
textAlign: "center",
175+
width: 150,
176+
cursor: "pointer",
177+
"&:hover": {
178+
borderRadius: 8,
179+
backgroundColor: "var(--background-default-grey-hover)"
180+
}
181+
},
182+
pictoTileLabel: {
183+
marginTop: 8,
184+
fontSize: 12,
185+
padding: "0 4px",
186+
overflow: "hidden",
187+
textOverflow: "ellipsis",
188+
whiteSpace: "nowrap"
189+
},
190+
pictogramsContainer: {
191+
display: "flex",
192+
flexWrap: "wrap",
193+
gap: fr.spacing("4v"),
194+
fontSize: 72
195+
},
196+
pictogramsWrapper: {
197+
padding: fr.spacing("1w"),
198+
borderRadius: "8px",
199+
backgroundColor: "var(--background-default-grey)"
200+
}
201+
}));

stories/picto/Search.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useState } from "react";
2+
import { tss } from "tss-react";
3+
import { SearchBar } from "../../dist/SearchBar";
4+
import { fr } from "../../dist/fr";
5+
import { NonPostableEvt } from "evt";
6+
import { useEvt } from "evt/hooks";
7+
8+
export type Props = {
9+
className?: string;
10+
search: string;
11+
onSearchChange: (search: string) => void;
12+
evtAction: NonPostableEvt<"scroll to">;
13+
};
14+
15+
export function Search(props: Props) {
16+
const { className, search, onSearchChange, evtAction } = props;
17+
18+
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null);
19+
const [searchBarWrapperElement, setSearchBarWrapperElement] = useState<HTMLDivElement | null>(
20+
null
21+
);
22+
23+
useEvt(
24+
ctx => {
25+
evtAction.attach(
26+
action => action === "scroll to",
27+
ctx,
28+
() => {
29+
inputElement?.focus();
30+
searchBarWrapperElement?.scrollIntoView({
31+
"behavior": "smooth",
32+
"block": "start"
33+
});
34+
}
35+
);
36+
},
37+
[evtAction, inputElement, searchBarWrapperElement]
38+
);
39+
40+
const { classes, cx } = useStyles();
41+
42+
return (
43+
<>
44+
<div
45+
className={cx(classes.root, className)}
46+
ref={searchBarWrapperElement => setSearchBarWrapperElement(searchBarWrapperElement)}
47+
>
48+
<SearchBar
49+
className={classes.searchBar}
50+
label="Filter by name"
51+
renderInput={({ className, id, placeholder, type }) => (
52+
<input
53+
ref={setInputElement}
54+
value={search}
55+
onChange={event => onSearchChange(event.target.value)}
56+
className={className}
57+
id={id}
58+
placeholder={placeholder}
59+
type={type}
60+
/>
61+
)}
62+
/>
63+
</div>
64+
</>
65+
);
66+
}
67+
68+
const useStyles = tss.withName({ Search }).create(() => ({
69+
"root": {
70+
"display": "flex",
71+
"paddingTop": fr.spacing("6v")
72+
},
73+
"searchBar": {
74+
"flex": 1
75+
}
76+
}));

0 commit comments

Comments
 (0)