|
1 | 1 | import React, { memo, forwardRef } from "react"; |
2 | | -// import { symToStr } from "tsafe/symToStr"; |
3 | 2 | import { fr } from "./lib"; |
4 | 3 | import { cx } from "./lib/tools/cx"; |
5 | 4 | import type { FrIconClassName, RiIconClassName } from "./lib/generatedFromCss/classNames"; |
| 5 | +import { RegisteredLinkProps, useLink } from "./lib/routing"; |
| 6 | +import { assert } from "tsafe/assert"; |
| 7 | +import type { Equals } from "tsafe"; |
6 | 8 |
|
7 | | -// We make users import dsfr.css, so we don't need to import the scoped CSS |
8 | | -// but in the future if we have a complete component coverage it |
9 | | -// we could stop requiring users to import the hole CSS and only import on a |
10 | | -// per component basis. |
11 | | -import "./dsfr/component/button/button.css"; |
12 | | - |
13 | | -// This is just an example, we should get types from .fr-icon-* list |
14 | | -// const icons = ["fr-icon-checkbox-circle-line", "fr-icon-account-circle-fill"] as const; |
15 | | -// type IconType = typeof icons[number]; |
16 | | - |
17 | | -type IconType = FrIconClassName | RiIconClassName; |
18 | | - |
19 | | -type ButtonIcon = { |
20 | | - name: IconType; |
21 | | - position?: "left" | "right"; |
22 | | -}; |
23 | | - |
24 | | -type ButtonCommonProps = { |
25 | | - label: string; |
26 | | - icon?: ButtonIcon; |
27 | | - priority?: "secondary" | "tertiary"; |
28 | | - className?: string; |
29 | | - size?: ButtonProps.Size; |
30 | | -}; |
31 | | - |
32 | | -export type ButtonProps = ButtonCommonProps & (ButtonProps.Anchor | ButtonProps.Button); |
| 9 | +export type ButtonProps = ButtonProps.Anchor | ButtonProps.Button; |
33 | 10 |
|
34 | 11 | export namespace ButtonProps { |
35 | | - export type Size = "sm" | "lg"; |
36 | | - export type Anchor = { |
37 | | - href: string | null; |
38 | | - target?: React.HTMLAttributeAnchorTarget; |
| 12 | + type Common = { |
| 13 | + className?: string; |
| 14 | + label: string; |
| 15 | + icon?: { |
| 16 | + iconId: FrIconClassName | RiIconClassName; |
| 17 | + position?: "left" | "right"; |
| 18 | + }; |
| 19 | + priority?: "secondary" | "tertiary"; |
| 20 | + size?: "sm" | "lg"; |
| 21 | + }; |
| 22 | + |
| 23 | + export type Anchor = Common & { |
| 24 | + linkProps: RegisteredLinkProps; |
| 25 | + onClick?: never; |
39 | 26 | disabled?: never; |
40 | 27 | type?: never; |
41 | | - onClick?: never; |
42 | 28 | }; |
43 | | - export type Button = { |
44 | | - onClick?: React.MouseEventHandler<HTMLButtonElement>; |
| 29 | + export type Button = Common & { |
| 30 | + linkProps?: never; |
| 31 | + onClick: React.MouseEventHandler<HTMLButtonElement>; |
45 | 32 | disabled?: boolean; |
46 | 33 | type?: "button" | "submit" | "reset"; |
47 | | - href?: never; |
48 | | - target?: never; |
49 | 34 | }; |
50 | 35 | } |
51 | 36 |
|
52 | 37 | export const Button = memo( |
53 | | - forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(props => { |
54 | | - const { icon, priority, className, size, label } = props; |
| 38 | + forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>((props, ref) => { |
| 39 | + const { |
| 40 | + icon, |
| 41 | + priority, |
| 42 | + className, |
| 43 | + size, |
| 44 | + label, |
| 45 | + linkProps, |
| 46 | + onClick, |
| 47 | + disabled, |
| 48 | + type, |
| 49 | + ...rest |
| 50 | + } = props; |
| 51 | + |
| 52 | + assert<Equals<keyof typeof rest, never>>(); |
| 53 | + |
| 54 | + const { Link } = useLink(); |
55 | 55 |
|
56 | 56 | const buttonClassName = cx( |
57 | 57 | fr.cx("fr-btn"), |
58 | 58 | priority && fr.cx(`fr-btn--${priority}`), |
59 | 59 | size && fr.cx(`fr-btn--${size}`), |
60 | | - icon && cx(fr.cx(icon.name), icon.position && fr.cx(`fr-btn--icon-${icon.position}`)), |
| 60 | + icon && cx(fr.cx(icon.iconId), icon.position && fr.cx(`fr-btn--icon-${icon.position}`)), |
61 | 61 | className |
62 | 62 | ); |
63 | | - const Component = |
64 | | - "href" in props ? ( |
65 | | - <a |
66 | | - className={buttonClassName} |
67 | | - href={props.href ?? undefined} |
68 | | - target={props.target || "_self"} |
69 | | - > |
70 | | - {label} |
71 | | - </a> |
72 | | - ) : ( |
73 | | - <button |
74 | | - className={buttonClassName} |
75 | | - type={props.type} |
76 | | - onClick={props.onClick} |
77 | | - disabled={props.disabled} |
78 | | - > |
79 | | - {label} |
80 | | - </button> |
81 | | - ); |
| 63 | + const Component = linkProps ? ( |
| 64 | + <Link |
| 65 | + {...linkProps} |
| 66 | + className={buttonClassName} |
| 67 | + ref={ref as React.ForwardedRef<HTMLAnchorElement>} |
| 68 | + > |
| 69 | + {label} |
| 70 | + </Link> |
| 71 | + ) : ( |
| 72 | + <button |
| 73 | + className={buttonClassName} |
| 74 | + type={type} |
| 75 | + onClick={onClick} |
| 76 | + disabled={disabled} |
| 77 | + ref={ref as React.ForwardedRef<HTMLButtonElement>} |
| 78 | + > |
| 79 | + {label} |
| 80 | + </button> |
| 81 | + ); |
82 | 82 |
|
83 | 83 | return Component; |
84 | 84 | }) |
|
0 commit comments