anima-ds-nucleus 1.0.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/LICENSE.md +20 -0
- package/README.md +109 -0
- package/dist/anima-ds.cjs.js +1332 -0
- package/dist/anima-ds.esm.js +88297 -0
- package/dist/vite.svg +1 -0
- package/package.json +76 -0
- package/src/assets/charly.png +0 -0
- package/src/components/Atoms/Alert/Alert.jsx +52 -0
- package/src/components/Atoms/Alert/Alert.stories.jsx +62 -0
- package/src/components/Atoms/Avatar/Avatar.jsx +60 -0
- package/src/components/Atoms/Avatar/Avatar.stories.jsx +61 -0
- package/src/components/Atoms/Badge/Badge.jsx +34 -0
- package/src/components/Atoms/Badge/Badge.stories.jsx +55 -0
- package/src/components/Atoms/Button/Button.jsx +281 -0
- package/src/components/Atoms/Button/Button.stories.jsx +365 -0
- package/src/components/Atoms/Divider/Divider.jsx +49 -0
- package/src/components/Atoms/Divider/Divider.stories.jsx +62 -0
- package/src/components/Atoms/Icon/Icon.jsx +361 -0
- package/src/components/Atoms/Icon/Icon.stories.jsx +115 -0
- package/src/components/Atoms/Label/Label.jsx +22 -0
- package/src/components/Atoms/Progress/Progress.jsx +49 -0
- package/src/components/Atoms/Progress/Progress.stories.jsx +88 -0
- package/src/components/Atoms/Radios/Radios.jsx +39 -0
- package/src/components/Atoms/Radios/Radios.stories.jsx +119 -0
- package/src/components/Atoms/Shadow/Shadow.stories.jsx +25 -0
- package/src/components/Atoms/Skeleton/Skeleton.jsx +50 -0
- package/src/components/Atoms/Skeleton/Skeleton.stories.jsx +55 -0
- package/src/components/Atoms/Spacing/Spacing.jsx +56 -0
- package/src/components/Atoms/Spacing/Spacing.stories.jsx +78 -0
- package/src/components/Atoms/Spinner/Spinner.jsx +47 -0
- package/src/components/Atoms/Spinner/Spinner.stories.jsx +56 -0
- package/src/components/Atoms/Toast/Toast.jsx +74 -0
- package/src/components/Atoms/Toast/Toast.stories.jsx +101 -0
- package/src/components/Atoms/Tooltip/Tooltip.jsx +49 -0
- package/src/components/Atoms/Tooltip/Tooltip.stories.jsx +58 -0
- package/src/components/Atoms/Typography/Typography.jsx +52 -0
- package/src/components/Atoms/Typography/Typography.stories.jsx +267 -0
- package/src/components/DataDisplay/AreaChart/AreaChart.jsx +95 -0
- package/src/components/DataDisplay/AreaChart/AreaChart.stories.jsx +51 -0
- package/src/components/DataDisplay/BarChart/BarChart.jsx +90 -0
- package/src/components/DataDisplay/BarChart/BarChart.stories.jsx +60 -0
- package/src/components/DataDisplay/Card/Card.jsx +31 -0
- package/src/components/DataDisplay/Card/Card.stories.jsx +50 -0
- package/src/components/DataDisplay/ColumnChart/ColumnChart.jsx +92 -0
- package/src/components/DataDisplay/ColumnChart/ColumnChart.stories.jsx +65 -0
- package/src/components/DataDisplay/DBGrid/DBGrid.jsx +138 -0
- package/src/components/DataDisplay/DBGrid/DBGrid.stories.jsx +126 -0
- package/src/components/DataDisplay/DonutChart/DonutChart.jsx +83 -0
- package/src/components/DataDisplay/DonutChart/DonutChart.stories.jsx +48 -0
- package/src/components/DataDisplay/EmptyState/EmptyState.jsx +30 -0
- package/src/components/DataDisplay/EmptyState/EmptyState.stories.jsx +42 -0
- package/src/components/DataDisplay/LineChart/LineChart.jsx +86 -0
- package/src/components/DataDisplay/LineChart/LineChart.stories.jsx +67 -0
- package/src/components/DataDisplay/List/List.jsx +30 -0
- package/src/components/DataDisplay/List/List.stories.jsx +59 -0
- package/src/components/DataDisplay/PieChart/PieChart.jsx +64 -0
- package/src/components/DataDisplay/PieChart/PieChart.stories.jsx +47 -0
- package/src/components/DataDisplay/StatCard/StatCard.jsx +55 -0
- package/src/components/DataDisplay/StatCard/StatCard.stories.jsx +59 -0
- package/src/components/DataDisplay/TagList/TagList.jsx +37 -0
- package/src/components/DataDisplay/TagList/TagList.stories.jsx +48 -0
- package/src/components/DataDisplay/Timeline/Timeline.jsx +41 -0
- package/src/components/DataDisplay/Timeline/Timeline.stories.jsx +64 -0
- package/src/components/Inputs/Checkbox/Checkbox.jsx +27 -0
- package/src/components/Inputs/Checkbox/Checkbox.stories.jsx +51 -0
- package/src/components/Inputs/DatePicker/DatePicker.jsx +55 -0
- package/src/components/Inputs/DatePicker/DatePicker.stories.jsx +52 -0
- package/src/components/Inputs/FileUpload/FileUpload.jsx +108 -0
- package/src/components/Inputs/FileUpload/FileUpload.stories.jsx +52 -0
- package/src/components/Inputs/Input/Input.jsx +50 -0
- package/src/components/Inputs/Input/Input.stories.jsx +63 -0
- package/src/components/Inputs/RadioButton/RadioButton.jsx +31 -0
- package/src/components/Inputs/RadioButton/RadioButton.stories.jsx +59 -0
- package/src/components/Inputs/Select/Select.jsx +59 -0
- package/src/components/Inputs/Select/Select.stories.jsx +66 -0
- package/src/components/Inputs/Switch/Switch.jsx +44 -0
- package/src/components/Inputs/Switch/Switch.stories.jsx +51 -0
- package/src/components/Inputs/Textarea/Textarea.jsx +51 -0
- package/src/components/Inputs/Textarea/Textarea.stories.jsx +65 -0
- package/src/components/Layout/Accordion/Accordion.jsx +58 -0
- package/src/components/Layout/Accordion/Accordion.stories.jsx +55 -0
- package/src/components/Layout/Breadcrumbs/Breadcrumbs.jsx +49 -0
- package/src/components/Layout/Breadcrumbs/Breadcrumbs.stories.jsx +44 -0
- package/src/components/Layout/Breakpoint/Breakpoint.jsx +35 -0
- package/src/components/Layout/Breakpoint/Breakpoint.stories.jsx +348 -0
- package/src/components/Layout/Drawer/Drawer.jsx +75 -0
- package/src/components/Layout/Drawer/Drawer.stories.jsx +77 -0
- package/src/components/Layout/Dropdown/Dropdown.jsx +83 -0
- package/src/components/Layout/Dropdown/Dropdown.stories.jsx +53 -0
- package/src/components/Layout/Grid/Grid.jsx +39 -0
- package/src/components/Layout/Grid/Grid.stories.jsx +546 -0
- package/src/components/Layout/Header/Header.jsx +50 -0
- package/src/components/Layout/Header/Header.stories.jsx +36 -0
- package/src/components/Layout/Layout/Layout.jsx +14 -0
- package/src/components/Layout/Layout/Layout.stories.jsx +34 -0
- package/src/components/Layout/Modal/Modal.jsx +78 -0
- package/src/components/Layout/Modal/Modal.stories.jsx +98 -0
- package/src/components/Layout/Pagination/Pagination.jsx +109 -0
- package/src/components/Layout/Pagination/Pagination.stories.jsx +62 -0
- package/src/components/Layout/Sidebar/Sidebar.jsx +57 -0
- package/src/components/Layout/Sidebar/Sidebar.stories.jsx +51 -0
- package/src/components/Layout/Stepper/Stepper.jsx +132 -0
- package/src/components/Layout/Stepper/Stepper.stories.jsx +78 -0
- package/src/components/Layout/Tabs/Tabs.jsx +63 -0
- package/src/components/Layout/Tabs/Tabs.stories.jsx +62 -0
- package/src/components/Views/ChangePasswordForm/ChangePasswordForm.jsx +125 -0
- package/src/components/Views/ChangePasswordForm/ChangePasswordForm.stories.jsx +55 -0
- package/src/components/Views/Chat/Chat.jsx +134 -0
- package/src/components/Views/Chat/Chat.stories.jsx +143 -0
- package/src/components/Views/LoginForm/LoginForm.jsx +98 -0
- package/src/components/Views/LoginForm/LoginForm.stories.jsx +55 -0
- package/src/i18n/config.js +209 -0
- package/src/index.js +60 -0
- package/src/main.jsx +510 -0
- package/src/providers/I18nProvider.jsx +13 -0
- package/src/style.css +721 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { Icon } from '../../Atoms/Icon/Icon';
|
|
3
|
+
import { Button } from '../../Atoms/Button/Button';
|
|
4
|
+
|
|
5
|
+
export const Modal = ({
|
|
6
|
+
isOpen = false,
|
|
7
|
+
onClose,
|
|
8
|
+
title,
|
|
9
|
+
children,
|
|
10
|
+
footer,
|
|
11
|
+
size = 'medium',
|
|
12
|
+
showCloseButton = true,
|
|
13
|
+
className = '',
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isOpen) {
|
|
18
|
+
document.body.style.overflow = 'hidden';
|
|
19
|
+
} else {
|
|
20
|
+
document.body.style.overflow = 'unset';
|
|
21
|
+
}
|
|
22
|
+
return () => {
|
|
23
|
+
document.body.style.overflow = 'unset';
|
|
24
|
+
};
|
|
25
|
+
}, [isOpen]);
|
|
26
|
+
|
|
27
|
+
if (!isOpen) return null;
|
|
28
|
+
|
|
29
|
+
const sizeClasses = {
|
|
30
|
+
small: 'max-w-md',
|
|
31
|
+
medium: 'max-w-lg',
|
|
32
|
+
large: 'max-w-2xl',
|
|
33
|
+
xl: 'max-w-4xl',
|
|
34
|
+
full: 'max-w-full mx-4',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className="fixed inset-0 z-50 overflow-y-auto"
|
|
40
|
+
onClick={onClose}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
|
44
|
+
<div
|
|
45
|
+
className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75"
|
|
46
|
+
onClick={onClose}
|
|
47
|
+
/>
|
|
48
|
+
<div
|
|
49
|
+
className={`inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle w-full ${sizeClasses[size]} ${className}`}
|
|
50
|
+
onClick={(e) => e.stopPropagation()}
|
|
51
|
+
>
|
|
52
|
+
{title && (
|
|
53
|
+
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
|
54
|
+
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
|
|
55
|
+
{showCloseButton && (
|
|
56
|
+
<button
|
|
57
|
+
onClick={onClose}
|
|
58
|
+
className="text-gray-400 hover:text-gray-500"
|
|
59
|
+
>
|
|
60
|
+
<Icon name="XMarkIcon" className="h-6 w-6" />
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
<div className="px-6 py-4">{children}</div>
|
|
66
|
+
{footer && (
|
|
67
|
+
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
|
68
|
+
{footer}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default Modal;
|
|
78
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Modal } from './Modal';
|
|
3
|
+
import { Button } from '../../Atoms/Button/Button';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Layout/Modal',
|
|
7
|
+
component: Modal,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
size: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: ['small', 'medium', 'large', 'xl', 'full'],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Default = {
|
|
18
|
+
render: () => {
|
|
19
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Button onClick={() => setIsOpen(true)}>Abrir Modal</Button>
|
|
23
|
+
<Modal
|
|
24
|
+
isOpen={isOpen}
|
|
25
|
+
onClose={() => setIsOpen(false)}
|
|
26
|
+
title="Título del Modal"
|
|
27
|
+
>
|
|
28
|
+
<p>Este es el contenido del modal. Puedes agregar cualquier contenido aquí.</p>
|
|
29
|
+
</Modal>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithFooter = {
|
|
36
|
+
render: () => {
|
|
37
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<Button onClick={() => setIsOpen(true)}>Abrir Modal con Footer</Button>
|
|
41
|
+
<Modal
|
|
42
|
+
isOpen={isOpen}
|
|
43
|
+
onClose={() => setIsOpen(false)}
|
|
44
|
+
title="Modal con Footer"
|
|
45
|
+
footer={
|
|
46
|
+
<div className="flex justify-end space-x-2">
|
|
47
|
+
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
|
48
|
+
Cancelar
|
|
49
|
+
</Button>
|
|
50
|
+
<Button variant="primary" onClick={() => setIsOpen(false)}>
|
|
51
|
+
Guardar
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
}
|
|
55
|
+
>
|
|
56
|
+
<p>Este modal tiene un footer con botones de acción.</p>
|
|
57
|
+
</Modal>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Large = {
|
|
64
|
+
render: () => {
|
|
65
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Button onClick={() => setIsOpen(true)}>Abrir Modal Grande</Button>
|
|
69
|
+
<Modal
|
|
70
|
+
isOpen={isOpen}
|
|
71
|
+
onClose={() => setIsOpen(false)}
|
|
72
|
+
title="Modal Grande"
|
|
73
|
+
size="large"
|
|
74
|
+
>
|
|
75
|
+
<p>Este es un modal de tamaño grande con más espacio para contenido.</p>
|
|
76
|
+
</Modal>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithoutTitle = {
|
|
83
|
+
render: () => {
|
|
84
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Button onClick={() => setIsOpen(true)}>Abrir Modal sin Título</Button>
|
|
88
|
+
<Modal
|
|
89
|
+
isOpen={isOpen}
|
|
90
|
+
onClose={() => setIsOpen(false)}
|
|
91
|
+
>
|
|
92
|
+
<p>Este modal no tiene título.</p>
|
|
93
|
+
</Modal>
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Icon } from '../../Atoms/Icon/Icon';
|
|
2
|
+
import { Button } from '../../Atoms/Button/Button';
|
|
3
|
+
|
|
4
|
+
export const Pagination = ({
|
|
5
|
+
currentPage = 1,
|
|
6
|
+
totalPages = 1,
|
|
7
|
+
onPageChange,
|
|
8
|
+
showFirstLast = true,
|
|
9
|
+
className = '',
|
|
10
|
+
...props
|
|
11
|
+
}) => {
|
|
12
|
+
const handlePageChange = (page) => {
|
|
13
|
+
if (page >= 1 && page <= totalPages && page !== currentPage) {
|
|
14
|
+
if (onPageChange) {
|
|
15
|
+
onPageChange(page);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getPageNumbers = () => {
|
|
21
|
+
const pages = [];
|
|
22
|
+
const maxVisible = 5;
|
|
23
|
+
|
|
24
|
+
if (totalPages <= maxVisible) {
|
|
25
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
26
|
+
pages.push(i);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
if (currentPage <= 3) {
|
|
30
|
+
for (let i = 1; i <= 5; i++) {
|
|
31
|
+
pages.push(i);
|
|
32
|
+
}
|
|
33
|
+
} else if (currentPage >= totalPages - 2) {
|
|
34
|
+
for (let i = totalPages - 4; i <= totalPages; i++) {
|
|
35
|
+
pages.push(i);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
for (let i = currentPage - 2; i <= currentPage + 2; i++) {
|
|
39
|
+
pages.push(i);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return pages;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (totalPages <= 1) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<nav className={className} aria-label="Pagination" {...props}>
|
|
50
|
+
<div className="flex items-center justify-center space-x-1">
|
|
51
|
+
{showFirstLast && (
|
|
52
|
+
<Button
|
|
53
|
+
variant="outline"
|
|
54
|
+
size="small"
|
|
55
|
+
onClick={() => handlePageChange(1)}
|
|
56
|
+
disabled={currentPage === 1}
|
|
57
|
+
>
|
|
58
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
59
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
|
|
60
|
+
</svg>
|
|
61
|
+
</Button>
|
|
62
|
+
)}
|
|
63
|
+
<Button
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="small"
|
|
66
|
+
onClick={() => handlePageChange(currentPage - 1)}
|
|
67
|
+
disabled={currentPage === 1}
|
|
68
|
+
>
|
|
69
|
+
<Icon name="ChevronLeftIcon" className="h-4 w-4" />
|
|
70
|
+
</Button>
|
|
71
|
+
|
|
72
|
+
{getPageNumbers().map((page) => (
|
|
73
|
+
<Button
|
|
74
|
+
key={page}
|
|
75
|
+
variant={currentPage === page ? 'primary' : 'outline'}
|
|
76
|
+
size="small"
|
|
77
|
+
onClick={() => handlePageChange(page)}
|
|
78
|
+
>
|
|
79
|
+
{page}
|
|
80
|
+
</Button>
|
|
81
|
+
))}
|
|
82
|
+
|
|
83
|
+
<Button
|
|
84
|
+
variant="outline"
|
|
85
|
+
size="small"
|
|
86
|
+
onClick={() => handlePageChange(currentPage + 1)}
|
|
87
|
+
disabled={currentPage === totalPages}
|
|
88
|
+
>
|
|
89
|
+
<Icon name="ChevronRightIcon" className="h-4 w-4" />
|
|
90
|
+
</Button>
|
|
91
|
+
{showFirstLast && (
|
|
92
|
+
<Button
|
|
93
|
+
variant="outline"
|
|
94
|
+
size="small"
|
|
95
|
+
onClick={() => handlePageChange(totalPages)}
|
|
96
|
+
disabled={currentPage === totalPages}
|
|
97
|
+
>
|
|
98
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
99
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5.25 4.5l7.5 7.5-7.5 7.5m6-15l7.5 7.5-7.5 7.5" />
|
|
100
|
+
</svg>
|
|
101
|
+
</Button>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</nav>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default Pagination;
|
|
109
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Pagination } from './Pagination';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Layout/Pagination',
|
|
6
|
+
component: Pagination,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Default = {
|
|
11
|
+
render: () => {
|
|
12
|
+
const [page, setPage] = useState(1);
|
|
13
|
+
return (
|
|
14
|
+
<Pagination
|
|
15
|
+
currentPage={page}
|
|
16
|
+
totalPages={10}
|
|
17
|
+
onPageChange={setPage}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const WithoutFirstLast = {
|
|
24
|
+
render: () => {
|
|
25
|
+
const [page, setPage] = useState(1);
|
|
26
|
+
return (
|
|
27
|
+
<Pagination
|
|
28
|
+
currentPage={page}
|
|
29
|
+
totalPages={10}
|
|
30
|
+
onPageChange={setPage}
|
|
31
|
+
showFirstLast={false}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const FewPages = {
|
|
38
|
+
render: () => {
|
|
39
|
+
const [page, setPage] = useState(1);
|
|
40
|
+
return (
|
|
41
|
+
<Pagination
|
|
42
|
+
currentPage={page}
|
|
43
|
+
totalPages={3}
|
|
44
|
+
onPageChange={setPage}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const ManyPages = {
|
|
51
|
+
render: () => {
|
|
52
|
+
const [page, setPage] = useState(5);
|
|
53
|
+
return (
|
|
54
|
+
<Pagination
|
|
55
|
+
currentPage={page}
|
|
56
|
+
totalPages={20}
|
|
57
|
+
onPageChange={setPage}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { Icon } from '../../Atoms/Icon/Icon';
|
|
3
|
+
|
|
4
|
+
export const Sidebar = ({
|
|
5
|
+
items = [],
|
|
6
|
+
activeItem,
|
|
7
|
+
onItemClick,
|
|
8
|
+
className = '',
|
|
9
|
+
...props
|
|
10
|
+
}) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
|
|
13
|
+
const defaultItems = [
|
|
14
|
+
{ id: 'home', label: t('nav.home'), icon: 'HomeIcon' },
|
|
15
|
+
{ id: 'dashboard', label: t('nav.dashboard'), icon: 'ChartBarIcon' },
|
|
16
|
+
{ id: 'profile', label: t('nav.profile'), icon: 'UserIcon' },
|
|
17
|
+
{ id: 'settings', label: t('nav.settings'), icon: 'Cog6ToothIcon' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const menuItems = items.length > 0 ? items : defaultItems;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<aside className={`bg-white shadow-sm border-r border-gray-200 ${className}`} {...props}>
|
|
24
|
+
<nav className="p-4">
|
|
25
|
+
<ul className="space-y-2">
|
|
26
|
+
{menuItems.map((item) => (
|
|
27
|
+
<li key={item.id}>
|
|
28
|
+
<button
|
|
29
|
+
onClick={() => onItemClick && onItemClick(item.id)}
|
|
30
|
+
className={`
|
|
31
|
+
w-full flex items-center px-4 py-2 rounded-lg
|
|
32
|
+
transition-colors
|
|
33
|
+
${
|
|
34
|
+
activeItem === item.id
|
|
35
|
+
? 'bg-blue-100 text-blue-700 font-medium'
|
|
36
|
+
: 'text-gray-700 hover:bg-gray-100'
|
|
37
|
+
}
|
|
38
|
+
`}
|
|
39
|
+
>
|
|
40
|
+
{item.icon && (
|
|
41
|
+
<Icon
|
|
42
|
+
name={item.icon}
|
|
43
|
+
className="mr-3 h-5 w-5"
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
<span>{item.label}</span>
|
|
47
|
+
</button>
|
|
48
|
+
</li>
|
|
49
|
+
))}
|
|
50
|
+
</ul>
|
|
51
|
+
</nav>
|
|
52
|
+
</aside>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default Sidebar;
|
|
57
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Sidebar } from './Sidebar';
|
|
3
|
+
import { I18nProvider } from '../../../providers/I18nProvider';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Layout/Sidebar',
|
|
7
|
+
component: Sidebar,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
decorators: [
|
|
10
|
+
(Story) => (
|
|
11
|
+
<I18nProvider>
|
|
12
|
+
<Story />
|
|
13
|
+
</I18nProvider>
|
|
14
|
+
),
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Default = {
|
|
19
|
+
render: () => {
|
|
20
|
+
const [activeItem, setActiveItem] = useState('home');
|
|
21
|
+
return (
|
|
22
|
+
<div className="w-64">
|
|
23
|
+
<Sidebar
|
|
24
|
+
activeItem={activeItem}
|
|
25
|
+
onItemClick={setActiveItem}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const WithCustomItems = {
|
|
33
|
+
render: () => {
|
|
34
|
+
const [activeItem, setActiveItem] = useState('item1');
|
|
35
|
+
const customItems = [
|
|
36
|
+
{ id: 'item1', label: 'Custom Item 1', icon: 'HomeIcon' },
|
|
37
|
+
{ id: 'item2', label: 'Custom Item 2', icon: 'UserIcon' },
|
|
38
|
+
{ id: 'item3', label: 'Custom Item 3', icon: 'SettingsIcon' },
|
|
39
|
+
];
|
|
40
|
+
return (
|
|
41
|
+
<div className="w-64">
|
|
42
|
+
<Sidebar
|
|
43
|
+
items={customItems}
|
|
44
|
+
activeItem={activeItem}
|
|
45
|
+
onItemClick={setActiveItem}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Icon } from '../../Atoms/Icon/Icon';
|
|
2
|
+
|
|
3
|
+
export const Stepper = ({
|
|
4
|
+
steps = [],
|
|
5
|
+
currentStep = 0,
|
|
6
|
+
orientation = 'horizontal',
|
|
7
|
+
className = '',
|
|
8
|
+
...props
|
|
9
|
+
}) => {
|
|
10
|
+
if (steps.length === 0) return null;
|
|
11
|
+
|
|
12
|
+
if (orientation === 'vertical') {
|
|
13
|
+
return (
|
|
14
|
+
<div className={className} {...props}>
|
|
15
|
+
<ol className="space-y-4">
|
|
16
|
+
{steps.map((step, index) => {
|
|
17
|
+
const isActive = index === currentStep;
|
|
18
|
+
const isCompleted = index < currentStep;
|
|
19
|
+
const isUpcoming = index > currentStep;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<li key={index} className="flex items-start">
|
|
23
|
+
<div className="flex items-center">
|
|
24
|
+
<div
|
|
25
|
+
className={`
|
|
26
|
+
flex items-center justify-center w-10 h-10 rounded-full border-2
|
|
27
|
+
${
|
|
28
|
+
isCompleted
|
|
29
|
+
? 'bg-blue-600 border-blue-600 text-white'
|
|
30
|
+
: isActive
|
|
31
|
+
? 'bg-blue-100 border-blue-600 text-blue-600'
|
|
32
|
+
: 'bg-white border-gray-300 text-gray-400'
|
|
33
|
+
}
|
|
34
|
+
`}
|
|
35
|
+
>
|
|
36
|
+
{isCompleted ? (
|
|
37
|
+
<Icon name="CheckCircleIcon" className="h-6 w-6" />
|
|
38
|
+
) : (
|
|
39
|
+
<span className="font-medium">{index + 1}</span>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
{index < steps.length - 1 && (
|
|
43
|
+
<div
|
|
44
|
+
className={`
|
|
45
|
+
w-0.5 h-12 ml-5
|
|
46
|
+
${isCompleted ? 'bg-blue-600' : 'bg-gray-300'}
|
|
47
|
+
`}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
<div className="ml-4 pb-8">
|
|
52
|
+
<h3
|
|
53
|
+
className={`
|
|
54
|
+
text-sm font-medium
|
|
55
|
+
${isActive || isCompleted ? 'text-gray-900' : 'text-gray-400'}
|
|
56
|
+
`}
|
|
57
|
+
>
|
|
58
|
+
{step.label}
|
|
59
|
+
</h3>
|
|
60
|
+
{step.description && (
|
|
61
|
+
<p className="text-sm text-gray-500 mt-1">{step.description}</p>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</li>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</ol>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={className} {...props}>
|
|
74
|
+
<ol className="flex items-center w-full">
|
|
75
|
+
{steps.map((step, index) => {
|
|
76
|
+
const isActive = index === currentStep;
|
|
77
|
+
const isCompleted = index < currentStep;
|
|
78
|
+
const isUpcoming = index > currentStep;
|
|
79
|
+
const isLast = index === steps.length - 1;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<li key={index} className="flex items-center w-full">
|
|
83
|
+
<div className="flex items-center">
|
|
84
|
+
<div
|
|
85
|
+
className={`
|
|
86
|
+
flex items-center justify-center w-10 h-10 rounded-full border-2
|
|
87
|
+
${
|
|
88
|
+
isCompleted
|
|
89
|
+
? 'bg-blue-600 border-blue-600 text-white'
|
|
90
|
+
: isActive
|
|
91
|
+
? 'bg-blue-100 border-blue-600 text-blue-600'
|
|
92
|
+
: 'bg-white border-gray-300 text-gray-400'
|
|
93
|
+
}
|
|
94
|
+
`}
|
|
95
|
+
>
|
|
96
|
+
{isCompleted ? (
|
|
97
|
+
<Icon name="CheckIcon" className="h-6 w-6" />
|
|
98
|
+
) : (
|
|
99
|
+
<span className="font-medium">{index + 1}</span>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
{!isLast && (
|
|
103
|
+
<div
|
|
104
|
+
className={`
|
|
105
|
+
w-full h-0.5 mx-2
|
|
106
|
+
${isCompleted ? 'bg-blue-600' : 'bg-gray-300'}
|
|
107
|
+
`}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</li>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</ol>
|
|
115
|
+
{steps[currentStep] && (
|
|
116
|
+
<div className="mt-6">
|
|
117
|
+
<h3 className="text-lg font-semibold text-gray-900">
|
|
118
|
+
{steps[currentStep].label}
|
|
119
|
+
</h3>
|
|
120
|
+
{steps[currentStep].description && (
|
|
121
|
+
<p className="text-sm text-gray-500 mt-1">
|
|
122
|
+
{steps[currentStep].description}
|
|
123
|
+
</p>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default Stepper;
|
|
132
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Stepper } from './Stepper';
|
|
3
|
+
import { Button } from '../../Atoms/Button/Button';
|
|
4
|
+
|
|
5
|
+
const steps = [
|
|
6
|
+
{ label: 'Información Personal', description: 'Completa tus datos básicos' },
|
|
7
|
+
{ label: 'Verificación', description: 'Verifica tu identidad' },
|
|
8
|
+
{ label: 'Configuración', description: 'Configura tus preferencias' },
|
|
9
|
+
{ label: 'Confirmación', description: 'Revisa y confirma' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
title: 'Layout/Stepper',
|
|
14
|
+
component: Stepper,
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Horizontal = {
|
|
19
|
+
render: () => {
|
|
20
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
21
|
+
return (
|
|
22
|
+
<div>
|
|
23
|
+
<Stepper steps={steps} currentStep={currentStep} />
|
|
24
|
+
<div className="mt-6 flex space-x-2">
|
|
25
|
+
<Button
|
|
26
|
+
variant="outline"
|
|
27
|
+
onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}
|
|
28
|
+
disabled={currentStep === 0}
|
|
29
|
+
>
|
|
30
|
+
Anterior
|
|
31
|
+
</Button>
|
|
32
|
+
<Button
|
|
33
|
+
variant="primary"
|
|
34
|
+
onClick={() => setCurrentStep(Math.min(steps.length - 1, currentStep + 1))}
|
|
35
|
+
disabled={currentStep === steps.length - 1}
|
|
36
|
+
>
|
|
37
|
+
Siguiente
|
|
38
|
+
</Button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Vertical = {
|
|
46
|
+
render: () => {
|
|
47
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
48
|
+
return (
|
|
49
|
+
<div>
|
|
50
|
+
<Stepper steps={steps} currentStep={currentStep} orientation="vertical" />
|
|
51
|
+
<div className="mt-6 flex space-x-2">
|
|
52
|
+
<Button
|
|
53
|
+
variant="outline"
|
|
54
|
+
onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}
|
|
55
|
+
disabled={currentStep === 0}
|
|
56
|
+
>
|
|
57
|
+
Anterior
|
|
58
|
+
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
variant="primary"
|
|
61
|
+
onClick={() => setCurrentStep(Math.min(steps.length - 1, currentStep + 1))}
|
|
62
|
+
disabled={currentStep === steps.length - 1}
|
|
63
|
+
>
|
|
64
|
+
Siguiente
|
|
65
|
+
</Button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Completed = {
|
|
73
|
+
args: {
|
|
74
|
+
steps: steps,
|
|
75
|
+
currentStep: 3,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|