@fabio.caffarello/react-design-system 1.0.0 → 1.2.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 (32) hide show
  1. package/README.md +1 -0
  2. package/dist/index.cjs +4 -4
  3. package/dist/index.js +330 -176
  4. package/dist/ui/atoms/Badge/Badge.d.ts +18 -0
  5. package/dist/ui/atoms/Badge/Badge.stories.d.ts +11 -0
  6. package/dist/ui/atoms/Badge/Badge.test.d.ts +1 -0
  7. package/dist/ui/atoms/Select/Select.d.ts +30 -0
  8. package/dist/ui/atoms/Select/Select.stories.d.ts +10 -0
  9. package/dist/ui/atoms/Select/Select.test.d.ts +1 -0
  10. package/dist/ui/atoms/Textarea/Textarea.d.ts +21 -0
  11. package/dist/ui/atoms/Textarea/Textarea.stories.d.ts +9 -0
  12. package/dist/ui/atoms/Textarea/Textarea.test.d.ts +1 -0
  13. package/dist/ui/atoms/index.d.ts +3 -0
  14. package/dist/ui/molecules/Card/Card.d.ts +22 -0
  15. package/dist/ui/molecules/Card/Card.stories.d.ts +11 -0
  16. package/dist/ui/molecules/Card/Card.test.d.ts +1 -0
  17. package/dist/ui/molecules/index.d.ts +1 -0
  18. package/package.json +1 -1
  19. package/src/ui/atoms/Badge/Badge.stories.tsx +86 -0
  20. package/src/ui/atoms/Badge/Badge.test.tsx +63 -0
  21. package/src/ui/atoms/Badge/Badge.tsx +60 -0
  22. package/src/ui/atoms/Select/Select.stories.tsx +93 -0
  23. package/src/ui/atoms/Select/Select.test.tsx +63 -0
  24. package/src/ui/atoms/Select/Select.tsx +85 -0
  25. package/src/ui/atoms/Textarea/Textarea.stories.tsx +72 -0
  26. package/src/ui/atoms/Textarea/Textarea.test.tsx +55 -0
  27. package/src/ui/atoms/Textarea/Textarea.tsx +67 -0
  28. package/src/ui/atoms/index.ts +6 -0
  29. package/src/ui/molecules/Card/Card.stories.tsx +117 -0
  30. package/src/ui/molecules/Card/Card.test.tsx +58 -0
  31. package/src/ui/molecules/Card/Card.tsx +63 -0
  32. package/src/ui/molecules/index.ts +2 -0
