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" ;
4
3
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
+
7
7
import { fr } from "./fr" ;
8
+ import { type RegisteredLinkProps , getLink } from "./link" ;
8
9
import { cx } from "./tools/cx" ;
9
10
import { useAnalyticsId } from "./tools/useAnalyticsId" ;
10
11
@@ -13,25 +14,64 @@ export type TileProps = {
13
14
id ?: string ;
14
15
className ?: string ;
15
16
title : ReactNode ;
16
- linkProps : RegisteredLinkProps ;
17
+ titleAs ?: `h${2 | 3 | 4 | 5 | 6 } `;
18
+ linkProps ?: RegisteredLinkProps ;
19
+ buttonProps ?: ComponentProps < "button" > ;
20
+ downloadButton ?: boolean ;
17
21
desc ?: ReactNode ;
22
+ detail ?: ReactNode ;
23
+ start ?: ReactNode ;
18
24
imageUrl ?: string ;
19
25
imageAlt ?: string ;
20
26
imageWidth ?: string | number ;
21
27
imageHeight ?: string | number ;
28
+ imageSvg ?: boolean ;
22
29
grey ?: boolean ;
23
-
24
30
/** make the whole tile clickable */
25
- enlargeLink ?: boolean ;
31
+ enlargeLinkOrButton ?: boolean ;
26
32
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
+ >
28
48
> ;
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 ;
31
55
style ?: CSSProperties ;
32
- } ;
56
+ } & ( TileNextProps . WithLink | TileNextProps . WithButton | TileNextProps . Unclickable ) ;
33
57
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
+ }
35
75
36
76
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-tile> */
37
77
export const Tile = memo (
@@ -40,16 +80,27 @@ export const Tile = memo(
40
80
id : id_props ,
41
81
className,
42
82
title,
83
+ titleAs : HtmlTitleTag = "h3" ,
43
84
linkProps,
85
+ buttonProps,
86
+ downloadButton,
44
87
desc,
88
+ detail,
89
+ start,
45
90
imageUrl,
46
91
imageAlt,
47
92
imageWidth,
48
93
imageHeight,
49
- horizontal = false ,
94
+ imageSvg = true ,
95
+ orientation = "vertical" ,
96
+ small = false ,
97
+ noBorder = false ,
98
+ noIcon = false ,
99
+ noBackground = false ,
50
100
grey = false ,
51
101
classes = { } ,
52
- enlargeLink = true ,
102
+ enlargeLinkOrButton = true ,
103
+ disabled = false ,
53
104
style,
54
105
...rest
55
106
} = props ;
@@ -69,9 +120,19 @@ export const Tile = memo(
69
120
className = { cx (
70
121
fr . cx (
71
122
"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"
75
136
) ,
76
137
classes . root ,
77
138
className
@@ -81,30 +142,90 @@ export const Tile = memo(
81
142
{ ...rest }
82
143
>
83
144
< 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 >
90
181
</ 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
+ ) }
100
222
</ div >
101
- ) ) ||
102
- null }
223
+ ) }
103
224
</ div >
104
225
) ;
105
226
} )
106
227
) ;
107
228
108
- Tile . displayName = symToStr ( { Tile } ) ;
229
+ Tile . displayName = symToStr ( { TileNext : Tile } ) ;
109
230
110
231
export default Tile ;
0 commit comments