Skip to content

Commit 6e73f35

Browse files
hriverahdezChristianAlbrecht
authored andcommitted
improve button component API
1 parent 139c69e commit 6e73f35

File tree

6 files changed

+66
-63
lines changed

6 files changed

+66
-63
lines changed

packages/components/src/Button/BaseButton.vue

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,17 @@ export type Variant = (typeof variants)[number];
66
<script setup lang="ts" generic="UNUSED">
77
import { type Component, computed } from "vue";
88
9-
import type { IconName } from "@knime/kds-styles/img/icons/def";
10-
119
import Icon from "../Icon/Icon.vue";
12-
import type { Size } from "../types";
13-
14-
export type BaseButtonProps =
15-
// button with label
16-
| {
17-
// common
18-
variant?: Variant;
19-
size?: Size;
20-
destructive?: boolean;
21-
disabled?: boolean;
22-
23-
// specific
24-
label: string;
25-
leadingIcon?: IconName | null;
26-
trailingIcon?: IconName | null;
27-
28-
// not allowed
29-
icon?: never;
30-
}
31-
// button only with icon
32-
| {
33-
// common
34-
variant?: Variant;
35-
size?: Size;
36-
destructive?: boolean;
37-
disabled?: boolean;
38-
39-
// specific
40-
icon: IconName;
41-
42-
// not allowed
43-
label?: never;
44-
leadingIcon?: never;
45-
trailingIcon?: never;
46-
};
10+
11+
import type { BaseButtonProps } from "./types";
4712
4813
type BaseButtonPropsWithComponent = BaseButtonProps & {
4914
component?: string | Component;
5015
};
5116
5217
const props = withDefaults(defineProps<BaseButtonPropsWithComponent>(), {
18+
label: undefined,
19+
leadingIcon: undefined,
5320
component: "button",
5421
size: "medium",
5522
variant: "filled",
@@ -91,20 +58,13 @@ function onClick(e: MouseEvent) {
9158
:disabled="props.disabled"
9259
@click="onClick($event)"
9360
>
94-
<template v-if="props.label">
95-
<Icon
96-
v-if="props.leadingIcon"
97-
:name="props.leadingIcon"
98-
:size="iconSize"
99-
/>
100-
<span class="label">{{ props.label }}</span>
101-
<Icon
102-
v-if="props.trailingIcon"
103-
:name="props.trailingIcon"
104-
:size="iconSize"
105-
/>
106-
</template>
107-
<Icon v-else-if="props.icon" :name="props.icon" :size="iconSize" />
61+
<Icon v-if="props.leadingIcon" :name="props.leadingIcon" :size="iconSize" />
62+
<span v-if="props.label" class="label">{{ props.label }}</span>
63+
<Icon
64+
v-if="props.trailingIcon && props.label"
65+
:name="props.trailingIcon"
66+
:size="iconSize"
67+
/>
10868
</Component>
10969
</template>
11070

packages/components/src/Button/Button.stories.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ const meta: Meta<typeof Button> = {
4343
control: { type: "select" },
4444
options: [undefined, ...iconNames],
4545
},
46-
icon: {
47-
control: { type: "select" },
48-
options: [undefined, ...iconNames],
49-
},
5046
},
5147
args: {
5248
onClick: fn(),
@@ -119,7 +115,7 @@ export const LeadingAndTrailingIcon: Story = {
119115
export const IconOnly: Story = {
120116
args: {
121117
variant: "outlined",
122-
icon: "ai-general",
118+
leadingIcon: "ai-general",
123119
},
124120
};
125121

@@ -140,7 +136,7 @@ export const AllCombinations: Story = buildAllCombinationsStory({
140136
variant: variants,
141137
disabled: [false, true],
142138
destructive: [false, true],
143-
icon: ["ai-general"],
139+
leadingIcon: ["ai-general"],
144140
},
145141
],
146142
});

packages/components/src/Button/Button.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts" generic="UNUSED">
2-
import BaseButton, { type BaseButtonProps } from "./BaseButton.vue";
2+
import BaseButton from "./BaseButton.vue";
3+
import type { BaseButtonProps } from "./types";
34
45
export type ButtonProps = BaseButtonProps & {}; // intersection needed for Storybook to work
56
const props = defineProps<ButtonProps>();

packages/components/src/Button/LinkButton.stories.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,6 @@ const meta: Meta<typeof LinkButton> = {
8484
control: { type: "select" },
8585
options: [undefined, ...iconNames],
8686
},
87-
icon: {
88-
control: { type: "select" },
89-
options: [undefined, ...iconNames],
90-
},
9187
to: {
9288
control: "text",
9389
description:

packages/components/src/Button/LinkButton.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { computed } from "vue";
33
44
import { resolveNuxtLinkComponent } from "../util/nuxtComponentResolver";
55
6-
import BaseButton, { type BaseButtonProps } from "./BaseButton.vue";
6+
import BaseButton from "./BaseButton.vue";
7+
import type { BaseButtonProps } from "./types";
78
89
export type LinkButtonProps = BaseButtonProps & {
910
// RouterLink props
@@ -38,6 +39,8 @@ export type LinkButtonProps = BaseButtonProps & {
3839
3940
const props = withDefaults(defineProps<LinkButtonProps>(), {
4041
download: undefined,
42+
rel: null,
43+
target: null,
4144
});
4245
4346
const component = computed(() => {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { IconName } from "src/Icon/Icon.vue";
2+
3+
import type { Size } from "../types";
4+
5+
import type { Variant } from "./BaseButton.vue";
6+
7+
type BaseProps = {
8+
variant?: Variant;
9+
size?: Size;
10+
destructive?: boolean;
11+
disabled?: boolean;
12+
};
13+
14+
type WithLabelOrLeadingIcon = BaseProps & {
15+
label: string;
16+
leadingIcon?: IconName;
17+
trailingIcon?: IconName;
18+
};
19+
20+
type EnsureLabelAndTrailingIcon = BaseProps & {
21+
label?: never;
22+
leadingIcon: IconName;
23+
trailingIcon?: never;
24+
};
25+
26+
export type BaseButtonProps =
27+
| WithLabelOrLeadingIcon
28+
| EnsureLabelAndTrailingIcon;
29+
30+
declare function buttonPropTester(p: BaseButtonProps): void;
31+
32+
// supports just label
33+
buttonPropTester({ label: "foo" });
34+
// supports just leading icon
35+
buttonPropTester({ leadingIcon: "ai-general" });
36+
// supports both leading icon and label
37+
buttonPropTester({ leadingIcon: "ai-general", label: "foo" });
38+
// supports label and trailing icon
39+
buttonPropTester({ label: "foo", trailingIcon: "ai-general" });
40+
// supports both leading supports all 3
41+
buttonPropTester({
42+
leadingIcon: "ai-general",
43+
label: "foo",
44+
trailingIcon: "ai-general",
45+
});
46+
// @ts-expect-error - should not allow leading and trailing icons without label
47+
buttonPropTester({ leadingIcon: "ai-general", trailingIcon: "ai-general" });

0 commit comments

Comments
 (0)