Skip to content

Commit 918c0b5

Browse files
committed
2 parents fda10ed + 4fba5ee commit 918c0b5

File tree

7 files changed

+507
-127
lines changed

7 files changed

+507
-127
lines changed

COMPONENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
- [ ] Translate
3737
- [x] Summary
3838
- [ ] Table
39-
- [ ] Tag
39+
- [x] Tag
4040
- [x] Download
4141
- [ ] Transcription
4242
- [x] Tile

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "0.43.2",
3+
"version": "0.44.0",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",
@@ -138,6 +138,7 @@
138138
"./ToggleSwitchGroup": "./dist/ToggleSwitchGroup.js",
139139
"./ToggleSwitch": "./dist/ToggleSwitch.js",
140140
"./Tile": "./dist/Tile.js",
141+
"./Tag": "./dist/Tag.js",
141142
"./Tabs": "./dist/Tabs.js",
142143
"./Summary": "./dist/Summary.js",
143144
"./Stepper": "./dist/Stepper.js",

src/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ export const Footer = memo(
285285
)}
286286
>
287287
<ul>
288-
{subPartnersLogos.map(logo => (
289-
<li>
288+
{subPartnersLogos.map((logo, i) => (
289+
<li key={i}>
290290
<a
291291
href={logo.href}
292292
className={cx(

src/Tag.tsx

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import React, {
2+
memo,
3+
forwardRef,
4+
type ReactNode,
5+
type RefAttributes,
6+
type MemoExoticComponent,
7+
type ForwardRefExoticComponent,
8+
type CSSProperties
9+
} from "react";
10+
import { fr } from "./fr";
11+
import { cx } from "./tools/cx";
12+
import type { FrIconClassName, RiIconClassName } from "./fr/generatedFromCss/classNames";
13+
import { getLink } from "./link";
14+
import type { RegisteredLinkProps } from "./link";
15+
import { assert } from "tsafe/assert";
16+
import type { Equals } from "tsafe";
17+
import { symToStr } from "tsafe/symToStr";
18+
19+
type DataAttribute = Record<`data-${string}`, string | boolean | null | undefined>;
20+
21+
export type TagProps = TagProps.Common &
22+
(TagProps.WithIcon | TagProps.WithoutIcon) &
23+
(TagProps.AsAnchor | TagProps.AsButton | TagProps.AsSpan);
24+
export namespace TagProps {
25+
export type Common = {
26+
className?: string;
27+
/** Default: false */
28+
small?: boolean;
29+
style?: CSSProperties;
30+
title?: string;
31+
children: ReactNode;
32+
};
33+
34+
export type WithIcon = {
35+
/** Function of the button, to provide if the label isn't explicit */
36+
iconId: FrIconClassName | RiIconClassName;
37+
};
38+
39+
export type WithoutIcon = {
40+
iconId?: never;
41+
};
42+
43+
export type AsAnchor = {
44+
linkProps: RegisteredLinkProps;
45+
onClick?: never;
46+
nativeButtonProps?: never;
47+
nativeSpanProps?: never;
48+
dismissible?: never;
49+
pressed?: never;
50+
};
51+
export type AsButton = {
52+
linkProps?: never;
53+
nativeSpanProps?: never;
54+
/** Default: false */
55+
dismissible?: boolean;
56+
pressed?: boolean;
57+
onClick?: React.MouseEventHandler<HTMLButtonElement>;
58+
nativeButtonProps?: React.DetailedHTMLProps<
59+
React.ButtonHTMLAttributes<HTMLButtonElement>,
60+
HTMLButtonElement
61+
> &
62+
DataAttribute;
63+
};
64+
export type AsSpan = {
65+
linkProps?: never;
66+
onClick?: never;
67+
dismissible?: never;
68+
pressed?: never;
69+
nativeButtonProps?: never;
70+
nativeSpanProps?: React.DetailedHTMLProps<
71+
React.HTMLAttributes<HTMLSpanElement>,
72+
HTMLSpanElement
73+
> &
74+
DataAttribute;
75+
};
76+
}
77+
78+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-tag> */
79+
export const Tag = memo(
80+
forwardRef<HTMLButtonElement | HTMLAnchorElement | HTMLSpanElement, TagProps>((props, ref) => {
81+
const {
82+
className: prop_className,
83+
children,
84+
title,
85+
iconId,
86+
small = false,
87+
pressed,
88+
dismissible = false,
89+
linkProps,
90+
nativeButtonProps,
91+
nativeSpanProps,
92+
style,
93+
onClick,
94+
...rest
95+
} = props;
96+
97+
assert<Equals<keyof typeof rest, never>>();
98+
99+
const { Link } = getLink();
100+
101+
const className = cx(
102+
fr.cx(
103+
"fr-tag",
104+
small && `fr-tag--sm`,
105+
iconId,
106+
iconId && "fr-tag--icon-left", // actually, it's always left but we need it in order to have the icon rendering
107+
dismissible && "fr-tag--dismiss"
108+
),
109+
linkProps !== undefined && linkProps.className,
110+
prop_className
111+
);
112+
113+
return (
114+
<>
115+
{linkProps !== undefined && (
116+
<Link
117+
{...linkProps}
118+
title={title ?? linkProps.title}
119+
className={cx(linkProps?.className, className)}
120+
style={{
121+
...linkProps?.style,
122+
...style
123+
}}
124+
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
125+
{...rest}
126+
>
127+
{children}
128+
</Link>
129+
)}
130+
{nativeButtonProps !== undefined && (
131+
<button
132+
{...nativeButtonProps}
133+
className={cx(nativeButtonProps?.className, className)}
134+
style={{
135+
...nativeButtonProps?.style,
136+
...style
137+
}}
138+
title={title ?? nativeButtonProps?.title}
139+
onClick={onClick ?? nativeButtonProps?.onClick}
140+
disabled={nativeButtonProps?.disabled}
141+
ref={ref as React.ForwardedRef<HTMLButtonElement>}
142+
aria-pressed={pressed}
143+
{...rest}
144+
>
145+
{children}
146+
</button>
147+
)}
148+
{linkProps === undefined && nativeButtonProps === undefined && (
149+
<span
150+
{...nativeSpanProps}
151+
className={cx(nativeSpanProps?.className, className)}
152+
style={{
153+
...nativeSpanProps?.style,
154+
...style
155+
}}
156+
title={title ?? nativeSpanProps?.title}
157+
ref={ref as React.ForwardedRef<HTMLSpanElement>}
158+
{...rest}
159+
>
160+
{children}
161+
</span>
162+
)}
163+
</>
164+
);
165+
})
166+
) as MemoExoticComponent<
167+
ForwardRefExoticComponent<
168+
TagProps.Common &
169+
(TagProps.WithIcon | TagProps.WithoutIcon) &
170+
(
171+
| (TagProps.AsAnchor & RefAttributes<HTMLAnchorElement>)
172+
| (TagProps.AsButton & RefAttributes<HTMLButtonElement>)
173+
| (TagProps.AsSpan & RefAttributes<HTMLSpanElement>)
174+
)
175+
>
176+
>;
177+
178+
Tag.displayName = symToStr({ Tag });
179+
180+
export default Tag;

0 commit comments

Comments
 (0)