@fabio.caffarello/react-design-system 1.6.0 → 1.7.0

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 (58) hide show
  1. package/dist/index.cjs +29 -4
  2. package/dist/index.js +1866 -716
  3. package/dist/ui/atoms/Button/Button.d.ts +28 -5
  4. package/dist/ui/atoms/Button/Button.stories.d.ts +11 -3
  5. package/dist/ui/atoms/Checkbox/Checkbox.d.ts +24 -0
  6. package/dist/ui/atoms/Checkbox/Checkbox.stories.d.ts +10 -0
  7. package/dist/ui/atoms/Checkbox/Checkbox.test.d.ts +1 -0
  8. package/dist/ui/atoms/Collapsible/Collapsible.d.ts +29 -0
  9. package/dist/ui/atoms/Collapsible/Collapsible.stories.d.ts +9 -0
  10. package/dist/ui/atoms/Collapsible/Collapsible.test.d.ts +1 -0
  11. package/dist/ui/atoms/Input/Input.d.ts +28 -4
  12. package/dist/ui/atoms/Input/Input.stories.d.ts +8 -3
  13. package/dist/ui/atoms/Radio/Radio.d.ts +26 -0
  14. package/dist/ui/atoms/Radio/Radio.stories.d.ts +10 -0
  15. package/dist/ui/atoms/Radio/Radio.test.d.ts +1 -0
  16. package/dist/ui/atoms/SidebarItem/SidebarItem.d.ts +3 -1
  17. package/dist/ui/atoms/SidebarItem/SidebarItem.stories.d.ts +3 -0
  18. package/dist/ui/atoms/index.d.ts +7 -0
  19. package/dist/ui/hooks/useCollapsible.d.ts +27 -0
  20. package/dist/ui/index.d.ts +13 -0
  21. package/dist/ui/molecules/InputWithLabel/InputWithLabel.d.ts +4 -2
  22. package/dist/ui/molecules/SidebarGroup/SidebarGroup.d.ts +8 -1
  23. package/dist/ui/molecules/SidebarGroup/SidebarGroup.stories.d.ts +11 -0
  24. package/dist/ui/molecules/SidebarGroup/SidebarGroup.test.d.ts +1 -0
  25. package/dist/ui/providers/ThemeProvider.d.ts +34 -0
  26. package/dist/ui/tokens/breakpoints.d.ts +36 -0
  27. package/dist/ui/tokens/colors.d.ts +89 -0
  28. package/dist/ui/tokens/sidebar.d.ts +48 -0
  29. package/dist/ui/tokens/spacing.d.ts +53 -0
  30. package/dist/ui/tokens/themes/dark.d.ts +38 -0
  31. package/dist/ui/tokens/themes/light.d.ts +38 -0
  32. package/dist/ui/tokens/tokens.factory.d.ts +57 -0
  33. package/dist/ui/tokens/typography.d.ts +90 -0
  34. package/package.json +3 -2
  35. package/src/ui/atoms/Button/Button.stories.tsx +77 -7
  36. package/src/ui/atoms/Button/Button.tsx +176 -28
  37. package/src/ui/atoms/Checkbox/Checkbox.stories.tsx +61 -0
  38. package/src/ui/atoms/Checkbox/Checkbox.test.tsx +32 -0
  39. package/src/ui/atoms/Checkbox/Checkbox.tsx +103 -0
  40. package/src/ui/atoms/Collapsible/Collapsible.tsx +2 -2
  41. package/src/ui/atoms/Input/Input.stories.tsx +67 -6
  42. package/src/ui/atoms/Input/Input.tsx +117 -14
  43. package/src/ui/atoms/Radio/Radio.stories.tsx +72 -0
  44. package/src/ui/atoms/Radio/Radio.test.tsx +32 -0
  45. package/src/ui/atoms/Radio/Radio.tsx +104 -0
  46. package/src/ui/atoms/index.ts +7 -0
  47. package/src/ui/index.ts +14 -0
  48. package/src/ui/molecules/InputWithLabel/InputWithLabel.tsx +5 -4
  49. package/src/ui/molecules/SidebarGroup/SidebarGroup.tsx +30 -38
  50. package/src/ui/providers/ThemeProvider.tsx +105 -0
  51. package/src/ui/tokens/breakpoints.ts +71 -0
  52. package/src/ui/tokens/colors.ts +250 -0
  53. package/src/ui/tokens/sidebar.ts +9 -3
  54. package/src/ui/tokens/spacing.ts +127 -0
  55. package/src/ui/tokens/themes/dark.ts +18 -0
  56. package/src/ui/tokens/themes/light.ts +18 -0
  57. package/src/ui/tokens/tokens.factory.ts +117 -0
  58. package/src/ui/tokens/typography.ts +191 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Dark Theme Tokens
