Skip to content

Commit fce523c

Browse files
committed
More precise type for the Button ref
1 parent 52570aa commit fce523c

File tree

3 files changed

+246
-46
lines changed

3 files changed

+246
-46
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ This module is a wrapper/compatibility layer for [@gouvfr/dsfr](https://github.c
3636
- [x] Exactly the same look and feel than with [@gouvfr/dsfr](https://www.npmjs.com/package/@gouvfr/dsfr).
3737
- [x] No [white flash when reloading in SSR setup](https://github.com/codegouvfr/@codegouvfr/react-dsfr/issues/2#issuecomment-1257263480).
3838
- [x] [Perfect integration with all major React framework: Next.js, Create React App, Vue](https://react-dsfr.etalab.studio/).
39-
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (8/42)
39+
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (12/42)
4040
- [x] Three shakable distribution, cherry pick the components you import. (It's not all in a big .js bundle)
4141
- [x] Optional integration with [MUI](https://mui.com/). If you use MUI components they will
4242
be automatically adapted to look like [DSFR components](https://www.systeme-de-design.gouv.fr/elements-d-interface). See [documentation](https://react-dsfr.etalab.studio/mui-integration).

src/Button.tsx

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,62 @@
11
import React, { memo, forwardRef } from "react";
2+
import type {
3+
ReactNode,
4+
RefAttributes,
5+
MemoExoticComponent,
6+
ForwardRefExoticComponent
7+
} from "react";
28
import { fr } from "./lib";
39
import { cx } from "./lib/tools/cx";
410
import type { FrIconClassName, RiIconClassName } from "./lib/generatedFromCss/classNames";
511
import { RegisteredLinkProps, useLink } from "./lib/routing";
612
import { assert } from "tsafe/assert";
713
import type { Equals } from "tsafe";
14+
import { symToStr } from "tsafe/symToStr";
815

9-
export type ButtonProps = ButtonProps.Anchor | ButtonProps.Button;
16+
export type ButtonProps = ButtonProps.Common &
17+
(ButtonProps.IconOnly | ButtonProps.WithIcon | ButtonProps.WithoutIcon) &
18+
(ButtonProps.AsAnchor | ButtonProps.AsButton);
1019
export namespace ButtonProps {
11-
type Common = {
20+
export type Common = {
1221
className?: string;
13-
label: string;
1422
/** Default primary */
15-
priority?: "primary" | "secondary" | "tertiary";
23+
priority?: "primary" | "secondary" | "tertiary" | "tertiary no outline";
1624
/** Default medium */
1725
size?: "small" | "medium" | "large";
18-
} & (WithIcon | WithoutIcon);
26+
};
27+
28+
export type IconOnly = {
29+
label?: never;
30+
/** Function of the button */
31+
title: string;
32+
iconId: FrIconClassName | RiIconClassName;
33+
iconPosition?: never;
34+
};
1935

2036
export type WithIcon = {
37+
label: ReactNode;
38+
/** Function of the button, to provide if the label isn't explicit */
39+
title?: string;
2140
iconId: FrIconClassName | RiIconClassName;
2241
/** Default left */
2342
iconPosition?: "left" | "right";
2443
};
2544

2645
export type WithoutIcon = {
46+
label: ReactNode;
47+
/** Function of the button, to provide if the label isn't explicit */
48+
title?: string;
2749
iconId?: never;
2850
iconPosition?: never;
2951
};
3052

31-
export type Anchor = Common & {
53+
export type AsAnchor = {
3254
linkProps: RegisteredLinkProps;
3355
onClick?: never;
3456
disabled?: never;
3557
type?: never;
3658
};
37-
export type Button = Common & {
59+
export type AsButton = {
3860
linkProps?: never;
3961
onClick: React.MouseEventHandler<HTMLButtonElement>;
4062
disabled?: boolean;
@@ -48,6 +70,7 @@ export const Button = memo(
4870
const {
4971
className: prop_className,
5072
label,
73+
title,
5174
iconId,
5275
iconPosition = "left",
5376
priority = "primary",
@@ -65,7 +88,12 @@ export const Button = memo(
6588

6689
const className = cx(
6790
fr.cx("fr-btn"),
68-
priority !== "primary" && fr.cx(`fr-btn--${priority}`),
91+
priority !== "primary" &&
92+
fr.cx(
93+
`fr-btn--${
94+
priority === "tertiary no outline" ? "tertiary-no-outline" : priority
95+
}`
96+
),
6997
size !== "medium" &&
7098
fr.cx(
7199
`fr-btn--${(() => {
@@ -77,29 +105,47 @@ export const Button = memo(
77105
}
78106
})()}`
79107
),
80-
iconId !== undefined && fr.cx(iconId, `fr-btn--icon-${iconPosition}`),
108+
iconId !== undefined &&
109+
fr.cx(iconId, label !== undefined && `fr-btn--icon-${iconPosition}`),
110+
linkProps !== undefined && linkProps.className,
81111
prop_className
82112
);
83-
const Component = linkProps ? (
113+
114+
return linkProps ? (
84115
<Link
85116
{...linkProps}
117+
title={title ?? linkProps.title}
86118
className={className}
87119
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
120+
{...rest}
88121
>
89122
{label}
90123
</Link>
91124
) : (
92125
<button
93126
className={className}
94127
type={type}
128+
title={title}
95129
onClick={onClick}
96130
disabled={disabled}
97131
ref={ref as React.ForwardedRef<HTMLButtonElement>}
132+
{...rest}
98133
>
99134
{label}
100135
</button>
101136
);
102-
103-
return Component;
104137
})
105-
);
138+
) as MemoExoticComponent<
139+
ForwardRefExoticComponent<
140+
ButtonProps.Common &
141+
(ButtonProps.IconOnly | ButtonProps.WithIcon | ButtonProps.WithoutIcon) &
142+
(
143+
| (ButtonProps.AsAnchor & RefAttributes<HTMLAnchorElement>)
144+
| (ButtonProps.AsButton & RefAttributes<HTMLButtonElement>)
145+
)
146+
>
147+
>;
148+
149+
Button.displayName = symToStr({ Button });
150+
151+
export default Button;

0 commit comments

Comments
 (0)