@@ -0,0 +1,72 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import Textarea from "./Textarea";
3
+
4
+ const meta: Meta<typeof Textarea> = {
5
+ title: "UI/Atoms/Textarea",
6
+ component: Textarea,
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: "A styled textarea component for longer text input. Supports error states and resize options.",
11
+ },
12
+ },
13
+ },
14
+ argTypes: {
15
+ placeholder: {
16
+ control: "text",
17
+ description: "Placeholder text",
18
+ },
19
+ rows: {
20
+ control: "number",
21
+ description: "Number of visible rows",
22
+ },
23
+ error: {
24
+ control: "boolean",
25
+ description: "Whether the textarea is in an error state",
26
+ },
27
+ resize: {
28
+ control: "select",
29
+ options: ["none", "both", "horizontal", "vertical"],
30
+ description: "Resize behavior",
31
+ },
32
+ },
33
+ };
34
+
35
+ export const Primary: StoryObj<typeof Textarea> = {
36
+ args: {
37
+ placeholder: "Enter description...",
38
+ rows: 4,
39
+ },
40
+ };
41
+
42
+ export const WithDefaultValue: StoryObj<typeof Textarea> = {
43
+ args: {
44
+ defaultValue: "This is a default value",
45
+ rows: 4,
46
+ },
47
+ };
48
+
49
+ export const WithError: StoryObj<typeof Textarea> = {
50
+ args: {
51
+ placeholder: "Enter description...",
52
+ rows: 4,
53
+ error: true,
54
+ },
55
+ };
56
+
57
+ export const NoResize: StoryObj<typeof Textarea> = {
58
+ args: {
59
+ placeholder: "Fixed size textarea",
60
+ rows: 4,
61
+ resize: "none",
62
+ },
63
+ };
64
+
65
+ export const LargeTextarea: StoryObj<typeof Textarea> = {
66
+ args: {
67
+ placeholder: "Enter a longer description...",
68
+ rows: 8,
69
+ },
70
+ };
71
+
72
+ export default meta;
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Textarea from "./Textarea";
4
+
5
+ describe("Textarea", () => {
6
+ it("renders with placeholder", () => {
7
+ render(<Textarea placeholder="Enter text..." />);
8
+ const textarea = screen.getByPlaceholderText("Enter text...");
9
+ expect(textarea).toBeInTheDocument();
10
+ });
11
+
12
+ it("renders with default value", () => {
13
+ render(<Textarea defaultValue="Default text" />);
14
+ const textarea = screen.getByDisplayValue("Default text");
15
+ expect(textarea).toBeInTheDocument();
16
+ });
17
+
18
+ it("applies error styling when error is true", () => {
19
+ render(<Textarea error />);
20
+ const textarea = screen.getByRole("textbox");
21
+ expect(textarea).toHaveClass("border-red-500");
22
+ expect(textarea).toHaveAttribute("aria-invalid", "true");
23
+ });
24
+
25
+ it("applies custom className", () => {
26
+ render(<Textarea className="custom-class" />);
27
+ const textarea = screen.getByRole("textbox");
28
+ expect(textarea).toHaveClass("custom-class");
29
+ });
30
+
31
+ it("applies resize-none class when resize is none", () => {
32
+ render(<Textarea resize="none" />);
33
+ const textarea = screen.getByRole("textbox");
34
+ expect(textarea).toHaveClass("resize-none");
35
+ });
36
+
37
+ it("applies resize-y class when resize is vertical", () => {
38
+ render(<Textarea resize="vertical" />);
39
+ const textarea = screen.getByRole("textbox");
40
+ expect(textarea).toHaveClass("resize-y");
41
+ });
42
+
43
+ it("has accessible attributes when error", () => {
44
+ render(<Textarea error id="test-textarea" />);
45
+ const textarea = screen.getByRole("textbox");
46
+ expect(textarea).toHaveAttribute("aria-invalid", "true");
47
+ expect(textarea).toHaveAttribute("aria-describedby", "test-textarea-error");
48
+ });
49
+
50
+ it("respects rows prop", () => {
51
+ render(<Textarea rows={10} />);
52
+ const textarea = screen.getByRole("textbox");
53
+ expect(textarea).toHaveAttribute("rows", "10");
54
+ });
55
+ });
@@ -0,0 +1,67 @@
1
+ import type { TextareaHTMLAttributes } from "react";
2
+
3
+ interface Props extends TextareaHTMLAttributes<HTMLTextAreaElement> {
4
+ error?: boolean;
5
+ resize?: "none" | "both" | "horizontal" | "vertical";
6
+ }
7
+
8
+ /**
9
+ * Textarea Component
10
+ *
11
+ * A styled textarea component for longer text input.
12
+ * Follows Atomic Design principles as an Atom component.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <Textarea
17
+ * placeholder="Enter description..."
18
+ * rows={4}
19
+ * />
20
+ * ```
21
+ */
22
+ export default function Textarea({
23
+ error = false,
24
+ resize = "vertical",
25
+ className = "",
26
+ ...props
27
+ }: Props) {
28
+ const baseClasses = [
29
+ "block",
30
+ "w-full",
31
+ "rounded",
32
+ "px-large",
33
+ "py-medium",
34
+ "border",
35
+ "text-base",
36
+ "focus:outline-none",
37
+ "focus:ring-2",
38
+ "focus:ring-offset-2",
39
+ ];
40
+
41
+ const resizeClasses: Record<NonNullable<Props["resize"]>, string> = {
42
+ none: "resize-none",
43
+ both: "resize",
44
+ horizontal: "resize-x",
45
+ vertical: "resize-y",
46
+ };
47
+
48
+ const errorClasses = error
49
+ ? "border-red-500 focus:ring-red-500"
50
+ : "border-gray-300 focus:ring-indigo-500";
51
+
52
+ const classes = [
53
+ ...baseClasses,
54
+ resizeClasses[resize],
55
+ errorClasses,
56
+ className,
57
+ ].filter(Boolean).join(" ");
58
+
59
+ return (
60
+ <textarea
61
+ className={classes}
62
+ aria-invalid={error}
63
+ aria-describedby={error && props.id ? `${props.id}-error` : undefined}
64
+ {...props}
65
+ />
66
+ );
67
+ }
@@ -7,3 +7,9 @@ export { default as Input } from "./Input/Input";
7
7
  export { default as Button } from "./Button/Button";
