Skip to content

Commit cab3f34

Browse files
authored
feat: update Tile component #150 (#277)
* feat: update Tile component #150 * docs: update stories of Tile component * feat: update Tile component #150 * style: changes requested in code review * style: changes requested in code review
1 parent c7ee458 commit cab3f34

File tree

3 files changed

+517
-75
lines changed

3 files changed

+517
-75
lines changed

src/Tile.tsx

Lines changed: 157 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import React, { memo, forwardRef, type ReactNode, type CSSProperties } from "react";
2-
import { symToStr } from "tsafe/symToStr";
3-
import { assert } from "tsafe/assert";
1+
import type { ComponentProps, CSSProperties, ReactNode } from "react";
2+
import React, { forwardRef, memo } from "react";
43
import type { Equals } from "tsafe";
5-
import type { RegisteredLinkProps } from "./link";
6-
import { getLink } from "./link";
4+
import { assert } from "tsafe/assert";
5+
import { symToStr } from "tsafe/symToStr";
6+
77
import { fr } from "./fr";
8+
import { type RegisteredLinkProps, getLink } from "./link";
89
import { cx } from "./tools/cx";
910
import { useAnalyticsId } from "./tools/useAnalyticsId";
1011

@@ -13,25 +14,64 @@ export type TileProps = {
1314
id?: string;
1415
className?: string;
1516
title: ReactNode;
16-
linkProps: RegisteredLinkProps;
17+
titleAs?: `h${2 | 3 | 4 | 5 | 6}`;
18+
linkProps?: RegisteredLinkProps;
19+
buttonProps?: ComponentProps<"button">;
20+
downloadButton?: boolean;
1721
desc?: ReactNode;
22+
detail?: ReactNode;
23+
start?: ReactNode;
1824
imageUrl?: string;
1925
imageAlt?: string;
2026
imageWidth?: string | number;
2127
imageHeight?: string | number;
28+
imageSvg?: boolean;
2229
grey?: boolean;
23-
2430
/** make the whole tile clickable */
25-
enlargeLink?: boolean;
31+
enlargeLinkOrButton?: boolean;
2632
classes?: Partial<
27-
Record<"root" | "title" | "link" | "body" | "desc" | "img" | "imgTag", string>
33+
Record<
34+
| "root"
35+
| "content"
36+
| "title"
37+
| "header"
38+
| "link"
39+
| "button"
40+
| "body"
41+
| "desc"
42+
| "detail"
43+
| "start"
44+
| "img"
45+
| "imgTag",
46+
string
47+
>
2848
>;
29-
/** Default false */
30-
horizontal?: boolean;
49+
orientation?: "horizontal" | "vertical";
50+
small?: boolean;
51+
noIcon?: boolean;
52+
noBorder?: boolean;
53+
noBackground?: boolean;
54+
disabled?: boolean;
3155
style?: CSSProperties;
32-
};
56+
} & (TileNextProps.WithLink | TileNextProps.WithButton | TileNextProps.Unclickable);
3357

34-
export namespace TileProps {}
58+
export namespace TileNextProps {
59+
export type Unclickable = {
60+
linkProps?: never;
61+
buttonProps?: never;
62+
enlargeLinkOrButton?: never;
63+
};
64+
65+
export type WithLink = {
66+
linkProps: RegisteredLinkProps;
67+
buttonProps?: never;
68+
};
69+
70+
export type WithButton = {
71+
linkProps?: never;
72+
buttonProps: ComponentProps<"button">;
73+
};
74+
}
3575

