Skip to content

Commit 30c6fd4

Browse files
authored
chore: Bump package version to 1.0.227 and enhance Badge and Card components (#34)
- Updated package version in package.json to 1.0.227 - Refactored Badge component styles for improved variant management - Introduced CardBadges component to display badges within Card component - Updated Card component to support actions and badges - Enhanced FormActions component to utilize ConfirmDialog for delete confirmation - Added tests and stories for new ConfirmDialog and CardBadges components
1 parent 7d0dba9 commit 30c6fd4

21 files changed

+1511
-96
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.226",
3+
"version": "1.0.227",
44
"description": "Programmer Network's official UI library for React",
55
"author": "Aleksandar Grbic - (https://programmer.network)",
66
"publishConfig": {

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports[`Badge component > renders correctly - snapshot test 1`] = `
44
<DocumentFragment>
55
<span
6-
class="yl:rounded yl:border yl:border-border yl:px-[3px] yl:py-[1px] yl:text-[10px] yl:text-inherit yl:bg-primary"
6+
class="yl:rounded yl:px-2 yl:py-1 yl:text-sm yl:font-medium yl:bg-primary yl:text-background yl:border-2 yl:border-background/50"
77
>
88
Test Badge
99
</span>

src/Components/Badge/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import { BadgeVariantEnum, IBadgeProps } from "./types";
55

66
const Badge: FC<IBadgeProps> = ({ title, className, variant }) => {
77
const variants = {
8-
[BadgeVariantEnum.FILLED]: "yl:bg-primary",
9-
[BadgeVariantEnum.OUTLINE]: "yl:bg-transparent"
8+
[BadgeVariantEnum.FILLED]:
9+
"yl:bg-primary yl:text-background yl:border-2 yl:border-background/50",
10+
[BadgeVariantEnum.OUTLINE]:
11+
"yl:bg-transparent yl:border-primary yl:text-primary yl:border-2 yl:border-primary/50"
1012
};
1113

12-
const baseClassNames =
13-
"yl:rounded yl:border yl:border-border yl:px-[3px] yl:py-[1px] yl:text-[10px] yl:text-inherit";
14+
const baseClassNames = "yl:rounded yl:px-2 yl:py-1 yl:text-sm yl:font-medium";
1415

1516
const variantClassNames = variant ? variants[variant] : "";
1617

src/Components/Card/Card.stories.tsx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Meta, StoryObj } from "@storybook/react-vite";
22
import { NavLink } from "react-router-dom";
33

44
import Card from ".";
5+
import { BadgeVariantEnum } from "../Badge/types";
6+
import Icon from "../Icon";
57
import { ICardData } from "./types";
68

79
const action =
@@ -79,6 +81,10 @@ const defaultData: ICardData = {
7981
aspectRatio: "16:9",
8082
lazy: false
8183
},
84+
badges: [
85+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
86+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE }
87+
],
8288
tags: [
8389
{ name: "nature", url: "/tags/nature" },
8490
{ name: "landscape", url: "/tags/landscape" },
@@ -338,4 +344,169 @@ export const CompactWithActions: Story = {
338344
}
339345
};
340346

347+
export const WithActions: Story = {
348+
args: {
349+
...Default.args,
350+
actions: [
351+
{
352+
label: "Edit",
353+
onClick: e => {
354+
action("edit-clicked")(e);
355+
window.alert("Edit action clicked");
356+
},
357+
variant: "primary",
358+
icon: <Icon iconName='IconEdit' className='w-4 h-4' />
359+
},
360+
{
361+
label: "Delete",
362+
onClick: e => {
363+
action("delete-clicked")(e);
364+
window.alert("Delete action clicked");
365+
},
366+
variant: "danger",
367+
icon: <Icon iconName='IconDeleteBin' className='w-4 h-4' />
368+
}
369+
]
370+
}
371+
};
372+
373+
export const EditOnlyAction: Story = {
374+
args: {
375+
...Default.args,
376+
actions: [
377+
{
378+
label: "Edit",
379+
onClick: e => {
380+
action("edit-clicked")(e);
381+
window.alert("Edit action clicked");
382+
},
383+
variant: "primary",
384+
icon: <Icon iconName='IconEdit' className='w-4 h-4' />
385+
}
386+
]
387+
}
388+
};
389+
390+
export const WithActionsNoImage: Story = {
391+
args: {
392+
...Default.args,
393+
data: {
394+
...defaultData,
395+
image: undefined
396+
},
397+
actions: [
398+
{
399+
label: "Edit",
400+
onClick: e => {
401+
action("edit-clicked")(e);
402+
window.alert("Edit action clicked");
403+
},
404+
variant: "primary",
405+
icon: <Icon iconName='IconEdit' className='w-4 h-4' />
406+
},
407+
{
408+
label: "Delete",
409+
onClick: e => {
410+
action("delete-clicked")(e);
411+
window.alert("Delete action clicked");
412+
},
413+
variant: "danger",
414+
icon: <Icon iconName='IconDeleteBin' className='w-4 h-4' />
415+
}
416+
]
417+
}
418+
};
419+
420+
export const EditOnlyActionNoImage: Story = {
421+
args: {
422+
...Default.args,
423+
data: {
424+
...defaultData,
425+
image: undefined
426+
},
427+
actions: [
428+
{
429+
label: "Edit",
430+
onClick: e => {
431+
action("edit-clicked")(e);
432+
window.alert("Edit action clicked");
433+
},
434+
variant: "primary",
435+
icon: <Icon iconName='IconEdit' className='w-4 h-4' />
436+
}
437+
]
438+
}
439+
};
440+
441+
export const WithBadges: Story = {
442+
args: {
443+
...Default.args,
444+
data: {
445+
...defaultData,
446+
badges: [
447+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
448+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE },
449+
{
450+
title: "Trending",
451+
variant: BadgeVariantEnum.FILLED,
452+
className: "yl:bg-green-500 yl:text-white"
453+
}
454+
]
455+
}
456+
}
457+
};
458+
459+
export const WithManyBadges: Story = {
460+
args: {
461+
...Default.args,
462+
data: {
463+
...defaultData,
464+
badges: [
465+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
466+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE },
467+
{ title: "Trending", variant: BadgeVariantEnum.FILLED },
468+
{
469+
title: "New",
470+
variant: BadgeVariantEnum.FILLED,
471+
className: "yl:bg-blue-500 yl:text-white"
472+
},
473+
{ title: "Popular", variant: BadgeVariantEnum.OUTLINE },
474+
{
475+
title: "Verified",
476+
variant: BadgeVariantEnum.FILLED,
477+
className: "yl:bg-green-600 yl:text-white"
478+
}
479+
]
480+
}
481+
}
482+
};
483+
484+
export const BadgesWithoutImage: Story = {
485+
args: {
486+
...Default.args,
487+
data: {
488+
...defaultData,
489+
image: undefined,
490+
badges: [
491+
{ title: "Featured", variant: BadgeVariantEnum.FILLED },
492+
{ title: "Staff Pick", variant: BadgeVariantEnum.OUTLINE }
493+
]
494+
}
495+
}
496+
};
497+
498+
export const BadgesOnly: Story = {
499+
args: {
500+
...Default.args,
501+
data: {
502+
...defaultData,
503+
tags: undefined,
504+
badges: [
505+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
506+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE }
507+
]
508+
}
509+
}
510+
};
511+
341512
export default CardStories;

src/Components/Card/Card.test.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BrowserRouter, NavLink } from "react-router-dom";
33
import { vi } from "vitest";
44

55
import Card from ".";
6+
import { BadgeVariantEnum } from "../Badge/types";
67
import { ICardData } from "./types";
78

89
const mockNavLink = NavLink;
@@ -101,6 +102,53 @@ describe("Card component", () => {
101102
expect(screen.getByText("#typescript")).toBeInTheDocument();
102103
});
103104

105+
it("renders badges when provided", () => {
106+
const cardWithBadges: ICardData = {
107+
...fullCardData,
108+
badges: [
109+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
110+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE }
111+
]
112+
};
113+
114+
renderCard({ data: cardWithBadges });
115+
116+
expect(screen.getByText("Premium")).toBeInTheDocument();
117+
expect(screen.getByText("Editor's Choice")).toBeInTheDocument();
118+
});
119+
120+
it("renders multiple badges correctly", () => {
121+
const cardWithManyBadges: ICardData = {
122+
...fullCardData,
123+
badges: [
124+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
125+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE },
126+
{ title: "Trending", variant: BadgeVariantEnum.FILLED },
127+
{ title: "New", variant: BadgeVariantEnum.OUTLINE }
128+
]
129+
};
130+
131+
renderCard({ data: cardWithManyBadges });
132+
133+
expect(screen.getByText("Premium")).toBeInTheDocument();
134+
expect(screen.getByText("Editor's Choice")).toBeInTheDocument();
135+
expect(screen.getByText("Trending")).toBeInTheDocument();
136+
expect(screen.getByText("New")).toBeInTheDocument();
137+
});
138+
139+
it("renders card without badges when none provided", () => {
140+
const cardWithoutBadges: ICardData = {
141+
...fullCardData,
142+
badges: undefined
143+
};
144+
145+
renderCard({ data: cardWithoutBadges });
146+
147+
// Should not render any badges
148+
expect(screen.queryByText("Premium")).not.toBeInTheDocument();
149+
expect(screen.queryByText("Editor's Choice")).not.toBeInTheDocument();
150+
});
151+
104152
it("renders image when provided", () => {
105153
renderCard({ data: fullCardData });
106154

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { render, screen } from "@testing-library/react";
2+
3+
import { BadgeVariantEnum } from "../../Badge/types";
4+
import { IBadge } from "../types";
5+
import CardBadges from "./CardBadges";
6+
7+
describe("CardBadges component", () => {
8+
const mockBadges: IBadge[] = [
9+
{ title: "Premium", variant: BadgeVariantEnum.FILLED },
10+
{ title: "Editor's Choice", variant: BadgeVariantEnum.OUTLINE },
11+
{ title: "Custom", className: "custom-class" }
12+
];
13+
14+
it("renders badges correctly", () => {
15+
render(<CardBadges badges={mockBadges} />);
16+
17+
expect(screen.getByText("Premium")).toBeInTheDocument();
18+
expect(screen.getByText("Editor's Choice")).toBeInTheDocument();
19+
expect(screen.getByText("Custom")).toBeInTheDocument();
20+
});
21+
22+
it("renders nothing when badges array is empty", () => {
23+
const { container } = render(<CardBadges badges={[]} />);
24+
expect(container.firstChild).toBeNull();
25+
});
26+
27+
it("renders nothing when badges prop is undefined", () => {
28+
const { container } = render(<CardBadges badges={undefined} />);
29+
expect(container.firstChild).toBeNull();
30+
});
31+
32+
it("renders badges with correct variants", () => {
33+
render(<CardBadges badges={mockBadges} />);
34+
35+
const premiumBadge = screen.getByText("Premium");
36+
const editorChoiceBadge = screen.getByText("Editor's Choice");
37+
38+
expect(premiumBadge).toHaveClass("yl:bg-primary");
39+
expect(editorChoiceBadge).toHaveClass("yl:bg-transparent");
40+
});
41+
42+
it("applies custom className to badges", () => {
43+
render(<CardBadges badges={mockBadges} />);
44+
45+
const customBadge = screen.getByText("Custom");
46+
expect(customBadge).toHaveClass("custom-class");
47+
});
48+
49+
it("renders badges in a flex container with proper spacing", () => {
50+
const { container } = render(<CardBadges badges={mockBadges} />);
51+
52+
const badgesContainer = container.firstChild as HTMLElement;
53+
expect(badgesContainer).toHaveClass(
54+
"yl:flex",
55+
"yl:items-center",
56+
"yl:gap-2",
57+
"yl:flex-wrap",
58+
"yl:mt-3",
59+
"yl:mb-1"
60+
);
61+
});
62+
63+
it("handles single badge correctly", () => {
64+
const singleBadge: IBadge[] = [
65+
{ title: "Single", variant: BadgeVariantEnum.FILLED }
66+
];
67+
68+
render(<CardBadges badges={singleBadge} />);
69+
expect(screen.getByText("Single")).toBeInTheDocument();
70+
});
71+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FC } from "react";
2+
3+
import Badge from "../../Badge";
4+
import { IBadge } from "../types";
5+
6+
interface ICardBadges {
7+
badges?: IBadge[];
8+
}
9+
10+
const CardBadges: FC<ICardBadges> = ({ badges }) => {
11+
if (!badges || badges.length === 0) {
12+
return null;
13+
}
14+
15+
return (
16+
<div className='yl:flex yl:items-center yl:gap-2 yl:flex-wrap yl:mt-3 yl:mb-1'>
17+
{badges.map((badge, index) => (
18+
<Badge
19+
key={`${badge.title}-${index}`}
20+
title={badge.title}
21+
variant={badge.variant}
22+
className={badge.className}
23+
/>
24+
))}
25+
</div>
26+
);
27+
};
28+
29+
export default CardBadges;

0 commit comments

Comments
 (0)