8
8
 
9
9
  export { default as BoxWrapper } from "./BoxWrapper/BoxWrapper";
10
+
11
+ export { default as Badge } from "./Badge/Badge";
12
+
13
+ export { default as Select } from "./Select/Select";
14
+
15
+ export { default as Textarea } from "./Textarea/Textarea";
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import Card from "./Card";
3
+ import { Text, Button } from "../../atoms";
4
+
5
+ const meta: Meta<typeof Card> = {
6
+ title: "UI/Molecules/Card",
7
+ component: Card,
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: "A versatile card component for displaying content in containers. Supports multiple variants and padding options.",
12
+ },
13
+ },
14
+ },
15
+ argTypes: {
16
+ variant: {
17
+ control: "select",
18
+ options: ["default", "hover", "selected"],
19
+ description: "Visual variant of the card",
20
+ },
21
+ padding: {
22
+ control: "select",
23
+ options: ["none", "small", "medium", "large"],
24
+ description: "Padding size",
25
+ },
26
+ },
27
+ };
28
+
29
+ export const Default: StoryObj<typeof Card> = {
30
+ args: {
31
+ children: (
32
+ <>
33
+ <Text as="h3" className="text-lg font-semibold mb-2">Card Title</Text>
34
+ <Text as="p" className="text-gray-600">This is a default card with medium padding.</Text>
35
+ </>
36
+ ),
37
+ },
38
+ };
39
+
40
+ export const Hover: StoryObj<typeof Card> = {
41
+ args: {
42
+ variant: "hover",
43
+ children: (
44
+ <>
45
+ <Text as="h3" className="text-lg font-semibold mb-2">Hover Card</Text>
46
+ <Text as="p" className="text-gray-600">This card has hover effects. Hover over it!</Text>
47
+ </>
48
+ ),
49
+ },
50
+ };
51
+
52
+ export const Selected: StoryObj<typeof Card> = {
53
+ args: {
54
+ variant: "selected",
55
+ children: (
56
+ <>
57
+ <Text as="h3" className="text-lg font-semibold mb-2">Selected Card</Text>
58
+ <Text as="p" className="text-gray-600">This card appears selected with a blue border.</Text>
59
+ </>
60
+ ),
61
+ },
62
+ };
63
+
64
+ export const WithPaddingSmall: StoryObj<typeof Card> = {
65
+ args: {
66
+ padding: "small",
67
+ children: (
68
+ <>
69
+ <Text as="h3" className="text-lg font-semibold mb-2">Small Padding</Text>
70
+ <Text as="p" className="text-gray-600">This card has small padding.</Text>
71
+ </>
72
+ ),
73
+ },
74
+ };
75
+
76
+ export const WithPaddingLarge: StoryObj<typeof Card> = {
77
+ args: {
78
+ padding: "large",
79
+ children: (
80
+ <>
81
+ <Text as="h3" className="text-lg font-semibold mb-2">Large Padding</Text>
82
+ <Text as="p" className="text-gray-600">This card has large padding for more spacious content.</Text>
83
+ </>
84
+ ),
85
+ },
86
+ };
87
+
88
+ export const WithActions: StoryObj<typeof Card> = {
89
+ args: {
90
+ variant: "hover",
91
+ padding: "large",
92
+ children: (
93
+ <>
94
+ <Text as="h3" className="text-lg font-semibold mb-2">Card with Actions</Text>
95
+ <Text as="p" className="text-gray-600 mb-4">This card includes action buttons.</Text>
96
+ <div className="flex gap-2">
97
+ <Button variant="regular">Primary Action</Button>
98
+ <Button variant="secondary">Secondary</Button>
99
+ </div>
100
+ </>
101
+ ),
102
+ },
103
+ };
104
+
105
+ export const NoPadding: StoryObj<typeof Card> = {
106
+ args: {
107
+ padding: "none",
108
+ children: (
109
+ <div className="p-4">
110
+ <Text as="h3" className="text-lg font-semibold mb-2">No Padding Card</Text>
111
+ <Text as="p" className="text-gray-600">This card has no default padding. Content controls its own spacing.</Text>
112
+ </div>
113
+ ),
114
+ },
115
+ };
116
+
117
+ export default meta;
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Card from "./Card";
4
+
5
+ describe("Card", () => {
6
+ it("renders with children", () => {
7
+ render(<Card>Test Content</Card>);
8
+ expect(screen.getByText("Test Content")).toBeInTheDocument();
9
+ });
10
+
11
+ it("renders with default variant", () => {
12
+ const { container } = render(<Card>Content</Card>);
13
+ const card = container.firstChild as HTMLElement;
14
+ expect(card).toHaveClass("bg-white", "rounded-lg", "border", "border-gray-200");
15
+ });
16
+
17
+ it("applies hover variant classes", () => {
18
+ const { container } = render(<Card variant="hover">Content</Card>);
19
+ const card = container.firstChild as HTMLElement;
20
+ expect(card).toHaveClass("hover:shadow-md", "transition-shadow", "cursor-pointer");
21
+ });
22
+
23
+ it("applies selected variant classes", () => {
24
+ const { container } = render(<Card variant="selected">Content</Card>);
25
+ const card = container.firstChild as HTMLElement;
26
+ expect(card).toHaveClass("border-indigo-500", "shadow-md");
27
+ });
28
+
29
+ it("applies medium padding by default", () => {
30
+ const { container } = render(<Card>Content</Card>);
31
+ const card = container.firstChild as HTMLElement;
32
+ expect(card).toHaveClass("p-4");
33
+ });
34
+
35
+ it("applies small padding", () => {
36
+ const { container } = render(<Card padding="small">Content</Card>);
37
+ const card = container.firstChild as HTMLElement;
38
+ expect(card).toHaveClass("p-2");
39
+ });
40
+
41
+ it("applies large padding", () => {
42
+ const { container } = render(<Card padding="large">Content</Card>);
43
+ const card = container.firstChild as HTMLElement;
44
+ expect(card).toHaveClass("p-6");
45
+ });
46
+
47
+ it("applies no padding", () => {
48
+ const { container } = render(<Card padding="none">Content</Card>);
49
+ const card = container.firstChild as HTMLElement;
50
+ expect(card).not.toHaveClass("p-2", "p-4", "p-6");
51
+ });
52
+
53
+ it("applies custom className", () => {
54
+ const { container } = render(<Card className="custom-class">Content</Card>);
55
+ const card = container.firstChild as HTMLElement;
56
+ expect(card).toHaveClass("custom-class");
57
+ });
58
+ });
@@ -0,0 +1,63 @@
1
+ import type { HTMLAttributes } from "react";
2
+
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ variant?: "default" | "hover" | "selected";
5
+ padding?: "none" | "small" | "medium" | "large";
6
+ }
7
+
8
+ /**
9
+ * Card Component
10
+ *
11
+ * A versatile card component for displaying content in containers.
12
+ * Follows Atomic Design principles as a Molecule component.
13
+ * Can be used to replace BoxWrapper in many cases with more flexibility.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <Card variant="hover" padding="large">
18
+ * <h3>Card Title</h3>
19
+ * <p>Card content</p>
20
+ * </Card>
21
+ * ```
22
+ */
23
+ export default function Card({
24
+ variant = "default",
25
+ padding = "medium",
26
+ className = "",
27
+ children,
28
+ ...props
29
+ }: Props) {
30
+ const baseClasses = [
31
+ "bg-white",
32
+ "rounded-lg",
33
+ "border",
34
+ "border-gray-200",
35
+ "shadow-sm",
36
+ ];
37
+
38
+ const variantClasses: Record<NonNullable<Props["variant"]>, string> = {
39
+ default: "",
40
+ hover: "hover:shadow-md transition-shadow cursor-pointer",
41
+ selected: "border-indigo-500 shadow-md",
42
+ };
43
+
44
+ const paddingClasses: Record<NonNullable<Props["padding"]>, string> = {
45
+ none: "",
46
+ small: "p-2",
47
+ medium: "p-4",
48
+ large: "p-6",
49
+ };
50
+
51
+ const classes = [
52
+ ...baseClasses,
53
+ variantClasses[variant],
54
+ paddingClasses[padding],
55
+ className,
56
+ ].filter(Boolean).join(" ");
57
+
58
+ return (
59
+ <div className={classes} {...props}>
60
+ {children}
61
+ </div>
62
+ );
63
+ }
@@ -1 +1,3 @@
1
1
  export { default as InputWithLabel } from "./InputWithLabel/InputWithLabel";
2
+
3
+ export { default as Card } from "./Card/Card";