Skip to content

Commit 630f544

Browse files
committed
chore: Bump package version to 1.0.218 and enhance Button component
- Update package version in package.json to 1.0.218 - Add new Error variant stories for Button component - Refactor Button component to utilize utility functions for styling - Update Button tests to reflect new styles and functionality - Introduce FormActions component with Save, Cancel, and Delete buttons - Add tests and stories for FormActions component
1 parent 7637024 commit 630f544

File tree

14 files changed

+501
-38
lines changed

14 files changed

+501
-38
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": "@programmer_network/yail",
3-
"version": "1.0.217",
3+
"version": "1.0.218",
44
"description": "Programmer Network's official UI library for React",
55
"author": "Aleksandar Grbic - (https://programmer.network)",
66
"publishConfig": {

src/Components/Button/Button.stories.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,56 @@ export const Primary: Story = {
141141
variant: ButtonVariantEnum.PRIMARY
142142
}
143143
};
144+
145+
export const Error: Story = {
146+
args: {
147+
children: "Delete",
148+
variant: ButtonVariantEnum.ERROR
149+
}
150+
};
151+
152+
export const ErrorLoading: Story = {
153+
args: {
154+
children: "Delete",
155+
variant: ButtonVariantEnum.ERROR,
156+
isLoading: true
157+
}
158+
};
159+
160+
export const ErrorOutlined: Story = {
161+
args: {
162+
children: "Delete",
163+
variant: ButtonVariantEnum.ERROR,
164+
outlined: true
165+
}
166+
};
167+
168+
export const ErrorOutlinedDisabled: Story = {
169+
args: {
170+
children: "Delete",
171+
variant: ButtonVariantEnum.ERROR,
172+
outlined: true,
173+
disabled: true
174+
}
175+
};
176+
177+
export const ErrorOutlinedLoading: Story = {
178+
args: {
179+
children: "Delete",
180+
variant: ButtonVariantEnum.ERROR,
181+
outlined: true,
182+
isLoading: true
183+
}
184+
};
185+
186+
export const ErrorOutlinedWithIcon: Story = {
187+
args: {
188+
children: "Delete",
189+
variant: ButtonVariantEnum.ERROR,
190+
outlined: true,
191+
icon: {
192+
iconName: "IconDeleteBin",
193+
iconPosition: "left"
194+
}
195+
}
196+
};

