@yusufalperendumlu/component-library 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.storybook/main.ts +1 -1
  2. package/.storybook/preview.ts +2 -2
  3. package/dist/cjs/index.css +1 -1
  4. package/dist/cjs/index.css.map +1 -1
  5. package/dist/cjs/index.js +1 -1
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/esm/index.css +1 -1
  8. package/dist/esm/index.css.map +1 -1
  9. package/dist/esm/index.js +1 -1
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/index.d.ts +63 -7
  12. package/dist/tailwind.css +1 -1
  13. package/eslint.config.js +23 -0
  14. package/jest.config.ts +13 -14
  15. package/package.json +18 -2
  16. package/postcss.config.js +6 -6
  17. package/prettier.config.js +84 -0
  18. package/src/components/Autocomplete/Autocomplete.stories.tsx +65 -0
  19. package/src/components/Autocomplete/Autocomplete.tsx +127 -0
  20. package/src/components/Autocomplete/Autocomplete.types.ts +14 -0
  21. package/src/components/Autocomplete/index.ts +3 -0
  22. package/src/components/Button/Button.stories.tsx +52 -52
  23. package/src/components/Button/Button.tsx +55 -50
  24. package/src/components/Button/Button.types.ts +7 -7
  25. package/src/components/Button/index.ts +3 -3
  26. package/src/components/Dialog/Dialog.stories.tsx +102 -0
  27. package/src/components/Dialog/Dialog.tsx +90 -0
  28. package/src/components/Dialog/Dialog.types.ts +7 -0
  29. package/src/components/Dialog/index.ts +3 -0
  30. package/src/components/Input/Input.stories.tsx +34 -0
  31. package/src/components/Input/Input.tsx +31 -9
  32. package/src/components/Input/Input.types.ts +6 -0
  33. package/src/components/Input/index.ts +3 -0
  34. package/src/components/Table/Table.stories.tsx +53 -0
  35. package/src/components/Table/Table.tsx +104 -0
  36. package/src/components/Table/Table.types.ts +13 -0
  37. package/src/components/Table/index.ts +3 -0
  38. package/src/components/Toast/Toast.stories.tsx +44 -0
  39. package/src/components/Toast/Toast.tsx +70 -0
  40. package/src/components/Toast/Toast.types.ts +7 -0
  41. package/src/components/Toast/icons.tsx +9 -0
  42. package/src/components/Toast/index.ts +0 -0
  43. package/src/components/index.ts +5 -2
  44. package/src/index.ts +3 -3
  45. package/src/styles/tailwind.css +124 -124
  46. package/src/tests/Autocomplete.test.tsx +81 -0
  47. package/src/tests/Button.test.tsx +48 -48
  48. package/src/tests/Dialog.test.tsx +86 -0
  49. package/src/tests/Input.test.tsx +42 -0
  50. package/src/tests/Table.test.tsx +55 -0
  51. package/src/tests/styleMock.ts +1 -1
  52. package/tailwind.config.js +3 -2
  53. package/tsconfig.json +20 -30
  54. package/src/components/Input/index.tsx +0 -3