3
+ *
4
+ * Dark theme color palette and tokens.
5
+ */
6
+ export declare const DARK_THEME: {
7
+ readonly colors: Record<import("../colors").ColorRole, import("../colors").SemanticColor>;
8
+ readonly spacing: {
9
+ readonly none: import("../spacing").SpacingToken;
10
+ readonly xs: import("../spacing").SpacingToken;
11
+ readonly sm: import("../spacing").SpacingToken;
12
+ readonly md: import("../spacing").SpacingToken;
13
+ readonly base: import("../spacing").SpacingToken;
14
+ readonly lg: import("../spacing").SpacingToken;
15
+ readonly xl: import("../spacing").SpacingToken;
16
+ readonly '2xl': import("../spacing").SpacingToken;
17
+ readonly '3xl': import("../spacing").SpacingToken;
18
+ readonly '4xl': import("../spacing").SpacingToken;
19
+ readonly '5xl': import("../spacing").SpacingToken;
20
+ readonly '6xl': import("../spacing").SpacingToken;
21
+ };
22
+ readonly typography: {
23
+ readonly h1: import("../typography").TypographyToken;
24
+ readonly h2: import("../typography").TypographyToken;
25
+ readonly h3: import("../typography").TypographyToken;
26
+ readonly h4: import("../typography").TypographyToken;
27
+ readonly h5: import("../typography").TypographyToken;
28
+ readonly h6: import("../typography").TypographyToken;
29
+ readonly body: import("../typography").TypographyToken;
30
+ readonly bodySmall: import("../typography").TypographyToken;
31
+ readonly bodyLarge: import("../typography").TypographyToken;
32
+ readonly label: import("../typography").TypographyToken;
33
+ readonly caption: import("../typography").TypographyToken;
34
+ readonly button: import("../typography").TypographyToken;
35
+ };
36
+ readonly breakpoints: Record<import("../breakpoints").BreakpointName, import("../breakpoints").BreakpointToken>;
37
+ readonly mode: "dark";
38
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Light Theme Tokens
3
+ *
4
+ * Light theme color palette and tokens.
5
+ */
6
+ export declare const LIGHT_THEME: {
7
+ readonly colors: Record<import("../colors").ColorRole, import("../colors").SemanticColor>;
8
+ readonly spacing: {
9
+ readonly none: import("../spacing").SpacingToken;
10
+ readonly xs: import("../spacing").SpacingToken;
11
+ readonly sm: import("../spacing").SpacingToken;
12
+ readonly md: import("../spacing").SpacingToken;
13
+ readonly base: import("../spacing").SpacingToken;
14
+ readonly lg: import("../spacing").SpacingToken;
15
+ readonly xl: import("../spacing").SpacingToken;
16
+ readonly '2xl': import("../spacing").SpacingToken;
17
+ readonly '3xl': import("../spacing").SpacingToken;
18
+ readonly '4xl': import("../spacing").SpacingToken;
19
+ readonly '5xl': import("../spacing").SpacingToken;
20
+ readonly '6xl': import("../spacing").SpacingToken;
21
+ };
22
+ readonly typography: {
23
+ readonly h1: import("../typography").TypographyToken;
24
+ readonly h2: import("../typography").TypographyToken;
25
+ readonly h3: import("../typography").TypographyToken;
26
+ readonly h4: import("../typography").TypographyToken;
27
+ readonly h5: import("../typography").TypographyToken;
28
+ readonly h6: import("../typography").TypographyToken;
29
+ readonly body: import("../typography").TypographyToken;
30
+ readonly bodySmall: import("../typography").TypographyToken;
31
+ readonly bodyLarge: import("../typography").TypographyToken;
32
+ readonly label: import("../typography").TypographyToken;
33
+ readonly caption: import("../typography").TypographyToken;
34
+ readonly button: import("../typography").TypographyToken;
35
+ };
36
+ readonly breakpoints: Record<import("../breakpoints").BreakpointName, import("../breakpoints").BreakpointToken>;
37
+ readonly mode: "light";
38
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Tokens Factory
3
+ *
4
+ * Main factory for creating all types of tokens.
5
+ * Implements Factory Pattern for unified token creation.
6
+ */
7
+ import { type SpacingScale, type SpacingToken } from './spacing';
8
+ import { type TypographyToken, type FontSize, type LineHeight, type FontWeight } from './typography';
9
+ import { type ColorRole, type SemanticColor } from './colors';
10
+ import { type BreakpointName, type BreakpointToken } from './breakpoints';
11
+ export type ThemeMode = 'light' | 'dark';
12
+ export interface TokenSet {
13
+ spacing: Record<string, SpacingToken>;
14
+ typography: Record<string, TypographyToken>;
15
+ colors: Record<ColorRole, SemanticColor>;
16
+ breakpoints: Record<BreakpointName, BreakpointToken>;
17
+ }
18
+ /**
19
+ * Tokens Factory
20
+ * Main factory for creating complete token sets
21
+ */
22
+ export declare class TokensFactory {
23
+ private colorFactory;
24
+ constructor(theme?: ThemeMode);
25
+ /**
26
+ * Create spacing token
27
+ */
28
+ createSpacing(scale: SpacingScale): SpacingToken;
29
+ /**
30
+ * Create typography token
31
+ */
32
+ createTypography(size: FontSize, lineHeight?: LineHeight, weight?: FontWeight): TypographyToken;
33
+ /**
34
+ * Create color palette
35
+ */
36
+ createColorPalette(): Record<ColorRole, SemanticColor>;
37
+ /**
38
+ * Create breakpoint token
39
+ */
40
+ createBreakpoint(name: BreakpointName): BreakpointToken;
41
+ /**
42
+ * Create complete token set for a theme
43
+ */
44
+ createTokenSet(): TokenSet;
45
+ /**
46
+ * Switch theme
47
+ */
48
+ setTheme(theme: ThemeMode): void;
49
+ }
50
+ /**
51
+ * Default factory instance (light theme)
52
+ */
53
+ export declare const defaultTokensFactory: TokensFactory;
54
+ /**
55
+ * Helper function to create token set
56
+ */
57
+ export declare function createTokenSet(theme?: ThemeMode): TokenSet;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Typography Tokens
3
+ *
4
+ * Centralized typography system with font families, sizes, weights, and line heights.
5
+ * Uses Factory Pattern for type-safe token creation.
6
+ */
7
+ export type FontFamily = 'sans' | 'serif' | 'mono';
8
+ export type FontWeight = 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
9
+ export type FontSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl';
10
+ export type LineHeight = 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose';
11
+ export interface TypographyToken {
12
+ fontSize: {
13
+ value: number;
14
+ rem: string;
15
+ px: string;
16
+ tailwind: string;
17
+ };
18
+ lineHeight: {
19
+ value: number;
20
+ tailwind: string;
21
+ };
22
+ fontWeight: {
23
+ value: number;
24
+ tailwind: string;
25
+ };
26
+ }
27
+ export interface FontFamilyToken {
28
+ name: string;
29
+ stack: string;
30
+ tailwind: string;
31
+ }
32
+ export interface FontWeightToken {
33
+ value: number;
34
+ tailwind: string;
35
+ }
36
+ /**
37
+ * Typography Token Factory
38
+ * Creates typography tokens with consistent values
39
+ */
40
+ export declare class TypographyTokenFactory {
41
+ /**
42
+ * Create font size token
43
+ */
44
+ static createFontSize(size: FontSize): TypographyToken['fontSize'];
45
+ /**
46
+ * Create line height token
47
+ */
48
+ static createLineHeight(height: LineHeight): TypographyToken['lineHeight'];
49
+ /**
50
+ * Create font weight token
51
+ */
52
+ static createFontWeight(weight: FontWeight): FontWeightToken;
53
+ /**
54
+ * Create complete typography token
55
+ */
56
+ static create(size: FontSize, lineHeight?: LineHeight, weight?: FontWeight): TypographyToken;
57
+ }
58
+ /**
59
+ * Font family tokens
60
+ */
61
+ export declare const FONT_FAMILY_TOKENS: Record<FontFamily, FontFamilyToken>;
62
+ /**
63
+ * Font weight tokens
64
+ */
65
+ export declare const FONT_WEIGHT_TOKENS: Record<FontWeight, FontWeightToken>;
66
+ /**
67
+ * Pre-defined typography tokens for common use cases
68
+ */
69
+ export declare const TYPOGRAPHY_TOKENS: {
70
+ readonly h1: TypographyToken;
71
+ readonly h2: TypographyToken;
72
+ readonly h3: TypographyToken;
73
+ readonly h4: TypographyToken;
74
+ readonly h5: TypographyToken;
75
+ readonly h6: TypographyToken;
76
+ readonly body: TypographyToken;
77
+ readonly bodySmall: TypographyToken;
78
+ readonly bodyLarge: TypographyToken;
79
+ readonly label: TypographyToken;
80
+ readonly caption: TypographyToken;
81
+ readonly button: TypographyToken;
82
+ };
83
+ /**
84
+ * Helper function to get typography token
85
+ */
86
+ export declare function getTypography(variant: keyof typeof TYPOGRAPHY_TOKENS): TypographyToken;
87
+ /**
88
+ * Helper function to get typography classes as string
89
+ */
90
+ export declare function getTypographyClasses(variant: keyof typeof TYPOGRAPHY_TOKENS): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fabio.caffarello/react-design-system",
3
3
  "private": false,
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -36,7 +36,8 @@
36
36
  },
37
37
  "peerDependencies": {
38
38
  "react": ">=19",
39
- "react-dom": ">=19"
39
+ "react-dom": ">=19",
40
+ "lucide-react": "^0.552.0"
40
41
  },
41
42
  "dependencies": {
42
43
  "@tailwindcss/postcss": "^4.1.16",
@@ -1,16 +1,86 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import Button from "./Button";
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import Button from './Button';
3
3
 
4
4
  const meta: Meta<typeof Button> = {
5
- title: "UI/Atoms/Button",
5
+ title: 'Atoms/Button',
6
6
  component: Button,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ variant: {
10
+ control: 'select',
11
+ options: ['primary', 'secondary', 'error', 'outline', 'ghost'],
12
+ },
13
+ size: {
14
+ control: 'select',
15
+ options: ['sm', 'md', 'lg'],
16
+ },
17
+ isLoading: {
18
+ control: 'boolean',
19
+ },
20
+ disabled: {
21
+ control: 'boolean',
22
+ },
23
+ },
7
24
  };
8
25
 
9
- export const Primary: StoryObj<typeof Button> = {
26
+ export default meta;
27
+ type Story = StoryObj<typeof Button>;
28
+
29
+ export const Primary: Story = {
10
30
  args: {
11
- children: "Hello from Storybook",
12
- variant: "regular",
31
+ variant: 'primary',
32
+ children: 'Primary Button',
13
33
  },
14
34
  };
15
35
 
16
- export default meta;
36
+ export const Secondary: Story = {
37
+ args: {
38
+ variant: 'secondary',
39
+ children: 'Secondary Button',
40
+ },
41
+ };
42
+
43
+ export const Error: Story = {
44
+ args: {
45
+ variant: 'error',
46
+ children: 'Error Button',
47
+ },
48
+ };
49
+
50
+ export const Outline: Story = {
51
+ args: {
52
+ variant: 'outline',
53
+ children: 'Outline Button',
54
+ },
55
+ };
56
+
57
+ export const Ghost: Story = {
58
+ args: {
59
+ variant: 'ghost',
60
+ children: 'Ghost Button',
61
+ },
62
+ };
63
+
64
+ export const Sizes: Story = {
65
+ render: () => (
66
+ <div className="flex items-center gap-4">
67
+ <Button size="sm">Small</Button>
68
+ <Button size="md">Medium</Button>
69
+ <Button size="lg">Large</Button>
70
+ </div>
71
+ ),
72
+ };
73
+
74
+ export const Loading: Story = {
75
+ args: {
76
+ isLoading: true,
77
+ children: 'Loading Button',
78
+ },
79
+ };
80
+
81
+ export const Disabled: Story = {
82
+ args: {
83
+ disabled: true,
84
+ children: 'Disabled Button',
85
+ },
86
+ };
@@ -1,35 +1,183 @@
1
- import type { ButtonHTMLAttributes } from "react";
1
+ 'use client';
2
2
 
3
- interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
4
- variant?: "regular" | "error" | "secondary";
3
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
4
+ import { getColorClass } from '../../tokens/colors';
5
+
6
+ export type ButtonVariant = 'primary' | 'regular' | 'secondary' | 'error' | 'outline' | 'ghost';
7
+ export type ButtonSize = 'sm' | 'md' | 'lg';
8
+
9
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
+ variant?: ButtonVariant;
11
+ size?: ButtonSize;
12
+ isLoading?: boolean;
13
+ leftIcon?: ReactNode;
14
+ rightIcon?: ReactNode;
5
15
  }
6
16
 
17
+ /**
18
+ * Button Component Builder
19
+ * Uses Builder Pattern for constructing button classes
20
+ */
21
+ class ButtonClassBuilder {
22
+ private classes: string[] = [];
23
+
24
+ addBase(): this {
25
+ this.classes.push(
26
+ 'inline-flex',
27
+ 'items-center',
28
+ 'justify-center',
29
+ 'font-medium',
30
+ 'rounded-md',
31
+ 'transition-colors',
32
+ 'focus:outline-none',
33
+ 'focus:ring-2',
34
+ 'focus:ring-offset-2',
35
+ 'disabled:opacity-50',
36
+ 'disabled:cursor-not-allowed'
37
+ );
38
+ return this;
39
+ }
40
+
41
+ addVariant(variant: ButtonVariant): this {
42
+ // Map 'regular' to 'primary' for backward compatibility
43
+ const normalizedVariant = variant === 'regular' ? 'primary' : variant;
44
+
45
+ const variantClasses: Record<'primary' | 'secondary' | 'error' | 'outline' | 'ghost', string[]> = {
46
+ primary: [
47
+ getColorClass('primary', 'DEFAULT', 'bg'),
48
+ getColorClass('primary', 'DEFAULT', 'text'),
49
+ 'hover:opacity-90',
50
+ 'focus:ring-indigo-500',
51
+ ],
52
+ secondary: [
53
+ getColorClass('secondary', 'DEFAULT', 'bg'),
54
+ getColorClass('secondary', 'DEFAULT', 'text'),
55
+ 'hover:opacity-90',
56
+ 'focus:ring-violet-500',
57
+ ],
58
+ error: [
59
+ getColorClass('error', 'DEFAULT', 'bg'),
60
+ getColorClass('error', 'DEFAULT', 'text'),
61
+ 'hover:opacity-90',
62
+ 'focus:ring-red-500',
63
+ ],
64
+ outline: [
65
+ 'border-2',
66
+ 'border-gray-300',
67
+ 'bg-transparent',
68
+ 'text-gray-700',
69
+ 'hover:bg-gray-50',
70
+ 'focus:ring-gray-500',
71
+ ],
72
+ ghost: [
73
+ 'bg-transparent',
74
+ 'text-gray-700',
75
+ 'hover:bg-gray-100',
76
+ 'focus:ring-gray-500',
77
+ ],
78
+ };
79
+
80
+ this.classes.push(...variantClasses[normalizedVariant]);
81
+ return this;
82
+ }
83
+
84
+ addSize(size: ButtonSize): this {
85
+ const sizeClasses: Record<ButtonSize, string[]> = {
86
+ sm: ['px-3', 'py-1.5', 'text-sm'],
87
+ md: ['px-4', 'py-2', 'text-base'],
88
+ lg: ['px-6', 'py-3', 'text-lg'],
89
+ };
90
+
91
+ this.classes.push(...sizeClasses[size]);
92
+ return this;
93
+ }
94
+
95
+ addCustom(className: string): this {
96
+ if (className) {
97
+ this.classes.push(className);
98
+ }
99
+ return this;
100
+ }
101
+
102
+ build(): string {
103
+ return this.classes.filter(Boolean).join(' ');
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Button Component
109
+ *
110
+ * A styled button component with variants and sizes.
111
+ * Follows Atomic Design principles as an Atom component.
112
+ * Uses Builder Pattern for class construction.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * <Button
117
+ * variant="primary"
118
+ * size="md"
119
+ * onClick={handleClick}
120
+ * >
121
+ * Click me
122
+ * </Button>
123
+ * ```
124
+ */
7
125
  export default function Button({
8
- className,
9
- variant = "regular",
126
+ variant = 'primary',
127
+ size = 'md',
128
+ isLoading = false,
129
+ leftIcon,
130
+ rightIcon,
131
+ className = '',
132
+ disabled = false,
133
+ children,
10
134
  ...props
11
- }: Props) {
12
- const classNames = [
13
- className,
14
- "rounded",
15
- "h-form-element",
16
- "lh-form-element",
17
- "px-large",
18
- "text-base",
19
- ];
20
-
21
- switch (variant) {
22
- case "error":
23
- classNames.push("bg-red-light", "text-red-dark", "font-bold");
24
- break;
25
- case "secondary":
26
- classNames.push("bg-transparent", "text-grey-minor", "px-0");
27
- break;
28
- case "regular":
29
- default:
30
- classNames.push("bg-blue-light", "text-blue-dark", "font-bold");
31
- break;
32
- }
135
+ }: ButtonProps) {
136
+ const builder = new ButtonClassBuilder();
137
+ const classes = builder
138
+ .addBase()
139
+ .addVariant(variant)
140
+ .addSize(size)
141
+ .addCustom(className)
142
+ .build();
33
143
 
34
- return <button className={classNames.join(" ")} {...props} />;
144
+ return (
145
+ <button
146
+ className={classes}
147
+ disabled={disabled || isLoading}
148
+ {...props}
149
+ >
150
+ {isLoading ? (
151
+ <>
152
+ <svg
153
+ className="animate-spin -ml-1 mr-2 h-4 w-4"
154
+ xmlns="http://www.w3.org/2000/svg"
155
+ fill="none"
156
+ viewBox="0 0 24 24"
157
+ >
158
+ <circle
159
+ className="opacity-25"
160
+ cx="12"
161
+ cy="12"
162
+ r="10"
163
+ stroke="currentColor"
164
+ strokeWidth="4"
165
+ />
166
+ <path
167
+ className="opacity-75"
168
+ fill="currentColor"
169
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
170
+ />
171
+ </svg>
172
+ Loading...
173
+ </>
174
+ ) : (
175
+ <>
176
+ {leftIcon && <span className="mr-2">{leftIcon}</span>}
177
+ {children}
178
+ {rightIcon && <span className="ml-2">{rightIcon}</span>}
179
+ </>
180
+ )}
181
+ </button>
182
+ );
35
183
  }
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import Checkbox from './Checkbox';
3
+
4
+ const meta: Meta<typeof Checkbox> = {
5
+ title: 'Atoms/Checkbox',
6
+ component: Checkbox,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ label: {
10
+ control: 'text',
11
+ },
12
+ error: {
13
+ control: 'boolean',
14
+ },
15
+ disabled: {
16
+ control: 'boolean',
17
+ },
18
+ checked: {
19
+ control: 'boolean',
20
+ },
21
+ },
22
+ };
23
+
24
+ export default meta;
25
+ type Story = StoryObj<typeof Checkbox>;
26
+
27
+ export const Default: Story = {
28
+ args: {
29
+ label: 'I agree to the terms and conditions',
30
+ checked: false,
31
+ },
32
+ };
33
+
34
+ export const Checked: Story = {
35
+ args: {
36
+ label: 'Subscribe to newsletter',
37
+ checked: true,
38
+ },
39
+ };
40
+
41
+ export const WithError: Story = {
42
+ args: {
43
+ label: 'Accept terms',
44
+ error: true,
45
+ helperText: 'You must accept the terms to continue',
46
+ },
47
+ };
48
+
49
+ export const Disabled: Story = {
50
+ args: {
51
+ label: 'This option is disabled',
52
+ disabled: true,
53
+ checked: false,
54
+ },
55
+ };
56
+
57
+ export const WithoutLabel: Story = {
58
+ args: {
59
+ checked: false,
60
+ },
61
+ };
@@ -0,0 +1,32 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { vi } from 'vitest';
4
+ import Checkbox from './Checkbox';
5
+
6
+ describe('Checkbox', () => {
7
+ it('renders checkbox with label', () => {
8
+ render(<Checkbox label="Test checkbox" />);
9
+ expect(screen.getByLabelText('Test checkbox')).toBeInTheDocument();
10
+ });
11
+
12
+ it('handles click events', async () => {
13
+ const handleChange = vi.fn();
14
+ render(<Checkbox label="Test" onChange={handleChange} />);
15
+
16
+ const checkbox = screen.getByLabelText('Test');
17
+ await userEvent.click(checkbox);
18
+
19
+ expect(handleChange).toHaveBeenCalledTimes(1);
20
+ });
21
+
22
+ it('shows error state', () => {
23
+ render(<Checkbox label="Test" error helperText="Error message" />);
24
+ expect(screen.getByText('Error message')).toBeInTheDocument();
25
+ expect(screen.getByRole('checkbox')).toHaveAttribute('aria-invalid', 'true');
26
+ });
27
+
28
+ it('is disabled when disabled prop is true', () => {
29
+ render(<Checkbox label="Test" disabled />);
30
+ expect(screen.getByRole('checkbox')).toBeDisabled();
31
+ });
32
+ });