src/Components/Button/Button.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ describe("Button component", () => {
4343
test("has correct styles when disabled and filled", () => {
4444
render(<Button disabled>Disabled Button</Button>);
4545
const button = screen.getByRole("button");
46-
expect(button).toHaveClass("yl:text-background");
4746
expect(button).toHaveClass("yl:bg-primary");
4847
});
4948

5049
test("has correct styles when loading and filled", () => {
5150
render(<Button isLoading>Button</Button>);
5251
const button = screen.getByRole("button");
53-
expect(button).toHaveClass("yl:hover:bg-transparent");
52+
expect(button).toHaveClass("yl:hover:fill-background");
5453
});
5554

5655
test("has correct styles when not disabled, not loading, and filled", () => {

src/Components/Button/Button.utils.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import classNames from "classnames";
2+
3+
import { ButtonVariantEnum, IGetButtonClassesArgs } from "./types";
4+
5+
export const getStyles = (isLoading: boolean) => {
6+
return {
7+
base: "yl:select-none yl:px-3 yl:py-2 yl:font-semibold yl:tracking-tight yl:rounded-md yl:border-2",
8+
disabled: "yl:cursor-not-allowed yl:opacity-70",
9+
enabled: classNames({
10+
"yl:cursor-pointer": !isLoading
11+
}),
12+
primary: {
13+
default: "yl:border-primary yl:bg-primary yl:text-background",
14+
outlined:
15+
"yl:border-primary yl:bg-transparent yl:text-primary yl:fill-primary",
16+
hover: classNames({
17+
"yl:hover:bg-transparent yl:hover:text-primary": !isLoading,
18+
"yl:hover:fill-background": isLoading
19+
}),
20+
hoverOutlined: classNames({
21+
"yl:hover:bg-primary yl:hover:text-background yl:hover:fill-background":
22+
!isLoading
23+
})
24+
},
25+
error: {
26+
default: "yl:border-error yl:bg-error yl:text-background yl:fill-bg",
27+
outlined: "yl:border-error yl:bg-transparent yl:text-error yl:fill-error",
28+
hover: classNames({
29+
"yl:hover:bg-transparent yl:hover:text-error yl:hover:fill-error":
30+
!isLoading
31+
}),
32+
hoverOutlined: classNames({
33+
"yl:hover:bg-error yl:hover:text-background yl:hover:fill-background":
34+
!isLoading
35+
})
36+
},
37+
secondary: {
38+
default: "yl:border-text/20 yl:text-text/20 yl:fill-text/20",
39+
hover: classNames({
40+
"yl:hover:border-text/30 yl:hover:text-text/30 yl:hover:fill-text/30 yl:hover:bg-background/20":
41+
!isLoading
42+
})
43+
}
44+
};
45+
};
46+
47+
export const getVariantClasses = (
48+
variant: ButtonVariantEnum,
49+
disabled: boolean,
50+
outlined: boolean,
51+
isLoading: boolean
52+
) => {
53+
const styles = getStyles(isLoading);
54+
55+
if (variant === ButtonVariantEnum.PRIMARY) {
56+
return outlined
57+
? `${styles.primary.outlined} ${!disabled && styles.primary.hoverOutlined}`
58+
: `${styles.primary.default} ${!disabled && styles.primary.hover}`;
59+
} else if (variant === ButtonVariantEnum.ERROR) {
60+
return outlined
61+
? `${styles.error.outlined} ${!disabled && styles.error.hoverOutlined}`
62+
: `${styles.error.default} ${!disabled && styles.error.hover}`;
63+
} else if (variant === ButtonVariantEnum.SECONDARY) {
64+
return `${styles.secondary.default} ${!disabled && styles.secondary.hover}`;
65+
}
66+
67+
return "";
68+
};
69+
70+
export const getButtonClasses = (args: IGetButtonClassesArgs) => {
71+
const { variant, disabled, outlined, isLoading, className } = args;
72+
73+
const styles = getStyles(isLoading);
74+
75+
return classNames(
76+
styles.base,
77+
disabled ? styles.disabled : styles.enabled,
78+
getVariantClasses(variant, disabled, outlined, isLoading),
79+
className
80+
);
81+
};

src/Components/Button/__snapshots__/Button.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports[`Button component > renders correctly - snapshot test 1`] = `
44
<DocumentFragment>
55
<button
6-
class="yl:select-none yl:px-3 yl:py-2 yl:font-semibold yl:tracking-tight yl:rounded-md yl:cursor-pointer yl:border-2 yl:border-primary yl:bg-primary yl:hover:text-primary yl:hover:bg-transparent yl:hover:text-primary yl:hover:fill-primary yl:text-background"
6+
class="yl:select-none yl:px-3 yl:py-2 yl:font-semibold yl:tracking-tight yl:rounded-md yl:border-2 yl:cursor-pointer yl:border-primary yl:bg-primary yl:text-background yl:hover:bg-transparent yl:hover:text-primary"
77
type="button"
88
>
99
<div

src/Components/Button/index.tsx

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import classNames from "classnames";
33
import Icon from "Components/Icon";
44
import Spinner from "Components/Spinner";
55

6+
import { getButtonClasses } from "./Button.utils";
67
import { ButtonVariantEnum, IButtonProps } from "./types";
78

89
const Button: React.FC<IButtonProps> = (
@@ -26,40 +27,18 @@ const Button: React.FC<IButtonProps> = (
2627
variant: ButtonVariantEnum.PRIMARY
2728
}
2829
) => {
29-
let cls =
30-
"yl:select-none yl:px-3 yl:py-2 yl:font-semibold yl:tracking-tight yl:rounded-md yl:cursor-pointer ";
31-
32-
if (variant === ButtonVariantEnum.PRIMARY) {
33-
cls += "yl:border-2 yl:border-primary ";
34-
}
35-
36-
if (disabled) {
37-
cls += "yl:cursor-not-allowed yl:opacity-70 ";
38-
if (variant === ButtonVariantEnum.SECONDARY) {
39-
cls += "yl:text-text ";
40-
} else if (outlined) {
41-
cls += "yl:text-primary ";
42-
} else {
43-
cls += "yl:text-background yl:bg-primary ";
44-
}
45-
} else {
46-
if (variant === ButtonVariantEnum.SECONDARY) {
47-
cls += "yl:text-text yl:hover:opacity-80 ";
48-
} else if (outlined) {
49-
cls +=
50-
"yl:bg-transparent yl:text-primary yl:fill-primary yl:hover:bg-primary yl:hover:fill-background yl:hover:text-background";
51-
} else {
52-
cls +=
53-
"yl:bg-primary yl:hover:text-primary yl:hover:bg-transparent yl:hover:text-primary yl:hover:fill-primary yl:text-background";
54-
}
55-
}
56-
5730
return (
5831
<button
5932
disabled={disabled}
6033
onClick={onClick}
6134
type={type}
62-
className={classNames(cls, className)}
35+
className={getButtonClasses({
36+
variant,
37+
disabled,
38+
outlined,
39+
className,
40+
isLoading
41+
})}
6342
>
6443
<div className='yl:relative yl:flex yl:items-center yl:justify-center'>
6544
<span

src/Components/Button/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { IconName } from "Components/Icons/types";
44

55
export enum ButtonVariantEnum {
66
PRIMARY = "primary",
7-
SECONDARY = "secondary"
7+
SECONDARY = "secondary",
8+
ERROR = "error"
89
}
910

1011
export interface IButtonProps {
@@ -22,3 +23,11 @@ export interface IButtonProps {
2223
};
2324
variant?: ButtonVariantEnum;
2425
}
26+
27+
export interface IGetButtonClassesArgs {
28+
variant: ButtonVariantEnum;
29+
disabled: boolean;
30+
outlined: boolean;
31+
isLoading: boolean;
32+
className?: string;
33+
}

src/Components/Dialog/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const Dialog = forwardRef<HTMLDialogElement, IDialogProps>(
7575
<dialog
7676
ref={ref}
7777
className={classNames(
78-
"yl:z-50 yl:rounded-lg yl:bg-text/5 yl:shadow-md yl:border-2 yl:border-border",
78+
"yl:z-50 yl:rounded-lg yl:bg-background yl:shadow-md yl:border yl:border-text/5",
7979
className
8080
)}
8181
onClick={e => e.stopPropagation()}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import FormActions from ".";
2+
3+
export default {
4+
title: "FormActions",
5+
parameters: {
6+
layout: "centered"
7+
},
8+
component: FormActions
9+
};
10+
11+
export const Default = () => {
12+
const handleSave = () => {
13+
return new Promise(resolve => {
14+
setTimeout(() => {
15+
resolve(true);
16+
}, 3000);
17+
});
18+
};
19+
20+
const handleCancel = () => {
21+
console.log("Cancel");
22+
};
23+
24+
const handleDelete = async () => {
25+
return new Promise(resolve => {
26+
setTimeout(() => {
27+
resolve(true);
28+
}, 3000);
29+
});
30+
};
31+
32+
return (
33+
<div>
34+
<FormActions
35+
onSave={handleSave}
36+
onCancel={handleCancel}
37+
onDelete={handleDelete}
38+
/>
39+
</div>
40+
);
41+
};
42+
43+
export const NoDelete = () => {
44+
return (
45+
<div>
46+
<FormActions onSave={() => {}} onCancel={() => {}} />
47+
</div>
48+
);
49+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { fireEvent, render, screen } from "@testing-library/react";
2+
import { vi } from "vitest";
3+
4+
import FormActions from ".";
5+
6+
describe("FormActions component", () => {
7+
test("renders correctly - snapshot test", () => {
8+
const { asFragment } = render(
9+
<FormActions onSave={vi.fn()} onCancel={vi.fn()} onDelete={vi.fn()} />
10+
);
11+
expect(asFragment()).toMatchSnapshot();
12+
});
13+
14+
test("renders all three buttons: Save, Cancel, and Delete", () => {
15+
render(<FormActions onSave={vi.fn()} onCancel={vi.fn()} />);
16+
17+
expect(screen.getByText("Save")).toBeInTheDocument();
18+
expect(screen.getByText("Cancel")).toBeInTheDocument();
19+
});
20+
21+
test("calls onSave function when Save button is clicked", () => {
22+
const handleSave = vi.fn();
23+
render(
24+
<FormActions onSave={handleSave} onCancel={vi.fn()} onDelete={vi.fn()} />
25+
);
26+
27+
fireEvent.click(screen.getByText("Save"));
28+
expect(handleSave).toHaveBeenCalledTimes(1);
29+
});
30+
31+
test("calls onCancel function when Cancel button is clicked", () => {
32+
const handleCancel = vi.fn();
33+
render(
34+
<FormActions
35+
onSave={vi.fn()}
36+
onCancel={handleCancel}
37+
onDelete={vi.fn()}
38+
/>
39+
);
40+
41+
fireEvent.click(screen.getByText("Cancel"));
42+
expect(handleCancel).toHaveBeenCalledTimes(1);
43+
});
44+
});

0 commit comments

Comments
 (0)