@@ -0,0 +1,70 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { cva } from 'class-variance-authority';
3
+ import { IoClose } from 'react-icons/io5';
4
+ import clsx from 'clsx';
5
+
6
+ import { toastIcons } from './icons';
7
+ import { ToastProps } from './Toast.types';
8
+
9
+ const toastVariants = cva(
10
+ 'flex items-center justify-between w-full max-w-xs px-4 py-2 rounded-md shadow-md transition-opacity duration-300',
11
+ {
12
+ variants: {
13
+ type: {
14
+ success: 'bg-green-500 text-white',
15
+ error: 'bg-red-500 text-white',
16
+ },
17
+ position: {
18
+ 'top-left': 'fixed top-4 left-4',
19
+ 'top-right': 'fixed top-4 right-4',
20
+ 'bottom-left': 'fixed bottom-4 left-4',
21
+ 'bottom-right': 'fixed bottom-4 right-4',
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ type: 'success',
26
+ position: 'top-right',
27
+ },
28
+ }
29
+ );
30
+
31
+ const Toast: React.FC<ToastProps> = ({
32
+ title,
33
+ description,
34
+ duration,
35
+ type,
36
+ position,
37
+ }) => {
38
+ const [isOpen, setIsOpen] = useState(true);
39
+
40
+ const handleClose = () => {
41
+ setIsOpen(false);
42
+ };
43
+
44
+ useEffect(() => {
45
+ if (!duration) return;
46
+
47
+ const timer = setTimeout(() => {
48
+ handleClose();
49
+ }, duration);
50
+
51
+ return () => clearTimeout(timer);
52
+ }, [duration]);
53
+
54
+ if (!isOpen) return null;
55
+
56
+ return (
57
+ <div className={clsx(toastVariants({ type, position }))}>
58
+ <span>{toastIcons[type]}</span>
59
+ <div className='flex flex-col ml-2 flex-1'>
60
+ <span className='font-semibold'>{title}</span>
61
+ <span className='text-sm'>{description}</span>
62
+ </div>
63
+ <button onClick={handleClose} className='ml-4'>
64
+ <IoClose size={16} />
65
+ </button>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default Toast;
@@ -0,0 +1,7 @@
1
+ export type ToastProps = {
2
+ title: string;
3
+ description: string;
4
+ duration?: number;
5
+ type: 'success' | 'error';
6
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ };
@@ -0,0 +1,9 @@
1
+ import { TiTick } from 'react-icons/ti';
2
+ import { FaTriangleExclamation } from 'react-icons/fa6';
3
+
4
+ export const toastIcons = {
5
+ success: (
6
+ <TiTick className='text-green-500 h-6 w-6 bg-white p-1 rounded-full text-sm mr-2' />
7
+ ),
8
+ error: <FaTriangleExclamation className='text-white text-lg mr-2' />,
9
+ };
File without changes
@@ -1,2 +1,5 @@
1
- export { default as Button } from "./Button";
2
- export { default as Input } from "./Input";
1
+ export { default as Autocomplete } from './Autocomplete';
2
+ export { default as Button } from './Button';
3
+ export { default as Dialog } from './Dialog';
4
+ export { default as Input } from './Input';
5
+ export { default as Table } from './Table';
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- import "./styles/tailwind.css"
2
-
3
- export * from "./components";
1
+ import './styles/tailwind.css';
2
+
3
+ export * from './components';
@@ -1,124 +1,124 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
- /*
5
- @custom-variant dark (&:is(.dark *));
6
-
7
- :root {
8
- --background: oklch(1 0 0);
9
- --foreground: oklch(0.145 0 0);
10
- --card: oklch(1 0 0);
11
- --card-foreground: oklch(0.145 0 0);
12
- --popover: oklch(1 0 0);
13
- --popover-foreground: oklch(0.145 0 0);
14
- --primary: oklch(0.205 0 0);
15
- --primary-foreground: oklch(0.985 0 0);
16
- --secondary: oklch(0.97 0 0);
17
- --secondary-foreground: oklch(0.205 0 0);
18
- --muted: oklch(0.97 0 0);
19
- --muted-foreground: oklch(0.556 0 0);
20
- --accent: oklch(0.97 0 0);
21
- --accent-foreground: oklch(0.205 0 0);
22
- --destructive: oklch(0.577 0.245 27.325);
23
- --destructive-foreground: oklch(0.577 0.245 27.325);
24
- --border: oklch(0.922 0 0);
25
- --input: oklch(0.922 0 0);
26
- --ring: oklch(0.708 0 0);
27
- --chart-1: oklch(0.646 0.222 41.116);
28
- --chart-2: oklch(0.6 0.118 184.704);
29
- --chart-3: oklch(0.398 0.07 227.392);
30
- --chart-4: oklch(0.828 0.189 84.429);
31
- --chart-5: oklch(0.769 0.188 70.08);
32
- --radius: 0.625rem;
33
- --sidebar: oklch(0.985 0 0);
34
- --sidebar-foreground: oklch(0.145 0 0);
35
- --sidebar-primary: oklch(0.205 0 0);
36
- --sidebar-primary-foreground: oklch(0.985 0 0);
37
- --sidebar-accent: oklch(0.97 0 0);
38
- --sidebar-accent-foreground: oklch(0.205 0 0);
39
- --sidebar-border: oklch(0.922 0 0);
40
- --sidebar-ring: oklch(0.708 0 0);
41
- }
42
-
43
- .dark {
44
- --background: oklch(0.145 0 0);
45
- --foreground: oklch(0.985 0 0);
46
- --card: oklch(0.145 0 0);
47
- --card-foreground: oklch(0.985 0 0);
48
- --popover: oklch(0.145 0 0);
49
- --popover-foreground: oklch(0.985 0 0);
50
- --primary: oklch(0.985 0 0);
51
- --primary-foreground: oklch(0.205 0 0);
52
- --secondary: oklch(0.269 0 0);
53
- --secondary-foreground: oklch(0.985 0 0);
54
- --muted: oklch(0.269 0 0);
55
- --muted-foreground: oklch(0.708 0 0);
56
- --accent: oklch(0.269 0 0);
57
- --accent-foreground: oklch(0.985 0 0);
58
- --destructive: oklch(0.396 0.141 25.723);
59
- --destructive-foreground: oklch(0.637 0.237 25.331);
60
- --border: oklch(0.269 0 0);
61
- --input: oklch(0.269 0 0);
62
- --ring: oklch(0.556 0 0);
63
- --chart-1: oklch(0.488 0.243 264.376);
64
- --chart-2: oklch(0.696 0.17 162.48);
65
- --chart-3: oklch(0.769 0.188 70.08);
66
- --chart-4: oklch(0.627 0.265 303.9);
67
- --chart-5: oklch(0.645 0.246 16.439);
68
- --sidebar: oklch(0.205 0 0);
69
- --sidebar-foreground: oklch(0.985 0 0);
70
- --sidebar-primary: oklch(0.488 0.243 264.376);
71
- --sidebar-primary-foreground: oklch(0.985 0 0);
72
- --sidebar-accent: oklch(0.269 0 0);
73
- --sidebar-accent-foreground: oklch(0.985 0 0);
74
- --sidebar-border: oklch(0.269 0 0);
75
- --sidebar-ring: oklch(0.439 0 0);
76
- }
77
-
78
- @theme inline {
79
- --color-background: var(--background);
80
- --color-foreground: var(--foreground);
81
- --color-card: var(--card);
82
- --color-card-foreground: var(--card-foreground);
83
- --color-popover: var(--popover);
84
- --color-popover-foreground: var(--popover-foreground);
85
- --color-primary: var(--primary);
86
- --color-primary-foreground: var(--primary-foreground);
87
- --color-secondary: var(--secondary);
88
- --color-secondary-foreground: var(--secondary-foreground);
89
- --color-muted: var(--muted);
90
- --color-muted-foreground: var(--muted-foreground);
91
- --color-accent: var(--accent);
92
- --color-accent-foreground: var(--accent-foreground);
93
- --color-destructive: var(--destructive);
94
- --color-destructive-foreground: var(--destructive-foreground);
95
- --color-border: var(--border);
96
- --color-input: var(--input);
97
- --color-ring: var(--ring);
98
- --color-chart-1: var(--chart-1);
99
- --color-chart-2: var(--chart-2);
100
- --color-chart-3: var(--chart-3);
101
- --color-chart-4: var(--chart-4);
102
- --color-chart-5: var(--chart-5);
103
- --radius-sm: calc(var(--radius) - 4px);
104
- --radius-md: calc(var(--radius) - 2px);
105
- --radius-lg: var(--radius);
106
- --radius-xl: calc(var(--radius) + 4px);
107
- --color-sidebar: var(--sidebar);
108
- --color-sidebar-foreground: var(--sidebar-foreground);
109
- --color-sidebar-primary: var(--sidebar-primary);
110
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
111
- --color-sidebar-accent: var(--sidebar-accent);
112
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
113
- --color-sidebar-border: var(--sidebar-border);
114
- --color-sidebar-ring: var(--sidebar-ring);
115
- }
116
-
117
- @layer base {
118
- * {
119
- @apply border-border outline-ring/50;
120
- }
121
- body {
122
- @apply bg-background text-foreground;
123
- }
124
- } */
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+ /*
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ :root {
8
+ --background: oklch(1 0 0);
9
+ --foreground: oklch(0.145 0 0);
10
+ --card: oklch(1 0 0);
11
+ --card-foreground: oklch(0.145 0 0);
12
+ --popover: oklch(1 0 0);
13
+ --popover-foreground: oklch(0.145 0 0);
14
+ --primary: oklch(0.205 0 0);
15
+ --primary-foreground: oklch(0.985 0 0);
16
+ --secondary: oklch(0.97 0 0);
17
+ --secondary-foreground: oklch(0.205 0 0);
18
+ --muted: oklch(0.97 0 0);
19
+ --muted-foreground: oklch(0.556 0 0);
20
+ --accent: oklch(0.97 0 0);
21
+ --accent-foreground: oklch(0.205 0 0);
22
+ --destructive: oklch(0.577 0.245 27.325);
23
+ --destructive-foreground: oklch(0.577 0.245 27.325);
24
+ --border: oklch(0.922 0 0);
25
+ --input: oklch(0.922 0 0);
26
+ --ring: oklch(0.708 0 0);
27
+ --chart-1: oklch(0.646 0.222 41.116);
28
+ --chart-2: oklch(0.6 0.118 184.704);
29
+ --chart-3: oklch(0.398 0.07 227.392);
30
+ --chart-4: oklch(0.828 0.189 84.429);
31
+ --chart-5: oklch(0.769 0.188 70.08);
32
+ --radius: 0.625rem;
33
+ --sidebar: oklch(0.985 0 0);
34
+ --sidebar-foreground: oklch(0.145 0 0);
35
+ --sidebar-primary: oklch(0.205 0 0);
36
+ --sidebar-primary-foreground: oklch(0.985 0 0);
37
+ --sidebar-accent: oklch(0.97 0 0);
38
+ --sidebar-accent-foreground: oklch(0.205 0 0);
39
+ --sidebar-border: oklch(0.922 0 0);
40
+ --sidebar-ring: oklch(0.708 0 0);
41
+ }
42
+
43
+ .dark {
44
+ --background: oklch(0.145 0 0);
45
+ --foreground: oklch(0.985 0 0);
46
+ --card: oklch(0.145 0 0);
47
+ --card-foreground: oklch(0.985 0 0);
48
+ --popover: oklch(0.145 0 0);
49
+ --popover-foreground: oklch(0.985 0 0);
50
+ --primary: oklch(0.985 0 0);
51
+ --primary-foreground: oklch(0.205 0 0);
52
+ --secondary: oklch(0.269 0 0);
53
+ --secondary-foreground: oklch(0.985 0 0);
54
+ --muted: oklch(0.269 0 0);
55
+ --muted-foreground: oklch(0.708 0 0);
56
+ --accent: oklch(0.269 0 0);
57
+ --accent-foreground: oklch(0.985 0 0);
58
+ --destructive: oklch(0.396 0.141 25.723);
59
+ --destructive-foreground: oklch(0.637 0.237 25.331);
60
+ --border: oklch(0.269 0 0);
61
+ --input: oklch(0.269 0 0);
62
+ --ring: oklch(0.556 0 0);
63
+ --chart-1: oklch(0.488 0.243 264.376);
64
+ --chart-2: oklch(0.696 0.17 162.48);
65
+ --chart-3: oklch(0.769 0.188 70.08);
66
+ --chart-4: oklch(0.627 0.265 303.9);
67
+ --chart-5: oklch(0.645 0.246 16.439);
68
+ --sidebar: oklch(0.205 0 0);
69
+ --sidebar-foreground: oklch(0.985 0 0);
70
+ --sidebar-primary: oklch(0.488 0.243 264.376);
71
+ --sidebar-primary-foreground: oklch(0.985 0 0);
72
+ --sidebar-accent: oklch(0.269 0 0);
73
+ --sidebar-accent-foreground: oklch(0.985 0 0);
74
+ --sidebar-border: oklch(0.269 0 0);
75
+ --sidebar-ring: oklch(0.439 0 0);
76
+ }
77
+
78
+ @theme inline {
79
+ --color-background: var(--background);
80
+ --color-foreground: var(--foreground);
81
+ --color-card: var(--card);
82
+ --color-card-foreground: var(--card-foreground);
83
+ --color-popover: var(--popover);
84
+ --color-popover-foreground: var(--popover-foreground);
85
+ --color-primary: var(--primary);
86
+ --color-primary-foreground: var(--primary-foreground);
87
+ --color-secondary: var(--secondary);
88
+ --color-secondary-foreground: var(--secondary-foreground);
89
+ --color-muted: var(--muted);
90
+ --color-muted-foreground: var(--muted-foreground);
91
+ --color-accent: var(--accent);
92
+ --color-accent-foreground: var(--accent-foreground);
93
+ --color-destructive: var(--destructive);
94
+ --color-destructive-foreground: var(--destructive-foreground);
95
+ --color-border: var(--border);
96
+ --color-input: var(--input);
97
+ --color-ring: var(--ring);
98
+ --color-chart-1: var(--chart-1);
99
+ --color-chart-2: var(--chart-2);
100
+ --color-chart-3: var(--chart-3);
101
+ --color-chart-4: var(--chart-4);
102
+ --color-chart-5: var(--chart-5);
103
+ --radius-sm: calc(var(--radius) - 4px);
104
+ --radius-md: calc(var(--radius) - 2px);
105
+ --radius-lg: var(--radius);
106
+ --radius-xl: calc(var(--radius) + 4px);
107
+ --color-sidebar: var(--sidebar);
108
+ --color-sidebar-foreground: var(--sidebar-foreground);
109
+ --color-sidebar-primary: var(--sidebar-primary);
110
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
111
+ --color-sidebar-accent: var(--sidebar-accent);
112
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
113
+ --color-sidebar-border: var(--sidebar-border);
114
+ --color-sidebar-ring: var(--sidebar-ring);
115
+ }
116
+
117
+ @layer base {
118
+ * {
119
+ @apply border-border outline-ring/50;
120
+ }
121
+ body {
122
+ @apply bg-background text-foreground;
123
+ }
124
+ } */
@@ -0,0 +1,81 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ import React from 'react';
4
+ import { render, screen, fireEvent } from '@testing-library/react';
5
+ import Autocomplete from '../components/Autocomplete';
6
+ import {
7
+ Option,
8
+ AutocompleteProps,
9
+ } from '../components/Autocomplete/Autocomplete.types';
10
+
11
+ describe('Autocomplete component', () => {
12
+ const options: Option[] = [
13
+ { id: 1, name: 'Apple' },
14
+ { id: 2, name: 'Banana' },
15
+ { id: 3, name: 'Orange' },
16
+ ];
17
+
18
+ const setup = (props?: Partial<AutocompleteProps>) => {
19
+ const defaultProps: AutocompleteProps = {
20
+ options,
21
+ selected: [],
22
+ setSelected: jest.fn(),
23
+ placeholder: 'Search...',
24
+ showChosen: false,
25
+ ...props,
26
+ };
27
+
28
+ render(<Autocomplete {...defaultProps} />);
29
+ return defaultProps;
30
+ };
31
+
32
+ it('renders input with placeholder', () => {
33
+ setup();
34
+ expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
35
+ });
36
+
37
+ it('filters options based on input', () => {
38
+ setup();
39
+
40
+ const input = screen.getByPlaceholderText('Search...');
41
+ fireEvent.change(input, { target: { value: 'ban' } });
42
+
43
+ expect(screen.getByText('Banana')).toBeInTheDocument();
44
+ expect(screen.queryByText('Apple')).not.toBeInTheDocument();
45
+ });
46
+
47
+ it('calls setSelected on item click (single selection)', () => {
48
+ const mockSetSelected = jest.fn();
49
+ setup({ setSelected: mockSetSelected });
50
+
51
+ const input = screen.getByPlaceholderText('Search...');
52
+ fireEvent.change(input, { target: { value: 'oran' } });
53
+
54
+ const option = screen.getByText('Orange');
55
+ fireEvent.click(option);
56
+
57
+ expect(mockSetSelected).toHaveBeenCalledWith([{ id: 3, name: 'Orange' }]);
58
+ });
59
+
60
+ it('supports multiple selections when showChosen is true', () => {
61
+ const mockSetSelected = jest.fn();
62
+ setup({ setSelected: mockSetSelected, showChosen: true });
63
+
64
+ const input = screen.getByPlaceholderText('Search...');
65
+ fireEvent.change(input, { target: { value: 'app' } });
66
+
67
+ fireEvent.click(screen.getByText('Apple'));
68
+
69
+ expect(mockSetSelected).toHaveBeenCalledWith([{ id: 1, name: 'Apple' }]);
70
+ });
71
+
72
+ it('does not show placeholder if item is selected and showChosen is false', () => {
73
+ setup({
74
+ selected: [{ id: 2, name: 'Banana' }],
75
+ showChosen: false,
76
+ });
77
+
78
+ expect(screen.queryByPlaceholderText('Search...')).not.toBeInTheDocument();
79
+ expect(screen.getByText('Banana')).toBeInTheDocument();
80
+ });
81
+ });
@@ -1,48 +1,48 @@
1
- import "@testing-library/jest-dom";
2
- import { render, screen, fireEvent } from "@testing-library/react";
3
- import Button from "../components/Button";
4
- import type { IButtonProps } from "../components/Button/Button.types";
5
-
6
- describe("Button component", () => {
7
- it("renders with given title", () => {
8
- render(<Button variant="outline" title="Click Me" />);
9
- expect(screen.getByText("Click Me")).toBeInTheDocument();
10
- });
11
-
12
- it("calls onClick when clicked", () => {
13
- const handleClick = jest.fn();
14
- render(<Button variant="outline" title="Click Me" onClick={handleClick} />);
15
- fireEvent.click(screen.getByText("Click Me"));
16
- expect(handleClick).toHaveBeenCalledTimes(1);
17
- });
18
-
19
- it("is disabled when disabled prop is true", () => {
20
- render(<Button variant="danger" title="Disabled" disabled />);
21
- const button = screen.getByText("Disabled") as HTMLButtonElement;
22
- expect(button).toBeDisabled();
23
- });
24
-
25
- it('applies the correct class for variant "primary"', () => {
26
- render(<Button title="Primary" variant="primary" />);
27
- const button = screen.getByText("Primary");
28
- expect(button.className).toMatch(/bg-\[#5876EE\]/); // Tailwind class match
29
- });
30
-
31
- it('applies the correct class for size "large"', () => {
32
- render(<Button variant="primary" title="Large Button" size="large" />);
33
- const button = screen.getByText("Large Button");
34
- expect(button.className).toMatch(/w-full/);
35
- });
36
-
37
- it("allows custom className via props", () => {
38
- render(
39
- <Button
40
- variant="secondary"
41
- title="Custom Class"
42
- className="custom-class"
43
- />
44
- );
45
- const button = screen.getByText("Custom Class");
46
- expect(button.className).toMatch(/custom-class/);
47
- });
48
- });
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import Button from '../components/Button';
4
+ import type { IButtonProps } from '../components/Button/Button.types';
5
+
6
+ describe('Button component', () => {
7
+ it('renders with given title', () => {
8
+ render(<Button variant='outline' title='Click Me' />);
9
+ expect(screen.getByText('Click Me')).toBeInTheDocument();
10
+ });
11
+
12
+ it('calls onClick when clicked', () => {
13
+ const handleClick = jest.fn();
14
+ render(<Button variant='outline' title='Click Me' onClick={handleClick} />);
15
+ fireEvent.click(screen.getByText('Click Me'));
16
+ expect(handleClick).toHaveBeenCalledTimes(1);
17
+ });
18
+
19
+ it('is disabled when disabled prop is true', () => {
20
+ render(<Button variant='danger' title='Disabled' disabled />);
21
+ const button = screen.getByText('Disabled') as HTMLButtonElement;
22
+ expect(button).toBeDisabled();
23
+ });
24
+
25
+ it('applies the correct class for variant "primary"', () => {
26
+ render(<Button title='Primary' variant='primary' />);
27
+ const button = screen.getByText('Primary');
28
+ expect(button.className).toMatch(/bg-\[#5876EE\]/); // Tailwind class match
29
+ });
30
+
31
+ it('applies the correct class for size "large"', () => {
32
+ render(<Button variant='primary' title='Large Button' size='large' />);
33
+ const button = screen.getByText('Large Button');
34
+ expect(button.className).toMatch(/w-full/);
35
+ });
36
+
37
+ it('allows custom className via props', () => {
38
+ render(
39
+ <Button
40
+ variant='secondary'
41
+ title='Custom Class'
42
+ className='custom-class'
43
+ />
44
+ );
45
+ const button = screen.getByText('Custom Class');
46
+ expect(button.className).toMatch(/custom-class/);
47
+ });
48
+ });
@@ -0,0 +1,86 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ import React from 'react';
4
+ import { render, screen, fireEvent } from '@testing-library/react';
5
+ import Dialog from '../components/Dialog';
6
+
7
+ describe('Dialog Component', () => {
8
+ const onCloseMock = jest.fn();
9
+
10
+ beforeEach(() => {
11
+ onCloseMock.mockClear();
12
+ });
13
+
14
+ test('renders children when open', () => {
15
+ render(
16
+ <Dialog isOpen={true} onClose={onCloseMock}>
17
+ <Dialog.Header>Header Text</Dialog.Header>
18
+ <Dialog.Body>Body Text</Dialog.Body>
19
+ <Dialog.Footer>Footer Text</Dialog.Footer>
20
+ </Dialog>
21
+ );
22
+
23
+ expect(screen.getByText('Header Text')).toBeInTheDocument();
24
+ expect(screen.getByText('Body Text')).toBeInTheDocument();
25
+ expect(screen.getByText('Footer Text')).toBeInTheDocument();
26
+ });
27
+
28
+ test('does not render when isOpen is false', () => {
29
+ const { container } = render(
30
+ <Dialog isOpen={false} onClose={onCloseMock}>
31
+ <Dialog.Body>Body Text</Dialog.Body>
32
+ </Dialog>
33
+ );
34
+
35
+ expect(container).toBeEmptyDOMElement();
36
+ });
37
+
38
+ test('calls onClose when clicking the close button', () => {
39
+ render(
40
+ <Dialog isOpen={true} onClose={onCloseMock}>
41
+ <Dialog.Body>Body Text</Dialog.Body>
42
+ </Dialog>
43
+ );
44
+
45
+ const closeButton = screen.getByRole('button');
46
+ fireEvent.click(closeButton);
47
+
48
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
49
+ });
50
+
51
+ test('calls onClose when clicking outside the dialog', () => {
52
+ render(
53
+ <Dialog isOpen={true} onClose={onCloseMock}>
54
+ <Dialog.Body>Body Text</Dialog.Body>
55
+ </Dialog>
56
+ );
57
+
58
+ // dialog içerisine değil, overlay alanına tıklayalım
59
+ fireEvent.mouseDown(document.body); // dialog dışına tıklama simülasyonu
60
+
61
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
62
+ });
63
+
64
+ test('calls onClose when pressing Escape key', () => {
65
+ render(
66
+ <Dialog isOpen={true} onClose={onCloseMock}>
67
+ <Dialog.Body>Body Text</Dialog.Body>
68
+ </Dialog>
69
+ );
70
+
71
+ fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' });
72
+
73
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ test('does not show close icon if showCloseIcon is false', () => {
77
+ render(
78
+ <Dialog isOpen={true} onClose={onCloseMock} showCloseIcon={false}>
79
+ <Dialog.Body>Body Text</Dialog.Body>
80
+ </Dialog>
81
+ );
82
+
83
+ const closeButton = screen.queryByRole('button');
84
+ expect(closeButton).toBeNull();
85
+ });
86
+ });
@@ -0,0 +1,42 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ import React from 'react';
4
+ import { render, screen, fireEvent } from '@testing-library/react';
5
+ import Input from '../components/Input';
6
+
7
+ describe('Input Component', () => {
8
+ it('renders the input with placeholder', () => {
9
+ render(<Input placeholder='Enter your name' type='text' />);
10
+ const inputElement = screen.getByPlaceholderText('Enter your name');
11
+ expect(inputElement).toBeInTheDocument();
12
+ });
13
+
14
+ it('accepts and displays typed value', () => {
15
+ render(<Input placeholder='Type here' type='text' />);
16
+ const inputElement = screen.getByPlaceholderText(
17
+ 'Type here'
18
+ ) as HTMLInputElement;
19
+
20
+ fireEvent.change(inputElement, { target: { value: 'Hello' } });
21
+
22
+ expect(inputElement.value).toBe('Hello');
23
+ });
24
+
25
+ it('applies custom className', () => {
26
+ render(<Input placeholder='Test' type='text' className='custom-class' />);
27
+ const inputElement = screen.getByPlaceholderText('Test');
28
+ expect(inputElement).toHaveClass('custom-class');
29
+ });
30
+
31
+ it('is disabled when disabled prop is passed', () => {
32
+ render(<Input placeholder='Disabled input' type='text' disabled />);
33
+ const inputElement = screen.getByPlaceholderText('Disabled input');
34
+ expect(inputElement).toBeDisabled();
35
+ });
36
+
37
+ it('renders password input type correctly', () => {
38
+ render(<Input placeholder='Password' type='password' />);
39
+ const inputElement = screen.getByPlaceholderText('Password');
40
+ expect(inputElement).toHaveAttribute('type', 'password');
41
+ });
42
+ });