Skip to content

Commit 1f6c0b8

Browse files
committed
!= Action in a button mode has a type=button as default
~ `Action` now has discriminative props
1 parent 7ccf943 commit 1f6c0b8

File tree

4 files changed

+80
-14
lines changed

4 files changed

+80
-14
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
33
The format is based on [EZEZ Changelog](https://ezez.dev/guidelines/changelog/)
44
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
55

6+
## [0.27.6] - 2025-01-05
7+
### Breaking
8+
- `Action` in a button mode has a `type=button` as default
9+
### Dev
10+
- `Action` now has discriminative props
11+
612
## [0.27.5] - 2025-01-05
713
### Fixed
814
- `Input` error state should color text too, should not color prefix and suffix

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-miui",
3-
"version": "0.27.5",
3+
"version": "0.27.6",
44
"author": "Jacek Nowacki",
55
"license": "MIT",
66
"scripts": {

src/components/ui/action/Action.stories.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const meta: Meta = {
1717
type: "text",
1818
},
1919
},
20+
href: { type: "string" },
2021
},
2122
};
2223

@@ -38,8 +39,45 @@ const Primary: Story = {
3839
},
3940
};
4041

42+
const Button: Story = {
43+
args: {
44+
label: "I am a button",
45+
icon: "checkmark",
46+
onClick: () => { alert("Clicked"); },
47+
},
48+
};
49+
50+
const Link: Story = {
51+
args: {
52+
label: "I am a link",
53+
icon: "checkmark",
54+
href: "https://example.com",
55+
},
56+
};
57+
58+
const WithCustomLink: Story = {
59+
args: {
60+
label: "I am a custom link",
61+
icon: "checkmark",
62+
to: "/subpage",
63+
Link: (props) => {
64+
return (
65+
<a
66+
href={props.href}
67+
onClick={(e) => { e.preventDefault(); alert("I'd take you to " + props.href); }}
68+
>
69+
{props.children}
70+
</a>
71+
);
72+
},
73+
},
74+
};
75+
4176
export {
4277
Primary,
78+
Button,
79+
Link,
80+
WithCustomLink,
4381
};
4482

4583
export default meta;

src/components/ui/action/Action.tsx

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from "react";
22
import type { ReactNode } from "react";
33

4+
import { omit } from "@ezez/utils";
5+
46
import type { ICON } from "../../icons/Icon";
57
import type { ThemeCSS } from "../../../theme";
68

@@ -13,19 +15,34 @@ interface LinkProps { // @TODO extract? - same on list item
1315
children: React.ReactNode;
1416
}
1517

16-
interface Props {
18+
type NativeLinkProps = {
1719
/**
1820
* If action should be a native link provide target URL as `href`
1921
*/
20-
href?: string;
22+
href: string;
23+
type?: never;
24+
};
25+
26+
type RouterLinkProps = {
2127
/**
2228
* If action should be a routed link provide target URL as `to` and `Link` component
2329
*/
24-
to?: string;
30+
to: string;
2531
/**
2632
* If action should be a routed link provide target URL as `to` and `Link` component
2733
*/
28-
Link?: React.ComponentClass<LinkProps> | React.FC<LinkProps>;
34+
Link: React.ComponentClass<LinkProps> | React.FC<LinkProps>;
35+
type?: never;
36+
};
37+
38+
type ButtonProps = {
39+
/**
40+
* If action should be a button provide `onClick` handler
41+
*/
42+
type?: React.ComponentProps<typeof Button>["type"];
43+
};
44+
45+
type CommonProps = {
2946
/**
3047
* Standard onClick handler (with no event)
3148
*/
@@ -47,15 +64,20 @@ interface Props {
4764
*/
4865
css?: ThemeCSS;
4966
badge?: ReactNode;
50-
}
67+
};
68+
69+
type Props = (NativeLinkProps | RouterLinkProps | ButtonProps) & CommonProps;
5170

5271
/**
5372
* Action is a round-shaped button or a link, usually used at headers/toolbars.
5473
*
5574
* Its label is displayed below the circular shape.
5675
*/
5776
const Action: React.FC<Props> = (props) => {
58-
const { icon, label, href, to, Link, css, ...restProps } = props;
77+
const { icon, label, css, ..._restProps } = props;
78+
const restProps = omit(
79+
_restProps as unknown as Record<string, string>, ["to", "Link", "href", "type"],
80+
) as Omit<Props, "to" | "Link" | "href" | "type" | "icon" | "label" | "css">;
5981
const maybeCss = css ? { css } : {};
6082

6183
let iconElem: ReactNode = icon;
@@ -72,24 +94,24 @@ const Action: React.FC<Props> = (props) => {
7294
</>
7395
);
7496

75-
if (to) {
76-
if (!Link) {
97+
if ("to" in props) {
98+
if (!("Link" in props)) {
7799
throw new TypeError("`to` prop given without `Link` component");
78100
}
79101

80102
return (
81-
<Link href={to} {...restProps}>
103+
<props.Link href={props.to} {...restProps}>
82104
<Anchor className={props.className} {...maybeCss}>{content}</Anchor>
83-
</Link>
105+
</props.Link>
84106
);
85107
}
86108

87-
if (href) {
88-
return <Anchor href={href} className={props.className} {...restProps} {...maybeCss}>{content}</Anchor>;
109+
if ("href" in props) {
110+
return <Anchor href={props.href} className={props.className} {...restProps} {...maybeCss}>{content}</Anchor>;
89111
}
90112

91113
return (
92-
<Button onClick={props.onClick} className={props.className} {...maybeCss}>
114+
<Button onClick={props.onClick} className={props.className} {...maybeCss} type={props.type ?? "button"}>
93115
{content}
94116
</Button>
95117
);

0 commit comments

Comments
 (0)