@fabio.caffarello/react-design-system 1.2.1 → 1.3.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.
- package/dist/index.cjs +4 -4
- package/dist/index.js +696 -246
- package/dist/ui/atoms/ErrorMessage/ErrorMessage.d.ts +18 -0
- package/dist/ui/atoms/ErrorMessage/ErrorMessage.stories.d.ts +7 -0
- package/dist/ui/atoms/ErrorMessage/ErrorMessage.test.d.ts +1 -0
- package/dist/ui/atoms/Label/Label.d.ts +20 -0
- package/dist/ui/atoms/Label/Label.stories.d.ts +8 -0
- package/dist/ui/atoms/Label/Label.test.d.ts +1 -0
- package/dist/ui/atoms/NavLink/NavLink.d.ts +20 -0
- package/dist/ui/atoms/NavLink/NavLink.stories.d.ts +8 -0
- package/dist/ui/atoms/NavLink/NavLink.test.d.ts +1 -0
- package/dist/ui/atoms/index.d.ts +3 -0
- package/dist/ui/molecules/Breadcrumb/Breadcrumb.d.ts +28 -0
- package/dist/ui/molecules/Breadcrumb/Breadcrumb.stories.d.ts +9 -0
- package/dist/ui/molecules/Breadcrumb/Breadcrumb.test.d.ts +1 -0
- package/dist/ui/molecules/Form/Form.d.ts +24 -0
- package/dist/ui/molecules/Form/Form.stories.d.ts +9 -0
- package/dist/ui/molecules/Form/Form.test.d.ts +1 -0
- package/dist/ui/molecules/Pagination/Pagination.d.ts +28 -0
- package/dist/ui/molecules/Pagination/Pagination.stories.d.ts +10 -0
- package/dist/ui/molecules/Pagination/Pagination.test.d.ts +1 -0
- package/dist/ui/molecules/index.d.ts +4 -0
- package/dist/ui/organisms/Modal/Modal.d.ts +25 -0
- package/dist/ui/organisms/Modal/Modal.stories.d.ts +9 -0
- package/dist/ui/organisms/Modal/Modal.test.d.ts +1 -0
- package/dist/ui/organisms/Table/Table.d.ts +35 -0
- package/dist/ui/organisms/Table/Table.stories.d.ts +9 -0
- package/dist/ui/organisms/Table/Table.test.d.ts +1 -0
- package/dist/ui/organisms/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/ui/atoms/ErrorMessage/ErrorMessage.stories.tsx +81 -0
- package/src/ui/atoms/ErrorMessage/ErrorMessage.test.tsx +40 -0
- package/src/ui/atoms/ErrorMessage/ErrorMessage.tsx +62 -0
- package/src/ui/atoms/Label/Label.stories.tsx +94 -0
- package/src/ui/atoms/Label/Label.test.tsx +47 -0
- package/src/ui/atoms/Label/Label.tsx +51 -0
- package/src/ui/atoms/NavLink/NavLink.stories.tsx +71 -0
- package/src/ui/atoms/NavLink/NavLink.test.tsx +44 -0
- package/src/ui/atoms/NavLink/NavLink.tsx +63 -0
- package/src/ui/atoms/index.ts +6 -0
- package/src/ui/molecules/Breadcrumb/Breadcrumb.stories.tsx +75 -0
- package/src/ui/molecules/Breadcrumb/Breadcrumb.test.tsx +89 -0
- package/src/ui/molecules/Breadcrumb/Breadcrumb.tsx +79 -0
- package/src/ui/molecules/Form/Form.stories.tsx +195 -0
- package/src/ui/molecules/Form/Form.test.tsx +87 -0
- package/src/ui/molecules/Form/Form.tsx +76 -0
- package/src/ui/molecules/Pagination/Pagination.stories.tsx +116 -0
- package/src/ui/molecules/Pagination/Pagination.test.tsx +112 -0
- package/src/ui/molecules/Pagination/Pagination.tsx +168 -0
- package/src/ui/molecules/index.ts +7 -0
- package/src/ui/organisms/Modal/Modal.stories.tsx +102 -0
- package/src/ui/organisms/Modal/Modal.test.tsx +111 -0
- package/src/ui/organisms/Modal/Modal.tsx +203 -0
- package/src/ui/organisms/Table/Table.stories.tsx +137 -0
- package/src/ui/organisms/Table/Table.test.tsx +109 -0
- package/src/ui/organisms/Table/Table.tsx +128 -0
- package/src/ui/organisms/index.ts +5 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
message: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* ErrorMessage Component
|
|
8
|
+
*
|
|
9
|
+
* A component for displaying validation error messages.
|
|
10
|
+
* Follows Atomic Design principles as an Atom component.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ErrorMessage message="This field is required" id="email-error" />
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export default function ErrorMessage({ message, id, className, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import ErrorMessage from "./ErrorMessage";
|
|
3
|
+
declare const meta: Meta<typeof ErrorMessage>;
|
|
4
|
+
export declare const Default: StoryObj<typeof ErrorMessage>;
|
|
5
|
+
export declare const WithInput: StoryObj<typeof ErrorMessage>;
|
|
6
|
+
export declare const MultipleErrors: StoryObj<typeof ErrorMessage>;
|
|
7
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LabelHTMLAttributes } from "react";
|
|
2
|
+
interface Props extends LabelHTMLAttributes<HTMLLabelElement> {
|
|
3
|
+
variant?: "default" | "required" | "optional";
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Label Component
|
|
8
|
+
*
|
|
9
|
+
* A styled label component for form inputs.
|
|
10
|
+
* Follows Atomic Design principles as an Atom component.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Label htmlFor="email" variant="required">
|
|
15
|
+
* Email Address
|
|
16
|
+
* </Label>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default function Label({ variant, className, children, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Label from "./Label";
|
|
3
|
+
declare const meta: Meta<typeof Label>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Label>;
|
|
5
|
+
export declare const Required: StoryObj<typeof Label>;
|
|
6
|
+
export declare const Optional: StoryObj<typeof Label>;
|
|
7
|
+
export declare const WithInput: StoryObj<typeof Label>;
|
|
8
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes } from "react";
|
|
2
|
+
interface Props extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
3
|
+
variant?: "default" | "active" | "disabled";
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* NavLink Component
|
|
8
|
+
*
|
|
9
|
+
* A navigation link component with active and disabled states.
|
|
10
|
+
* Follows Atomic Design principles as an Atom component.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <NavLink href="/dashboard" variant="active">
|
|
15
|
+
* Dashboard
|
|
16
|
+
* </NavLink>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default function NavLink({ variant, className, children, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import NavLink from "./NavLink";
|
|
3
|
+
declare const meta: Meta<typeof NavLink>;
|
|
4
|
+
export declare const Default: StoryObj<typeof NavLink>;
|
|
5
|
+
export declare const Active: StoryObj<typeof NavLink>;
|
|
6
|
+
export declare const Disabled: StoryObj<typeof NavLink>;
|
|
7
|
+
export declare const NavigationBar: StoryObj<typeof NavLink>;
|
|
8
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/ui/atoms/index.d.ts
CHANGED
|
@@ -6,3 +6,6 @@ export { default as BoxWrapper } from "./BoxWrapper/BoxWrapper";
|
|
|
6
6
|
export { default as Badge } from "./Badge/Badge";
|
|
7
7
|
export { default as Select } from "./Select/Select";
|
|
8
8
|
export { default as Textarea } from "./Textarea/Textarea";
|
|
9
|
+
export { default as Label } from "./Label/Label";
|
|
10
|
+
export { default as ErrorMessage } from "./ErrorMessage/ErrorMessage";
|
|
11
|
+
export { default as NavLink } from "./NavLink/NavLink";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
export interface BreadcrumbItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
items: BreadcrumbItem[];
|
|
8
|
+
separator?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Breadcrumb Component
|
|
12
|
+
*
|
|
13
|
+
* A breadcrumb navigation component for hierarchical navigation.
|
|
14
|
+
* Follows Atomic Design principles as a Molecule component.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <Breadcrumb
|
|
19
|
+
* items={[
|
|
20
|
+
* { label: "Home", href: "/" },
|
|
21
|
+
* { label: "Epics", href: "/epics" },
|
|
22
|
+
* { label: "Epic Details" }
|
|
23
|
+
* ]}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export default function Breadcrumb({ items, separator, className, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Breadcrumb from "./Breadcrumb";
|
|
3
|
+
declare const meta: Meta<typeof Breadcrumb>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Breadcrumb>;
|
|
5
|
+
export declare const TwoLevels: StoryObj<typeof Breadcrumb>;
|
|
6
|
+
export declare const ThreeLevels: StoryObj<typeof Breadcrumb>;
|
|
7
|
+
export declare const CustomSeparator: StoryObj<typeof Breadcrumb>;
|
|
8
|
+
export declare const SingleItem: StoryObj<typeof Breadcrumb>;
|
|
9
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FormHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
interface Props extends FormHTMLAttributes<HTMLFormElement> {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
error?: string | null;
|
|
7
|
+
success?: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Form Component
|
|
11
|
+
*
|
|
12
|
+
* A wrapper component for forms with validation states and layout.
|
|
13
|
+
* Follows Atomic Design principles as a Molecule component.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Form onSubmit={handleSubmit} loading={isSubmitting}>
|
|
18
|
+
* <Input name="email" />
|
|
19
|
+
* <Button type="submit">Submit</Button>
|
|
20
|
+
* </Form>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export default function Form({ children, onSubmit, loading, error, success, className, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Form from "./Form";
|
|
3
|
+
declare const meta: Meta<typeof Form>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Form>;
|
|
5
|
+
export declare const WithError: StoryObj<typeof Form>;
|
|
6
|
+
export declare const WithSuccess: StoryObj<typeof Form>;
|
|
7
|
+
export declare const Loading: StoryObj<typeof Form>;
|
|
8
|
+
export declare const CompleteForm: StoryObj<typeof Form>;
|
|
9
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
currentPage: number;
|
|
4
|
+
totalPages: number;
|
|
5
|
+
onPageChange: (page: number) => void;
|
|
6
|
+
totalItems?: number;
|
|
7
|
+
itemsPerPage?: number;
|
|
8
|
+
showPageInfo?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pagination Component
|
|
12
|
+
*
|
|
13
|
+
* A pagination component for navigating through pages of data.
|
|
14
|
+
* Follows Atomic Design principles as a Molecule component.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <Pagination
|
|
19
|
+
* currentPage={1}
|
|
20
|
+
* totalPages={10}
|
|
21
|
+
* onPageChange={(page) => setPage(page)}
|
|
22
|
+
* totalItems={100}
|
|
23
|
+
* itemsPerPage={10}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export default function Pagination({ currentPage, totalPages, onPageChange, totalItems, itemsPerPage, showPageInfo, className, ...props }: Props): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Pagination from "./Pagination";
|
|
3
|
+
declare const meta: Meta<typeof Pagination>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Pagination>;
|
|
5
|
+
export declare const FirstPage: StoryObj<typeof Pagination>;
|
|
6
|
+
export declare const MiddlePage: StoryObj<typeof Pagination>;
|
|
7
|
+
export declare const LastPage: StoryObj<typeof Pagination>;
|
|
8
|
+
export declare const FewPages: StoryObj<typeof Pagination>;
|
|
9
|
+
export declare const WithoutPageInfo: StoryObj<typeof Pagination>;
|
|
10
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { default as InputWithLabel } from "./InputWithLabel/InputWithLabel";
|
|
2
2
|
export { default as Card } from "./Card/Card";
|
|
3
|
+
export { default as Form } from "./Form/Form";
|
|
4
|
+
export { default as Breadcrumb } from "./Breadcrumb/Breadcrumb";
|
|
5
|
+
export type { BreadcrumbItem } from "./Breadcrumb/Breadcrumb";
|
|
6
|
+
export { default as Pagination } from "./Pagination/Pagination";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
title?: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
variant?: "default" | "large" | "fullscreen";
|
|
8
|
+
showCloseButton?: boolean;
|
|
9
|
+
footer?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Modal Component
|
|
13
|
+
*
|
|
14
|
+
* A modal/dialog component with overlay, portal rendering, and accessibility.
|
|
15
|
+
* Follows Atomic Design principles as an Organism component.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
|
|
20
|
+
* <p>Are you sure?</p>
|
|
21
|
+
* </Modal>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export default function Modal({ isOpen, onClose, title, children, variant, showCloseButton, footer, className, ...props }: Props): import("react/jsx-runtime").JSX.Element | null;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Modal from "./Modal";
|
|
3
|
+
declare const meta: Meta<typeof Modal>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Modal>;
|
|
5
|
+
export declare const WithFooter: StoryObj<typeof Modal>;
|
|
6
|
+
export declare const Large: StoryObj<typeof Modal>;
|
|
7
|
+
export declare const WithoutTitle: StoryObj<typeof Modal>;
|
|
8
|
+
export declare const WithoutCloseButton: StoryObj<typeof Modal>;
|
|
9
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface TableColumn<T = any> {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
render?: (value: any, row: T) => ReactNode;
|
|
6
|
+
sortable?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface Props<T = any> extends HTMLAttributes<HTMLTableElement> {
|
|
9
|
+
columns: TableColumn<T>[];
|
|
10
|
+
data: T[];
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
onSort?: (columnKey: string, direction: 'asc' | 'desc') => void;
|
|
13
|
+
sortColumn?: string;
|
|
14
|
+
sortDirection?: 'asc' | 'desc';
|
|
15
|
+
emptyMessage?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Table Component
|
|
19
|
+
*
|
|
20
|
+
* A table component with sorting, loading states, and responsive design.
|
|
21
|
+
* Follows Atomic Design principles as an Organism component.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Table
|
|
26
|
+
* columns={[
|
|
27
|
+
* { key: 'name', label: 'Name', sortable: true },
|
|
28
|
+
* { key: 'status', label: 'Status' }
|
|
29
|
+
* ]}
|
|
30
|
+
* data={items}
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export default function Table<T = any>({ columns, data, loading, onSort, sortColumn, sortDirection, emptyMessage, className, ...props }: Props<T>): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Table from "./Table";
|
|
3
|
+
declare const meta: Meta<typeof Table>;
|
|
4
|
+
export declare const Default: StoryObj<typeof Table>;
|
|
5
|
+
export declare const WithSorting: StoryObj<typeof Table>;
|
|
6
|
+
export declare const WithCustomRendering: StoryObj<typeof Table>;
|
|
7
|
+
export declare const Loading: StoryObj<typeof Table>;
|
|
8
|
+
export declare const Empty: StoryObj<typeof Table>;
|
|
9
|
+
export default meta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import ErrorMessage from "./ErrorMessage";
|
|
3
|
+
import { Input, Label } from "../../atoms";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ErrorMessage> = {
|
|
6
|
+
title: "UI/Atoms/ErrorMessage",
|
|
7
|
+
component: ErrorMessage,
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: "A component for displaying validation error messages. Accessible with role='alert'.",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
message: {
|
|
17
|
+
control: "text",
|
|
18
|
+
description: "Error message to display",
|
|
19
|
+
},
|
|
20
|
+
id: {
|
|
21
|
+
control: "text",
|
|
22
|
+
description: "ID for accessibility (should match aria-describedby on input)",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Default: StoryObj<typeof ErrorMessage> = {
|
|
28
|
+
args: {
|
|
29
|
+
message: "This field is required",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const WithInput: StoryObj<typeof ErrorMessage> = {
|
|
34
|
+
render: () => (
|
|
35
|
+
<div className="space-y-2 max-w-md">
|
|
36
|
+
<Label htmlFor="email" variant="required">
|
|
37
|
+
Email Address
|
|
38
|
+
</Label>
|
|
39
|
+
<Input
|
|
40
|
+
id="email"
|
|
41
|
+
type="email"
|
|
42
|
+
placeholder="Enter email..."
|
|
43
|
+
aria-invalid="true"
|
|
44
|
+
aria-describedby="email-error"
|
|
45
|
+
/>
|
|
46
|
+
<ErrorMessage message="Please enter a valid email address" id="email-error" />
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const MultipleErrors: StoryObj<typeof ErrorMessage> = {
|
|
52
|
+
render: () => (
|
|
53
|
+
<div className="space-y-4 max-w-md">
|
|
54
|
+
<div className="space-y-2">
|
|
55
|
+
<Label htmlFor="name" variant="required">
|
|
56
|
+
Name
|
|
57
|
+
</Label>
|
|
58
|
+
<Input
|
|
59
|
+
id="name"
|
|
60
|
+
aria-invalid="true"
|
|
61
|
+
aria-describedby="name-error"
|
|
62
|
+
/>
|
|
63
|
+
<ErrorMessage message="Name must be at least 3 characters" id="name-error" />
|
|
64
|
+
</div>
|
|
65
|
+
<div className="space-y-2">
|
|
66
|
+
<Label htmlFor="password" variant="required">
|
|
67
|
+
Password
|
|
68
|
+
</Label>
|
|
69
|
+
<Input
|
|
70
|
+
id="password"
|
|
71
|
+
type="password"
|
|
72
|
+
aria-invalid="true"
|
|
73
|
+
aria-describedby="password-error"
|
|
74
|
+
/>
|
|
75
|
+
<ErrorMessage message="Password must be at least 8 characters" id="password-error" />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default meta;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import ErrorMessage from "./ErrorMessage";
|
|
4
|
+
|
|
5
|
+
describe("ErrorMessage", () => {
|
|
6
|
+
it("renders error message", () => {
|
|
7
|
+
render(<ErrorMessage message="This field is required" />);
|
|
8
|
+
expect(screen.getByText("This field is required")).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("has alert role", () => {
|
|
12
|
+
render(<ErrorMessage message="Error message" />);
|
|
13
|
+
const error = screen.getByRole("alert");
|
|
14
|
+
expect(error).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("has aria-live attribute", () => {
|
|
18
|
+
const { container } = render(<ErrorMessage message="Error message" />);
|
|
19
|
+
const error = container.querySelector('[role="alert"]');
|
|
20
|
+
expect(error).toHaveAttribute("aria-live", "polite");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("applies custom id", () => {
|
|
24
|
+
render(<ErrorMessage message="Error message" id="custom-error-id" />);
|
|
25
|
+
const error = screen.getByRole("alert");
|
|
26
|
+
expect(error).toHaveAttribute("id", "custom-error-id");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("displays error icon", () => {
|
|
30
|
+
const { container } = render(<ErrorMessage message="Error message" />);
|
|
31
|
+
const svg = container.querySelector("svg");
|
|
32
|
+
expect(svg).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("applies custom className", () => {
|
|
36
|
+
const { container } = render(<ErrorMessage message="Error" className="custom-class" />);
|
|
37
|
+
const error = container.querySelector('[role="alert"]');
|
|
38
|
+
expect(error).toHaveClass("custom-class");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
message: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ErrorMessage Component
|
|
10
|
+
*
|
|
11
|
+
* A component for displaying validation error messages.
|
|
12
|
+
* Follows Atomic Design principles as an Atom component.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <ErrorMessage message="This field is required" id="email-error" />
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default function ErrorMessage({
|
|
20
|
+
message,
|
|
21
|
+
id,
|
|
22
|
+
className = "",
|
|
23
|
+
...props
|
|
24
|
+
}: Props) {
|
|
25
|
+
const baseClasses = [
|
|
26
|
+
"mt-1",
|
|
27
|
+
"text-sm",
|
|
28
|
+
"text-red-600",
|
|
29
|
+
"flex",
|
|
30
|
+
"items-center",
|
|
31
|
+
"gap-1",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const classes = [
|
|
35
|
+
...baseClasses,
|
|
36
|
+
className,
|
|
37
|
+
].filter(Boolean).join(" ");
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
role="alert"
|
|
42
|
+
id={id}
|
|
43
|
+
className={classes}
|
|
44
|
+
aria-live="polite"
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<svg
|
|
48
|
+
className="h-4 w-4 flex-shrink-0"
|
|
49
|
+
fill="currentColor"
|
|
50
|
+
viewBox="0 0 20 20"
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
fillRule="evenodd"
|
|
55
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
56
|
+
clipRule="evenodd"
|
|
57
|
+
/>
|
|
58
|
+
</svg>
|
|
59
|
+
<span>{message}</span>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Label from "./Label";
|
|
3
|
+
import { Input } from "../../atoms";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Label> = {
|
|
6
|
+
title: "UI/Atoms/Label",
|
|
7
|
+
component: Label,
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: "A styled label component for form inputs. Supports required and optional variants.",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
variant: {
|
|
17
|
+
control: "select",
|
|
18
|
+
options: ["default", "required", "optional"],
|
|
19
|
+
description: "Visual variant of the label",
|
|
20
|
+
},
|
|
21
|
+
htmlFor: {
|
|
22
|
+
control: "text",
|
|
23
|
+
description: "ID of the associated input element",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Default: StoryObj<typeof Label> = {
|
|
29
|
+
args: {
|
|
30
|
+
children: "Email Address",
|
|
31
|
+
htmlFor: "email",
|
|
32
|
+
},
|
|
33
|
+
render: (args) => (
|
|
34
|
+
<div className="space-y-2">
|
|
35
|
+
<Label {...args} />
|
|
36
|
+
<Input id="email" placeholder="Enter email..." />
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Required: StoryObj<typeof Label> = {
|
|
42
|
+
args: {
|
|
43
|
+
children: "Email Address",
|
|
44
|
+
htmlFor: "email-required",
|
|
45
|
+
variant: "required",
|
|
46
|
+
},
|
|
47
|
+
render: (args) => (
|
|
48
|
+
<div className="space-y-2">
|
|
49
|
+
<Label {...args} />
|
|
50
|
+
<Input id="email-required" placeholder="Enter email..." required />
|
|
51
|
+
</div>
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Optional: StoryObj<typeof Label> = {
|
|
56
|
+
args: {
|
|
57
|
+
children: "Middle Name",
|
|
58
|
+
htmlFor: "middle-name",
|
|
59
|
+
variant: "optional",
|
|
60
|
+
},
|
|
61
|
+
render: (args) => (
|
|
62
|
+
<div className="space-y-2">
|
|
63
|
+
<Label {...args} />
|
|
64
|
+
<Input id="middle-name" placeholder="Enter middle name..." />
|
|
65
|
+
</div>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const WithInput: StoryObj<typeof Label> = {
|
|
70
|
+
render: () => (
|
|
71
|
+
<div className="space-y-4 max-w-md">
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Label htmlFor="name" variant="required">
|
|
74
|
+
Full Name
|
|
75
|
+
</Label>
|
|
76
|
+
<Input id="name" placeholder="Enter your name..." />
|
|
77
|
+
</div>
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
<Label htmlFor="email" variant="required">
|
|
80
|
+
Email
|
|
81
|
+
</Label>
|
|
82
|
+
<Input id="email" type="email" placeholder="Enter your email..." />
|
|
83
|
+
</div>
|
|
84
|
+
<div className="space-y-2">
|
|
85
|
+
<Label htmlFor="phone" variant="optional">
|
|
86
|
+
Phone Number
|
|
87
|
+
</Label>
|
|
88
|
+
<Input id="phone" type="tel" placeholder="Enter your phone..." />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default meta;
|