Skip to content

Commit 5147b06

Browse files
authored
Merge pull request #412 from codegouvfr/feat/span-tag
feat(tag): let user use span tag for Tag
2 parents a44d174 + 1bd0767 commit 5147b06

File tree

3 files changed

+66
-25
lines changed

3 files changed

+66
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "1.23.9",
3+
"version": "1.23.10-rc.2",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",

src/Tag.tsx

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ type DataAttribute = Record<`data-${string}`, string | boolean | null | undefine
2222

2323
export type TagProps = TagProps.Common &
2424
(TagProps.WithIcon | TagProps.WithoutIcon) &
25-
(TagProps.AsAnchor | TagProps.AsButton | TagProps.AsParagraph);
25+
(TagProps.AsAnchor | TagProps.AsButton | TagProps.AsParagraph | TagProps.AsSpan);
2626
export namespace TagProps {
27+
export type HTMLElement =
28+
| HTMLButtonElement
29+
| HTMLAnchorElement
30+
| HTMLParagraphElement
31+
| HTMLSpanElement;
32+
2733
export type Common = {
2834
id?: string;
2935
className?: string;
@@ -32,6 +38,7 @@ export namespace TagProps {
3238
style?: CSSProperties;
3339
title?: string;
3440
children: ReactNode;
41+
as?: "p" | "span" | "button" | "a";
3542
};
3643

3744
export type WithIcon = {
@@ -44,18 +51,20 @@ export namespace TagProps {
4451
};
4552

4653
export type AsAnchor = {
54+
as?: "a";
4755
linkProps: RegisteredLinkProps;
4856
onClick?: never;
4957
nativeButtonProps?: never;
50-
/** @deprecated Tag is now <p> by default. Use `nativeParagraphProps` instead. */
58+
/** @deprecated Tag is now `<p>` by default. Use `nativeParagraphProps` instead. */
5159
nativeSpanProps?: never;
5260
nativeParagraphProps?: never;
5361
dismissible?: never;
5462
pressed?: never;
5563
};
5664
export type AsButton = {
65+
as?: "button";
5766
linkProps?: never;
58-
/** @deprecated Tag is now <p> by default. Use `nativeParagraphProps` instead. */
67+
/** @deprecated Tag is now `<p>` by default. Use `nativeParagraphProps` instead. */
5968
nativeSpanProps?: never;
6069
nativeParagraphProps?: never;
6170
/** Default: false */
@@ -65,23 +74,32 @@ export namespace TagProps {
6574
nativeButtonProps?: ComponentProps<"button"> & DataAttribute;
6675
};
6776
export type AsParagraph = {
77+
as?: "p";
6878
linkProps?: never;
6979
onClick?: never;
7080
dismissible?: never;
7181
pressed?: never;
7282
nativeButtonProps?: never;
73-
/** @deprecated Tag is now <p> by default. Use `nativeParagraphProps` instead. */
83+
/** @deprecated Tag is now `<p>` by default. Use `nativeParagraphProps` instead. */
7484
nativeSpanProps?: ComponentProps<"span"> & DataAttribute;
7585
nativeParagraphProps?: ComponentProps<"p"> & DataAttribute;
7686
};
77-
78-
/** @deprecated Tag is now <p> by default. Use `AsParagraph` instead. */
79-
export type AsSpan = AsParagraph;
87+
export type AsSpan = {
88+
as: "span";
89+
linkProps?: never;
90+
onClick?: never;
91+
dismissible?: never;
92+
pressed?: never;
93+
nativeButtonProps?: never;
94+
nativeSpanProps?: ComponentProps<"span"> & DataAttribute;
95+
nativeParagraphProps?: never;
96+
};
8097
}
8198

8299
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-tag> */
83100
export const Tag = memo(
84-
forwardRef<HTMLButtonElement | HTMLAnchorElement | HTMLParagraphElement, TagProps>(
101+
forwardRef<TagProps.HTMLElement, TagProps>(
102+
// -- (lint hack) to keep same indent as before
85103
(props, ref) => {
86104
const {
87105
id: id_props,
@@ -98,6 +116,7 @@ export const Tag = memo(
98116
nativeSpanProps,
99117
style,
100118
onClick,
119+
as: AsTag = "p",
101120
...rest
102121
} = props;
103122

@@ -122,6 +141,7 @@ export const Tag = memo(
122141
prop_className
123142
);
124143

144+
// to support old usage
125145
const nativeParagraphOrSpanProps = nativeParagraphProps ?? nativeSpanProps;
126146

127147
return (
@@ -161,22 +181,24 @@ export const Tag = memo(
161181
{children}
162182
</button>
163183
)}
164-
{linkProps === undefined && nativeButtonProps === undefined && (
165-
<p
166-
{...nativeParagraphOrSpanProps}
167-
id={id_props ?? nativeParagraphOrSpanProps?.id ?? id}
168-
className={cx(nativeParagraphOrSpanProps?.className, className)}
169-
style={{
170-
...nativeParagraphOrSpanProps?.style,
171-
...style
172-
}}
173-
title={title ?? nativeParagraphOrSpanProps?.title}
174-
ref={ref as React.ForwardedRef<HTMLParagraphElement>}
175-
{...rest}
176-
>
177-
{children}
178-
</p>
179-
)}
184+
{linkProps === undefined &&
185+
nativeButtonProps === undefined &&
186+
(AsTag === "span" || AsTag === "p") && (
187+
<AsTag
188+
{...nativeParagraphOrSpanProps}
189+
id={id_props ?? nativeParagraphOrSpanProps?.id ?? id}
190+
className={cx(nativeParagraphOrSpanProps?.className, className)}
191+
style={{
192+
...nativeParagraphOrSpanProps?.style,
193+
...style
194+
}}
195+
title={title ?? nativeParagraphOrSpanProps?.title}
196+
ref={ref as React.ForwardedRef<HTMLParagraphElement>}
197+
{...rest}
198+
>
199+
{children}
200+
</AsTag>
201+
)}
180202
</>
181203
);
182204
}
@@ -189,6 +211,7 @@ export const Tag = memo(
189211
| (TagProps.AsAnchor & RefAttributes<HTMLAnchorElement>)
190212
| (TagProps.AsButton & RefAttributes<HTMLButtonElement>)
191213
| (TagProps.AsParagraph & RefAttributes<HTMLParagraphElement>)
214+
| (TagProps.AsSpan & RefAttributes<HTMLSpanElement>)
192215
)
193216
>
194217
>;

stories/Tag.stories.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ const { meta, getStory } = getStoryFactory({
3535
Example: \`{ "aria-controls": "fr-modal-1", onMouseEnter: event => {...} }\``,
3636
"control": { "type": null }
3737
},
38+
39+
"as": {
40+
"options": (() => {
41+
const options = ["p", "span", "button", "a", undefined] as const;
42+
43+
assert<Equals<typeof options[number], TagProps["as"]>>();
44+
45+
return options;
46+
})(),
47+
"control": { type: "select", labels: { null: "default p element" } },
48+
"description":
49+
"You can specify a 'span' element instead of default 'p' if the badge is inside a `<p>`. 'button' and 'a' are implicit."
50+
},
3851
"children": {
3952
"description": "The label of the button",
4053
"control": { "type": "string" }
@@ -107,3 +120,8 @@ export const TagPressed = getStory({
107120
onClick: () => console.log("click")
108121
}
109122
});
123+
124+
export const AsSpan = getStory({
125+
"children": "Label button",
126+
as: "span"
127+
});

0 commit comments

Comments
 (0)