Skip to content

Commit 3f37a4f

Browse files
committed
chore: Bump package version to 1.0.230 and add Pill and PersonCard components
- Updated package version in package.json to 1.0.230 - Introduced Pill component with customizable variants and sizes - Added PersonCard component to display user information with social links and actions - Created stories and tests for both Pill and PersonCard components - Updated index.ts and types.ts to include new components and their types
1 parent e3c4fc8 commit 3f37a4f

File tree

12 files changed

+1581
-1
lines changed

12 files changed

+1581
-1
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.229",
3+
"version": "1.0.230",
44
"description": "Programmer Network's official UI library for React",
55
"author": "Aleksandar Grbic - (https://programmer.network)",
66
"publishConfig": {
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
3+
import Pill from "./index";
4+
5+
const meta: Meta<typeof Pill> = {
6+
title: "Components/Core/Pill",
7+
component: Pill,
8+
tags: ["autodocs"],
9+
argTypes: {
10+
variant: {
11+
control: { type: "select" },
12+
options: [
13+
"primary",
14+
"secondary",
15+
"success",
16+
"error",
17+
"warning",
18+
"indigo",
19+
"purple",
20+
"pink"
21+
]
22+
},
23+
size: {
24+
control: { type: "select" },
25+
options: ["small", "medium", "large"]
26+
},
27+
clickable: {
28+
control: { type: "boolean" }
29+
},
30+
disabled: {
31+
control: { type: "boolean" }
32+
}
33+
}
34+
};
35+
36+
export default meta;
37+
type Story = StoryObj<typeof Pill>;
38+
39+
export const Default: Story = {
40+
args: {
41+
children: "Badge"
42+
}
43+
};
44+
45+
export const Primary: Story = {
46+
args: {
47+
children: "Primary",
48+
variant: "primary"
49+
}
50+
};
51+
52+
export const Secondary: Story = {
53+
args: {
54+
children: "Secondary",
55+
variant: "secondary"
56+
}
57+
};
58+
59+
export const Success: Story = {
60+
args: {
61+
children: "Success",
62+
variant: "success"
63+
}
64+
};
65+
66+
export const Error: Story = {
67+
args: {
68+
children: "Error",
69+
variant: "error"
70+
}
71+
};
72+
73+
export const Warning: Story = {
74+
args: {
75+
children: "Warning",
76+
variant: "warning"
77+
}
78+
};
79+
80+
export const Indigo: Story = {
81+
args: {
82+
children: "Indigo",
83+
variant: "indigo"
84+
}
85+
};
86+
87+
export const Purple: Story = {
88+
args: {
89+
children: "Purple",
90+
variant: "purple"
91+
}
92+
};
93+
94+
export const Pink: Story = {
95+
args: {
96+
children: "Pink",
97+
variant: "pink"
98+
}
99+
};
100+
101+
export const Clickable: Story = {
102+
args: {
103+
children: "Clickable",
104+
variant: "primary",
105+
clickable: true,
106+
onClick: () => console.log("Pill clicked!")
107+
}
108+
};
109+
110+
export const Disabled: Story = {
111+
args: {
112+
children: "Disabled",
113+
variant: "primary",
114+
clickable: true,
115+
disabled: true
116+
}
117+
};
118+
119+
export const Sizes: Story = {
120+
render: () => (
121+
<div className='yl:flex yl:items-center yl:gap-4'>
122+
<Pill size='small' variant='primary'>
123+
Small
124+
</Pill>
125+
<Pill size='medium' variant='primary'>
126+
Medium
127+
</Pill>
128+
<Pill size='large' variant='primary'>
129+
Large
130+
</Pill>
131+
</div>
132+
)
133+
};
134+
135+
export const AllVariants: Story = {
136+
render: () => (
137+
<div className='yl:flex yl:flex-wrap yl:gap-2'>
138+
<Pill variant='primary'>Primary</Pill>
139+
<Pill variant='secondary'>Secondary</Pill>
140+
<Pill variant='success'>Success</Pill>
141+
<Pill variant='error'>Error</Pill>
142+
<Pill variant='warning'>Warning</Pill>
143+
<Pill variant='indigo'>Indigo</Pill>
144+
<Pill variant='purple'>Purple</Pill>
145+
<Pill variant='pink'>Pink</Pill>
146+
</div>
147+
)
148+
};
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { fireEvent, render, screen } from "@testing-library/react";
2+
import { vi } from "vitest";
3+
4+
import Pill from "./index";
5+
6+
describe("Pill Component", () => {
7+
it("renders children correctly", () => {
8+
render(<Pill>Test Pill</Pill>);
9+
expect(screen.getByText("Test Pill")).toBeInTheDocument();
10+
});
11+
12+
it("applies default variant and size", () => {
13+
render(<Pill>Default Pill</Pill>);
14+
const pill = screen.getByText("Default Pill");
15+
expect(pill).toHaveClass(
16+
"yl:bg-primary/10",
17+
"yl:text-primary",
18+
"yl:px-2",
19+
"yl:py-1"
20+
);
21+
});
22+
23+
it("applies custom variant correctly", () => {
24+
render(<Pill variant='error'>Error Pill</Pill>);
25+
const pill = screen.getByText("Error Pill");
26+
expect(pill).toHaveClass("yl:bg-red-500/10", "yl:text-red-400");
27+
});
28+
29+
it("applies custom size correctly", () => {
30+
render(<Pill size='large'>Large Pill</Pill>);
31+
const pill = screen.getByText("Large Pill");
32+
expect(pill).toHaveClass("yl:px-3", "yl:py-1.5", "yl:text-sm");
33+
});
34+
35+
it("renders as span when not clickable", () => {
36+
render(<Pill>Non-clickable</Pill>);
37+
const pill = screen.getByText("Non-clickable");
38+
expect(pill.tagName).toBe("SPAN");
39+
expect(pill).not.toHaveClass("yl:cursor-pointer");
40+
});
41+
42+
it("renders as button when clickable", () => {
43+
const handleClick = vi.fn();
44+
render(
45+
<Pill clickable onClick={handleClick}>
46+
Clickable
47+
</Pill>
48+
);
49+
const pill = screen.getByText("Clickable");
50+
expect(pill.tagName).toBe("BUTTON");
51+
expect(pill).toHaveClass("yl:cursor-pointer");
52+
});
53+
54+
it("handles click events correctly", () => {
55+
const handleClick = vi.fn();
56+
render(
57+
<Pill clickable onClick={handleClick}>
58+
Clickable
59+
</Pill>
60+
);
61+
const pill = screen.getByText("Clickable");
62+
63+
fireEvent.click(pill);
64+
expect(handleClick).toHaveBeenCalledTimes(1);
65+
});
66+
67+
it("applies disabled state correctly", () => {
68+
const handleClick = vi.fn();
69+
render(
70+
<Pill clickable disabled onClick={handleClick}>
71+
Disabled
72+
</Pill>
73+
);
74+
const pill = screen.getByText("Disabled");
75+
76+
expect(pill).toBeDisabled();
77+
expect(pill).toHaveClass("yl:cursor-not-allowed", "yl:opacity-50");
78+
79+
fireEvent.click(pill);
80+
expect(handleClick).not.toHaveBeenCalled();
81+
});
82+
83+
it("applies custom className", () => {
84+
render(<Pill className='custom-class'>Custom</Pill>);
85+
const pill = screen.getByText("Custom");
86+
expect(pill).toHaveClass("custom-class");
87+
});
88+
89+
it("applies hover classes when clickable", () => {
90+
render(
91+
<Pill clickable variant='success'>
92+
Hoverable
93+
</Pill>
94+
);
95+
const pill = screen.getByText("Hoverable");
96+
expect(pill).toHaveClass("yl:hover:bg-green-500/20");
97+
});
98+
99+
it("does not apply hover classes when disabled", () => {
100+
render(
101+
<Pill clickable disabled variant='success'>
102+
Disabled
103+
</Pill>
104+
);
105+
const pill = screen.getByText("Disabled");
106+
expect(pill).not.toHaveClass("yl:hover:bg-green-500/20");
107+
});
108+
109+
describe("All variants render correctly", () => {
110+
const variants = [
111+
{
112+
variant: "primary",
113+
bgClass: "yl:bg-primary/10",
114+
textClass: "yl:text-primary"
115+
},
116+
{
117+
variant: "secondary",
118+
bgClass: "yl:bg-muted/10",
119+
textClass: "yl:text-muted"
120+
},
121+
{
122+
variant: "success",
123+
bgClass: "yl:bg-green-500/10",
124+
textClass: "yl:text-green-400"
125+
},
126+
{
127+
variant: "error",
128+
bgClass: "yl:bg-red-500/10",
129+
textClass: "yl:text-red-400"
130+
},
131+
{
132+
variant: "warning",
133+
bgClass: "yl:bg-yellow-500/10",
134+
textClass: "yl:text-yellow-400"
135+
},
136+
{
137+
variant: "indigo",
138+
bgClass: "yl:bg-indigo-500/10",
139+
textClass: "yl:text-indigo-400"
140+
},
141+
{
142+
variant: "purple",
143+
bgClass: "yl:bg-purple-500/10",
144+
textClass: "yl:text-purple-400"
145+
},
146+
{
147+
variant: "pink",
148+
bgClass: "yl:bg-pink-500/10",
149+
textClass: "yl:text-pink-400"
150+
}
151+
] as const;
152+
153+
variants.forEach(({ variant, bgClass, textClass }) => {
154+
it(`renders ${variant} variant correctly`, () => {
155+
render(<Pill variant={variant}>{variant}</Pill>);
156+
const pill = screen.getByText(variant);
157+
expect(pill).toHaveClass(bgClass, textClass);
158+
});
159+
});
160+
});
161+
162+
describe("All sizes render correctly", () => {
163+
const sizes = [
164+
{ size: "small", classes: ["yl:px-2", "yl:py-0.5", "yl:text-xs"] },
165+
{ size: "medium", classes: ["yl:px-2", "yl:py-1", "yl:text-xs"] },
166+
{ size: "large", classes: ["yl:px-3", "yl:py-1.5", "yl:text-sm"] }
167+
] as const;
168+
169+
sizes.forEach(({ size, classes }) => {
170+
it(`renders ${size} size correctly`, () => {
171+
render(<Pill size={size}>{size}</Pill>);
172+
const pill = screen.getByText(size);
173+
classes.forEach(className => {
174+
expect(pill).toHaveClass(className);
175+
});
176+
});
177+
});
178+
});
179+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ReactNode } from "react";
2+
3+
export type PillVariant =
4+
| "primary"
5+
| "secondary"
6+
| "success"
7+
| "error"
8+
| "warning"
9+
| "indigo"
10+
| "purple"
11+
| "pink";
12+
13+
export type PillSize = "small" | "medium" | "large";
14+
15+
export interface IPillProps {
16+
children: ReactNode;
17+
variant?: PillVariant;
18+
size?: PillSize;
19+
clickable?: boolean;
20+
onClick?: (e: React.MouseEvent) => void;
21+
className?: string;
22+
disabled?: boolean;
23+
[key: string]: any;
24+
}

0 commit comments

Comments
 (0)