@fabio.caffarello/react-design-system 1.4.1 → 1.5.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 +621 -431
- package/dist/ui/atoms/NavLink/NavLink.d.ts +5 -5
- package/dist/ui/atoms/NavLink/NavLink.stories.d.ts +1 -0
- package/dist/ui/atoms/SidebarItem/SidebarItem.d.ts +21 -0
- package/dist/ui/atoms/SidebarItem/SidebarItem.stories.d.ts +7 -0
- package/dist/ui/atoms/SidebarItem/SidebarItem.test.d.ts +1 -0
- package/dist/ui/atoms/index.d.ts +2 -0
- package/dist/ui/molecules/NavbarGroup/NavbarGroup.d.ts +23 -0
- package/dist/ui/molecules/NavbarGroup/NavbarGroup.stories.d.ts +8 -0
- package/dist/ui/molecules/NavbarGroup/NavbarGroup.test.d.ts +1 -0
- package/dist/ui/molecules/SidebarGroup/SidebarGroup.d.ts +20 -0
- package/dist/ui/molecules/SidebarHeader/SidebarHeader.d.ts +19 -0
- package/dist/ui/molecules/index.d.ts +6 -0
- package/dist/ui/organisms/Sidebar/Sidebar.d.ts +35 -0
- package/dist/ui/organisms/Sidebar/Sidebar.stories.d.ts +8 -0
- package/dist/ui/organisms/Sidebar/Sidebar.test.d.ts +1 -0
- package/dist/ui/organisms/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/ui/atoms/NavLink/NavLink.stories.tsx +12 -0
- package/src/ui/atoms/NavLink/NavLink.tsx +9 -5
- package/src/ui/atoms/SidebarItem/SidebarItem.stories.tsx +55 -0
- package/src/ui/atoms/SidebarItem/SidebarItem.test.tsx +25 -0
- package/src/ui/atoms/SidebarItem/SidebarItem.tsx +61 -0
- package/src/ui/atoms/index.ts +3 -0
- package/src/ui/molecules/NavbarGroup/NavbarGroup.stories.tsx +62 -0
- package/src/ui/molecules/NavbarGroup/NavbarGroup.test.tsx +32 -0
- package/src/ui/molecules/NavbarGroup/NavbarGroup.tsx +71 -0
- package/src/ui/molecules/SidebarGroup/SidebarGroup.tsx +52 -0
- package/src/ui/molecules/SidebarHeader/SidebarHeader.tsx +80 -0
- package/src/ui/molecules/index.ts +9 -0
- package/src/ui/organisms/Sidebar/Sidebar.stories.tsx +117 -0
- package/src/ui/organisms/Sidebar/Sidebar.test.tsx +40 -0
- package/src/ui/organisms/Sidebar/Sidebar.tsx +83 -0
- package/src/ui/organisms/index.ts +3 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { Text } from "../../atoms";
|
|
5
|
+
import { Button } from "../../atoms";
|
|
6
|
+
|
|
7
|
+
export interface SidebarHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
title: string;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
showCloseButton?: boolean;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* SidebarHeader Component
|
|
16
|
+
*
|
|
17
|
+
* Header section of a sidebar with title and optional close button.
|
|
18
|
+
* Follows Atomic Design principles as a Molecule component.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <SidebarHeader title="Navigation" onClose={handleClose} />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export default function SidebarHeader({
|
|
26
|
+
title,
|
|
27
|
+
onClose,
|
|
28
|
+
showCloseButton = false,
|
|
29
|
+
children,
|
|
30
|
+
className = "",
|
|
31
|
+
...props
|
|
32
|
+
}: SidebarHeaderProps) {
|
|
33
|
+
const baseClasses = [
|
|
34
|
+
"flex",
|
|
35
|
+
"items-center",
|
|
36
|
+
"justify-between",
|
|
37
|
+
"px-4",
|
|
38
|
+
"py-4",
|
|
39
|
+
"border-b",
|
|
40
|
+
"border-gray-200",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const classes = [
|
|
44
|
+
...baseClasses,
|
|
45
|
+
className,
|
|
46
|
+
].filter(Boolean).join(" ");
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className={classes} {...props}>
|
|
50
|
+
<Text as="h2" className="text-lg font-semibold text-gray-900">
|
|
51
|
+
{title}
|
|
52
|
+
</Text>
|
|
53
|
+
<div className="flex items-center space-x-2">
|
|
54
|
+
{children}
|
|
55
|
+
{showCloseButton && onClose && (
|
|
56
|
+
<Button
|
|
57
|
+
variant="secondary"
|
|
58
|
+
onClick={onClose}
|
|
59
|
+
className="p-1"
|
|
60
|
+
aria-label="Close sidebar"
|
|
61
|
+
>
|
|
62
|
+
<svg
|
|
63
|
+
className="h-5 w-5"
|
|
64
|
+
fill="none"
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
>
|
|
68
|
+
<path
|
|
69
|
+
strokeLinecap="round"
|
|
70
|
+
strokeLinejoin="round"
|
|
71
|
+
strokeWidth={2}
|
|
72
|
+
d="M6 18L18 6M6 6l12 12"
|
|
73
|
+
/>
|
|
74
|
+
</svg>
|
|
75
|
+
</Button>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -14,3 +14,12 @@ export type { EmptyStateProps } from "./EmptyState/EmptyState";
|
|
|
14
14
|
|
|
15
15
|
export { default as Dropdown } from "./Dropdown/Dropdown";
|
|
16
16
|
export type { DropdownProps, DropdownItem } from "./Dropdown/Dropdown";
|
|
17
|
+
|
|
18
|
+
export { default as SidebarGroup } from "./SidebarGroup/SidebarGroup";
|
|
19
|
+
export type { SidebarGroupProps } from "./SidebarGroup/SidebarGroup";
|
|
20
|
+
|
|
21
|
+
export { default as SidebarHeader } from "./SidebarHeader/SidebarHeader";
|
|
22
|
+
export type { SidebarHeaderProps } from "./SidebarHeader/SidebarHeader";
|
|
23
|
+
|
|
24
|
+
export { default as NavbarGroup } from "./NavbarGroup/NavbarGroup";
|
|
25
|
+
export type { NavbarGroupProps } from "./NavbarGroup/NavbarGroup";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import Sidebar from "./Sidebar";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Sidebar> = {
|
|
5
|
+
title: "UI/Organisms/Sidebar",
|
|
6
|
+
component: Sidebar,
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: "A sidebar navigation component with header, groups, and items. Uses Compound Components pattern.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
argTypes: {
|
|
15
|
+
variant: {
|
|
16
|
+
control: "select",
|
|
17
|
+
options: ["default", "collapsed"],
|
|
18
|
+
description: "Visual variant of the sidebar",
|
|
19
|
+
},
|
|
20
|
+
title: {
|
|
21
|
+
control: "text",
|
|
22
|
+
description: "Title displayed in the header",
|
|
23
|
+
},
|
|
24
|
+
showHeader: {
|
|
25
|
+
control: "boolean",
|
|
26
|
+
description: "Whether to show the header",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Default: StoryObj<typeof Sidebar> = {
|
|
32
|
+
args: {
|
|
33
|
+
title: "Navigation",
|
|
34
|
+
showHeader: true,
|
|
35
|
+
children: (
|
|
36
|
+
<>
|
|
37
|
+
<Sidebar.Group title="Agile">
|
|
38
|
+
<Sidebar.Item href="/epics" isActive>
|
|
39
|
+
Epics
|
|
40
|
+
</Sidebar.Item>
|
|
41
|
+
<Sidebar.Item href="/stories">Stories</Sidebar.Item>
|
|
42
|
+
<Sidebar.Item href="/backlog">Backlog</Sidebar.Item>
|
|
43
|
+
<Sidebar.Item href="/kanban">Kanban</Sidebar.Item>
|
|
44
|
+
<Sidebar.Item href="/sprints">Sprints</Sidebar.Item>
|
|
45
|
+
</Sidebar.Group>
|
|
46
|
+
<Sidebar.Group title="Documentation">
|
|
47
|
+
<Sidebar.Item href="/adrs">ADRs</Sidebar.Item>
|
|
48
|
+
<Sidebar.Item href="/roadmap">Roadmap</Sidebar.Item>
|
|
49
|
+
</Sidebar.Group>
|
|
50
|
+
</>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const WithIcons: StoryObj<typeof Sidebar> = {
|
|
56
|
+
args: {
|
|
57
|
+
title: "Navigation",
|
|
58
|
+
showHeader: true,
|
|
59
|
+
children: (
|
|
60
|
+
<Sidebar.Group title="Agile">
|
|
61
|
+
<Sidebar.Item
|
|
62
|
+
href="/epics"
|
|
63
|
+
isActive
|
|
64
|
+
icon={
|
|
65
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
66
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
67
|
+
</svg>
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
Epics
|
|
71
|
+
</Sidebar.Item>
|
|
72
|
+
<Sidebar.Item
|
|
73
|
+
href="/stories"
|
|
74
|
+
icon={
|
|
75
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
76
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
77
|
+
</svg>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
Stories
|
|
81
|
+
</Sidebar.Item>
|
|
82
|
+
</Sidebar.Group>
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const Collapsed: StoryObj<typeof Sidebar> = {
|
|
88
|
+
args: {
|
|
89
|
+
title: "Navigation",
|
|
90
|
+
variant: "collapsed",
|
|
91
|
+
showHeader: true,
|
|
92
|
+
children: (
|
|
93
|
+
<Sidebar.Group>
|
|
94
|
+
<Sidebar.Item href="/epics" isActive>
|
|
95
|
+
Epics
|
|
96
|
+
</Sidebar.Item>
|
|
97
|
+
<Sidebar.Item href="/stories">Stories</Sidebar.Item>
|
|
98
|
+
</Sidebar.Group>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const WithoutHeader: StoryObj<typeof Sidebar> = {
|
|
104
|
+
args: {
|
|
105
|
+
showHeader: false,
|
|
106
|
+
children: (
|
|
107
|
+
<Sidebar.Group>
|
|
108
|
+
<Sidebar.Item href="/epics" isActive>
|
|
109
|
+
Epics
|
|
110
|
+
</Sidebar.Item>
|
|
111
|
+
<Sidebar.Item href="/stories">Stories</Sidebar.Item>
|
|
112
|
+
</Sidebar.Group>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default meta;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import Sidebar from "./Sidebar";
|
|
4
|
+
|
|
5
|
+
describe("Sidebar", () => {
|
|
6
|
+
it("renders with title and children", () => {
|
|
7
|
+
render(
|
|
8
|
+
<Sidebar title="Navigation">
|
|
9
|
+
<Sidebar.Group>
|
|
10
|
+
<Sidebar.Item href="/epics">Epics</Sidebar.Item>
|
|
11
|
+
</Sidebar.Group>
|
|
12
|
+
</Sidebar>
|
|
13
|
+
);
|
|
14
|
+
expect(screen.getByText("Navigation")).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByRole("link", { name: /epics/i })).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("hides header when showHeader is false", () => {
|
|
19
|
+
render(
|
|
20
|
+
<Sidebar title="Navigation" showHeader={false}>
|
|
21
|
+
<Sidebar.Group>
|
|
22
|
+
<Sidebar.Item href="/epics">Epics</Sidebar.Item>
|
|
23
|
+
</Sidebar.Group>
|
|
24
|
+
</Sidebar>
|
|
25
|
+
);
|
|
26
|
+
expect(screen.queryByText("Navigation")).not.toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("applies collapsed variant styles", () => {
|
|
30
|
+
const { container } = render(
|
|
31
|
+
<Sidebar variant="collapsed" title="Navigation">
|
|
32
|
+
<Sidebar.Group>
|
|
33
|
+
<Sidebar.Item href="/epics">Epics</Sidebar.Item>
|
|
34
|
+
</Sidebar.Group>
|
|
35
|
+
</Sidebar>
|
|
36
|
+
);
|
|
37
|
+
const sidebar = container.querySelector("aside");
|
|
38
|
+
expect(sidebar?.className).toContain("w-16");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import SidebarHeader from "../../molecules/SidebarHeader/SidebarHeader";
|
|
5
|
+
import SidebarGroup from "../../molecules/SidebarGroup/SidebarGroup";
|
|
6
|
+
import SidebarItem from "../../atoms/SidebarItem/SidebarItem";
|
|
7
|
+
|
|
8
|
+
export interface SidebarProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
variant?: "default" | "collapsed";
|
|
10
|
+
title?: string;
|
|
11
|
+
showHeader?: boolean;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sidebar Component
|
|
18
|
+
*
|
|
19
|
+
* A sidebar navigation component with header, groups, and items.
|
|
20
|
+
* Follows Atomic Design principles as an Organism component.
|
|
21
|
+
* Uses Compound Components pattern.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Sidebar title="Navigation" variant="default">
|
|
26
|
+
* <Sidebar.Group title="Agile">
|
|
27
|
+
* <Sidebar.Item href="/epics" isActive>Epics</Sidebar.Item>
|
|
28
|
+
* <Sidebar.Item href="/stories">Stories</Sidebar.Item>
|
|
29
|
+
* </Sidebar.Group>
|
|
30
|
+
* </Sidebar>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function SidebarComponent({
|
|
34
|
+
variant = "default",
|
|
35
|
+
title,
|
|
36
|
+
showHeader = true,
|
|
37
|
+
onClose,
|
|
38
|
+
children,
|
|
39
|
+
className = "",
|
|
40
|
+
...props
|
|
41
|
+
}: SidebarProps) {
|
|
42
|
+
const baseClasses = [
|
|
43
|
+
"flex",
|
|
44
|
+
"flex-col",
|
|
45
|
+
"bg-white",
|
|
46
|
+
"border-r",
|
|
47
|
+
"border-gray-200",
|
|
48
|
+
"h-full",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const variantClasses = {
|
|
52
|
+
default: "w-64",
|
|
53
|
+
collapsed: "w-16",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const classes = [
|
|
57
|
+
...baseClasses,
|
|
58
|
+
variantClasses[variant],
|
|
59
|
+
className,
|
|
60
|
+
].filter(Boolean).join(" ");
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<aside className={classes} {...props}>
|
|
64
|
+
{showHeader && title && (
|
|
65
|
+
<SidebarHeader
|
|
66
|
+
title={title}
|
|
67
|
+
onClose={onClose}
|
|
68
|
+
showCloseButton={!!onClose}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
<nav className="flex-1 overflow-y-auto py-4">
|
|
72
|
+
{children}
|
|
73
|
+
</nav>
|
|
74
|
+
</aside>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Compound Components
|
|
79
|
+
SidebarComponent.Group = SidebarGroup;
|
|
80
|
+
SidebarComponent.Item = SidebarItem;
|
|
81
|
+
SidebarComponent.Header = SidebarHeader;
|
|
82
|
+
|
|
83
|
+
export default SidebarComponent;
|
|
@@ -4,3 +4,6 @@ export { default as Modal } from "./Modal/Modal";
|
|
|
4
4
|
|
|
5
5
|
export { default as Table } from "./Table/Table";
|
|
6
6
|
export type { TableColumn } from "./Table/Table";
|
|
7
|
+
|
|
8
|
+
export { default as Sidebar } from "./Sidebar/Sidebar";
|
|
9
|
+
export type { SidebarProps } from "./Sidebar/Sidebar";
|