3676
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-tile> */
3777
export const Tile = memo(
@@ -40,16 +80,27 @@ export const Tile = memo(
4080
id: id_props,
4181
className,
4282
title,
83+
titleAs: HtmlTitleTag = "h3",
4384
linkProps,
85+
buttonProps,
86+
downloadButton,
4487
desc,
88+
detail,
89+
start,
4590
imageUrl,
4691
imageAlt,
4792
imageWidth,
4893
imageHeight,
49-
horizontal = false,
94+
imageSvg = true,
95+
orientation = "vertical",
96+
small = false,
97+
noBorder = false,
98+
noIcon = false,
99+
noBackground = false,
50100
grey = false,
51101
classes = {},
52-
enlargeLink = true,
102+
enlargeLinkOrButton = true,
103+
disabled = false,
53104
style,
54105
...rest
55106
} = props;
@@ -69,9 +120,19 @@ export const Tile = memo(
69120
className={cx(
70121
fr.cx(
71122
"fr-tile",
72-
enlargeLink && "fr-enlarge-link",
73-
horizontal && "fr-tile--horizontal",
74-
grey && "fr-tile--grey"
123+
enlargeLinkOrButton &&
124+
(linkProps
125+
? "fr-enlarge-link"
126+
: buttonProps
127+
? "fr-enlarge-button"
128+
: null),
129+
orientation && `fr-tile--${orientation}`,
130+
noIcon && "fr-tile--no-icon",
131+
noBorder && "fr-tile--no-border",
132+
noBackground && "fr-tile--no-background",
133+
grey && "fr-tile--grey",
134+
small && "fr-tile--sm",
135+
buttonProps && downloadButton && "fr-tile--download"
75136
),
76137
classes.root,
77138
className
@@ -81,30 +142,90 @@ export const Tile = memo(
81142
{...rest}
82143
>
83144
<div className={cx(fr.cx("fr-tile__body"), classes.body)}>
84-
<h3 className={cx(fr.cx("fr-tile__title"), classes.title)}>
85-
<Link {...linkProps} className={cx(classes.link, linkProps.className)}>
86-
{title}
87-
</Link>
88-
</h3>
89-
<p className={cx(fr.cx("fr-tile__desc"), classes.desc)}>{desc}</p>
145+
<div className={cx(fr.cx("fr-tile__content"), classes.content)}>
146+
<HtmlTitleTag className={cx(fr.cx("fr-tile__title"), classes.title)}>
147+
{linkProps !== undefined ? (
148+
<Link
149+
{...linkProps}
150+
href={disabled ? undefined : linkProps.href}
151+
className={cx(classes.link, linkProps.className)}
152+
aria-disabled={disabled}
153+
>
154+
{title}
155+
</Link>
156+
) : buttonProps !== undefined ? (
157+
<button
158+
{...buttonProps}
159+
className={cx(classes.button, buttonProps.className)}
160+
disabled={disabled}
161+
>
162+
{title}
163+
</button>
164+
) : (
165+
title
166+
)}
167+
</HtmlTitleTag>
168+
169+
{desc !== undefined && (
170+
<p className={cx(fr.cx("fr-tile__desc"), classes.desc)}>{desc}</p>
171+
)}
172+
{detail !== undefined && (
173+
<p className={cx(fr.cx("fr-tile__detail"), classes.detail)}>{detail}</p>
174+
)}
175+
{start !== undefined && (
176+
<div className={cx(fr.cx("fr-tile__start"), classes.start)}>
177+
{start}
178+
</div>
179+
)}
180+
</div>
90181
</div>
91-
{(imageUrl !== undefined && imageUrl.length && (
92-
<div className={cx(fr.cx("fr-tile__img"), classes.img)}>
93-
<img
94-
className={cx(fr.cx("fr-responsive-img"), classes.imgTag)}
95-
src={imageUrl}
96-
alt={imageAlt}
97-
width={imageWidth}
98-
height={imageHeight}
99-
/>
182+
183+
{imageUrl !== undefined && imageUrl.length > 0 && (
184+
<div className={cx(fr.cx("fr-tile__header"), classes.header)}>
185+
{imageSvg ? (
186+
<div className={cx(fr.cx("fr-tile__pictogram"), classes.img)}>
187+
<svg
188+
aria-hidden={true}
189+
className={fr.cx("fr-artwork")}
190+
viewBox="0 0 80 80"
191+
width="80px"
192+
height="80px"
193+
xmlns="http://www.w3.org/2000/svg"
194+
xmlnsXlink="http://www.w3.org/1999/xlink"
195+
>
196+
{(
197+
[
198+
"artwork-decorative",
199+
"artwork-minor",
200+
"artwork-major"
201+
] as const
202+
).map(label => (
203+
<use
204+
key={label}
205+
className={fr.cx(`fr-${label}`)}
206+
xlinkHref={`${imageUrl}#${label}`}
207+
/>
208+
))}
209+
</svg>
210+
</div>
211+
) : (
212+
<div className={cx(fr.cx("fr-tile__img"), classes.img)}>
213+
<img
214+
className={cx(fr.cx("fr-responsive-img"), classes.imgTag)}
215+
src={imageUrl}
216+
alt={imageAlt}
217+
width={imageWidth}
218+
height={imageHeight}
219+
/>
220+
</div>
221+
)}
100222
</div>
101-
)) ||
102-
null}
223+
)}
103224
</div>
104225
);
105226
})
106227
);
107228

108-
Tile.displayName = symToStr({ Tile });
229+
Tile.displayName = symToStr({ TileNext: Tile });
109230

110231
export default Tile;

0 commit comments

Comments
 (0)