diff --git a/.storybook/main.js b/.storybook/main.js index 6a3365831..8ae8ed940 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -3,7 +3,8 @@ module.exports = { "../stories/*.stories.mdx", "../stories/*.stories.@(ts|tsx)", "../stories/blocks/*.stories.@(ts|tsx)", - "../stories/charts/*.stories.@(ts|tsx)" + "../stories/charts/*.stories.@(ts|tsx)", + "../stories/picto/*.stories.@(ts|tsx)", ], "addons": [ "@storybook/addon-links", diff --git a/src/picto/utils/IconWrapper.tsx b/src/picto/utils/IconWrapper.tsx index 447b4caf0..c3b51016f 100644 --- a/src/picto/utils/IconWrapper.tsx +++ b/src/picto/utils/IconWrapper.tsx @@ -8,11 +8,11 @@ export type IconProps = { const getSize = (size: IconSize) => { switch (size) { case "small": - return "1.25rem"; + return "1.25em"; case "medium": - return "1.5rem"; + return "1.5em"; case "large": - return "2.5rem"; + return "2.5em"; case "inherit": return "inherit"; default: diff --git a/stories/Pictograms.stories.mdx b/stories/Pictograms.stories.mdx new file mode 100644 index 000000000..a77fa774f --- /dev/null +++ b/stories/Pictograms.stories.mdx @@ -0,0 +1,19 @@ +import { Meta } from "@storybook/addon-docs"; +import { Pictograms } from "./picto/Pictograms"; + + + +
+ +
diff --git a/stories/picto/Pictograms.tsx b/stories/picto/Pictograms.tsx new file mode 100644 index 000000000..b3756c0ea --- /dev/null +++ b/stories/picto/Pictograms.tsx @@ -0,0 +1,201 @@ +import { createElement, useState } from "react"; +import { Source } from "@storybook/components"; +import { Search } from "./Search"; +import { useConst } from "powerhooks/useConst"; +import { Evt } from "evt"; +import { tss } from "tss-react"; +import { fr } from "../../dist/fr"; +import * as Picto from "../../dist/picto"; +import { createModal } from "../../dist/Modal"; +import { Tooltip } from "../../dist/Tooltip"; +import CallOut from "../../dist/CallOut"; +import { getLink } from "../../dist/link"; + +const pictogrameEntries = Object.entries(Picto); + +const modal = createModal({ + id: "foo-modal", + isOpenedByDefault: false +}); + +export function Pictograms() { + const [search, setSearch] = useState(""); + + const { css, classes } = useStyles(); + + const filteredPictograms = pictogrameEntries.filter(([key]) => + key.toLowerCase().includes(search.toLowerCase()) + ); + const [selectedPicto, setSelectedPicto] = useState<{ key: string } | null>(null); + + const evtSearchAction = useConst(() => Evt.create<"scroll to">()); + + const { Link } = getLink(); + + return ( +
+ evtSearchAction.post("scroll to"), + "children": "Start searching" + }} + > + This tool help you find the perfect DSFR compliant pictogram for your project. +
+
+ + Learn more about pictograms + +
+ +

+ {search === "" + ? `${filteredPictograms.length} pictograms` + : `Found ${filteredPictograms.length} pictogram${ + filteredPictograms.length > 1 ? "s" : "" + }`} +

+
+
+ {filteredPictograms.map(([key, PictoComponent]) => ( +
{ + setSelectedPicto({ key }); + modal.open(); + }} + > + {typeof PictoComponent === "function" && ( + + )} +
{key}
+
+ ))} +
+
+ + {selectedPicto !== null && ( + <> +
+ +
+
+ {selectedPicto && + createElement( + Picto[selectedPicto.key] as React.ElementType, + { + style: { + backgroundSize: "30px 30px", + backgroundColor: "transparent", + backgroundPosition: + "0px 0px, 0px 15px, 15px -15px, -15px 0px", + backgroundImage: + "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%)" + }, + fontSize: 210 + } + )} +
+
+ +
+ {createElement( + Picto[selectedPicto.key] as React.ElementType, + { fontSize: "small" } + )} +
+
+ +
+ {createElement( + Picto[selectedPicto.key] as React.ElementType, + { fontSize: "medium" } + )} +
+
+ +
+ {createElement( + Picto[selectedPicto.key] as React.ElementType, + { fontSize: "large" } + )} +
+
+
+
+
+ + )} +
+
+ ); +} + +const useStyles = tss.withName({ Pictograms }).create(() => ({ + pictoTile: { + textAlign: "center", + width: 150, + cursor: "pointer", + "&:hover": { + borderRadius: 8, + backgroundColor: "var(--background-default-grey-hover)" + } + }, + pictoTileLabel: { + marginTop: 8, + fontSize: 12, + padding: "0 4px", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap" + }, + pictogramsContainer: { + display: "flex", + flexWrap: "wrap", + gap: fr.spacing("4v"), + fontSize: 72 + }, + pictogramsWrapper: { + padding: fr.spacing("1w"), + borderRadius: "8px", + backgroundColor: "var(--background-default-grey)" + } +})); diff --git a/stories/picto/Search.tsx b/stories/picto/Search.tsx new file mode 100644 index 000000000..caa259a36 --- /dev/null +++ b/stories/picto/Search.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import { tss } from "tss-react"; +import { SearchBar } from "../../dist/SearchBar"; +import { fr } from "../../dist/fr"; +import { NonPostableEvt } from "evt"; +import { useEvt } from "evt/hooks"; + +export type Props = { + className?: string; + search: string; + onSearchChange: (search: string) => void; + evtAction: NonPostableEvt<"scroll to">; +}; + +export function Search(props: Props) { + const { className, search, onSearchChange, evtAction } = props; + + const [inputElement, setInputElement] = useState(null); + const [searchBarWrapperElement, setSearchBarWrapperElement] = useState( + null + ); + + useEvt( + ctx => { + evtAction.attach( + action => action === "scroll to", + ctx, + () => { + inputElement?.focus(); + searchBarWrapperElement?.scrollIntoView({ + "behavior": "smooth", + "block": "start" + }); + } + ); + }, + [evtAction, inputElement, searchBarWrapperElement] + ); + + const { classes, cx } = useStyles(); + + return ( + <> +
setSearchBarWrapperElement(searchBarWrapperElement)} + > + ( + onSearchChange(event.target.value)} + className={className} + id={id} + placeholder={placeholder} + type={type} + /> + )} + /> +
+ + ); +} + +const useStyles = tss.withName({ Search }).create(() => ({ + "root": { + "display": "flex", + "paddingTop": fr.spacing("6v") + }, + "searchBar": { + "flex": 1 + } +}));