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.
Files changed (116) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +109 -0
  3. package/dist/anima-ds.cjs.js +1332 -0
  4. package/dist/anima-ds.esm.js +88297 -0
  5. package/dist/vite.svg +1 -0
  6. package/package.json +76 -0
  7. package/src/assets/charly.png +0 -0
  8. package/src/components/Atoms/Alert/Alert.jsx +52 -0
  9. package/src/components/Atoms/Alert/Alert.stories.jsx +62 -0
  10. package/src/components/Atoms/Avatar/Avatar.jsx +60 -0
  11. package/src/components/Atoms/Avatar/Avatar.stories.jsx +61 -0
  12. package/src/components/Atoms/Badge/Badge.jsx +34 -0
  13. package/src/components/Atoms/Badge/Badge.stories.jsx +55 -0
  14. package/src/components/Atoms/Button/Button.jsx +281 -0
  15. package/src/components/Atoms/Button/Button.stories.jsx +365 -0
  16. package/src/components/Atoms/Divider/Divider.jsx +49 -0
  17. package/src/components/Atoms/Divider/Divider.stories.jsx +62 -0
  18. package/src/components/Atoms/Icon/Icon.jsx +361 -0
  19. package/src/components/Atoms/Icon/Icon.stories.jsx +115 -0
  20. package/src/components/Atoms/Label/Label.jsx +22 -0
  21. package/src/components/Atoms/Progress/Progress.jsx +49 -0
  22. package/src/components/Atoms/Progress/Progress.stories.jsx +88 -0
  23. package/src/components/Atoms/Radios/Radios.jsx +39 -0
  24. package/src/components/Atoms/Radios/Radios.stories.jsx +119 -0
  25. package/src/components/Atoms/Shadow/Shadow.stories.jsx +25 -0
  26. package/src/components/Atoms/Skeleton/Skeleton.jsx +50 -0
  27. package/src/components/Atoms/Skeleton/Skeleton.stories.jsx +55 -0
  28. package/src/components/Atoms/Spacing/Spacing.jsx +56 -0
  29. package/src/components/Atoms/Spacing/Spacing.stories.jsx +78 -0
  30. package/src/components/Atoms/Spinner/Spinner.jsx +47 -0
  31. package/src/components/Atoms/Spinner/Spinner.stories.jsx +56 -0
  32. package/src/components/Atoms/Toast/Toast.jsx +74 -0
  33. package/src/components/Atoms/Toast/Toast.stories.jsx +101 -0
  34. package/src/components/Atoms/Tooltip/Tooltip.jsx +49 -0
  35. package/src/components/Atoms/Tooltip/Tooltip.stories.jsx +58 -0
  36. package/src/components/Atoms/Typography/Typography.jsx +52 -0
  37. package/src/components/Atoms/Typography/Typography.stories.jsx +267 -0
  38. package/src/components/DataDisplay/AreaChart/AreaChart.jsx +95 -0
  39. package/src/components/DataDisplay/AreaChart/AreaChart.stories.jsx +51 -0
  40. package/src/components/DataDisplay/BarChart/BarChart.jsx +90 -0
  41. package/src/components/DataDisplay/BarChart/BarChart.stories.jsx +60 -0
  42. package/src/components/DataDisplay/Card/Card.jsx +31 -0
  43. package/src/components/DataDisplay/Card/Card.stories.jsx +50 -0
  44. package/src/components/DataDisplay/ColumnChart/ColumnChart.jsx +92 -0
  45. package/src/components/DataDisplay/ColumnChart/ColumnChart.stories.jsx +65 -0
  46. package/src/components/DataDisplay/DBGrid/DBGrid.jsx +138 -0
  47. package/src/components/DataDisplay/DBGrid/DBGrid.stories.jsx +126 -0
  48. package/src/components/DataDisplay/DonutChart/DonutChart.jsx +83 -0
  49. package/src/components/DataDisplay/DonutChart/DonutChart.stories.jsx +48 -0
  50. package/src/components/DataDisplay/EmptyState/EmptyState.jsx +30 -0
  51. package/src/components/DataDisplay/EmptyState/EmptyState.stories.jsx +42 -0
  52. package/src/components/DataDisplay/LineChart/LineChart.jsx +86 -0
  53. package/src/components/DataDisplay/LineChart/LineChart.stories.jsx +67 -0
  54. package/src/components/DataDisplay/List/List.jsx +30 -0
  55. package/src/components/DataDisplay/List/List.stories.jsx +59 -0
  56. package/src/components/DataDisplay/PieChart/PieChart.jsx +64 -0
  57. package/src/components/DataDisplay/PieChart/PieChart.stories.jsx +47 -0
  58. package/src/components/DataDisplay/StatCard/StatCard.jsx +55 -0
  59. package/src/components/DataDisplay/StatCard/StatCard.stories.jsx +59 -0
  60. package/src/components/DataDisplay/TagList/TagList.jsx +37 -0
  61. package/src/components/DataDisplay/TagList/TagList.stories.jsx +48 -0
  62. package/src/components/DataDisplay/Timeline/Timeline.jsx +41 -0
  63. package/src/components/DataDisplay/Timeline/Timeline.stories.jsx +64 -0
  64. package/src/components/Inputs/Checkbox/Checkbox.jsx +27 -0
  65. package/src/components/Inputs/Checkbox/Checkbox.stories.jsx +51 -0
  66. package/src/components/Inputs/DatePicker/DatePicker.jsx +55 -0
  67. package/src/components/Inputs/DatePicker/DatePicker.stories.jsx +52 -0
  68. package/src/components/Inputs/FileUpload/FileUpload.jsx +108 -0
  69. package/src/components/Inputs/FileUpload/FileUpload.stories.jsx +52 -0
  70. package/src/components/Inputs/Input/Input.jsx +50 -0
  71. package/src/components/Inputs/Input/Input.stories.jsx +63 -0
  72. package/src/components/Inputs/RadioButton/RadioButton.jsx +31 -0
  73. package/src/components/Inputs/RadioButton/RadioButton.stories.jsx +59 -0
  74. package/src/components/Inputs/Select/Select.jsx +59 -0
  75. package/src/components/Inputs/Select/Select.stories.jsx +66 -0
  76. package/src/components/Inputs/Switch/Switch.jsx +44 -0
  77. package/src/components/Inputs/Switch/Switch.stories.jsx +51 -0
  78. package/src/components/Inputs/Textarea/Textarea.jsx +51 -0
  79. package/src/components/Inputs/Textarea/Textarea.stories.jsx +65 -0
  80. package/src/components/Layout/Accordion/Accordion.jsx +58 -0
  81. package/src/components/Layout/Accordion/Accordion.stories.jsx +55 -0
  82. package/src/components/Layout/Breadcrumbs/Breadcrumbs.jsx +49 -0
  83. package/src/components/Layout/Breadcrumbs/Breadcrumbs.stories.jsx +44 -0
  84. package/src/components/Layout/Breakpoint/Breakpoint.jsx +35 -0
  85. package/src/components/Layout/Breakpoint/Breakpoint.stories.jsx +348 -0
  86. package/src/components/Layout/Drawer/Drawer.jsx +75 -0
  87. package/src/components/Layout/Drawer/Drawer.stories.jsx +77 -0
  88. package/src/components/Layout/Dropdown/Dropdown.jsx +83 -0
  89. package/src/components/Layout/Dropdown/Dropdown.stories.jsx +53 -0
  90. package/src/components/Layout/Grid/Grid.jsx +39 -0
  91. package/src/components/Layout/Grid/Grid.stories.jsx +546 -0
  92. package/src/components/Layout/Header/Header.jsx +50 -0
  93. package/src/components/Layout/Header/Header.stories.jsx +36 -0
  94. package/src/components/Layout/Layout/Layout.jsx +14 -0
  95. package/src/components/Layout/Layout/Layout.stories.jsx +34 -0
  96. package/src/components/Layout/Modal/Modal.jsx +78 -0
  97. package/src/components/Layout/Modal/Modal.stories.jsx +98 -0
  98. package/src/components/Layout/Pagination/Pagination.jsx +109 -0
  99. package/src/components/Layout/Pagination/Pagination.stories.jsx +62 -0
  100. package/src/components/Layout/Sidebar/Sidebar.jsx +57 -0
  101. package/src/components/Layout/Sidebar/Sidebar.stories.jsx +51 -0
  102. package/src/components/Layout/Stepper/Stepper.jsx +132 -0
  103. package/src/components/Layout/Stepper/Stepper.stories.jsx +78 -0
  104. package/src/components/Layout/Tabs/Tabs.jsx +63 -0
  105. package/src/components/Layout/Tabs/Tabs.stories.jsx +62 -0
  106. package/src/components/Views/ChangePasswordForm/ChangePasswordForm.jsx +125 -0
  107. package/src/components/Views/ChangePasswordForm/ChangePasswordForm.stories.jsx +55 -0
  108. package/src/components/Views/Chat/Chat.jsx +134 -0
  109. package/src/components/Views/Chat/Chat.stories.jsx +143 -0
  110. package/src/components/Views/LoginForm/LoginForm.jsx +98 -0
  111. package/src/components/Views/LoginForm/LoginForm.stories.jsx +55 -0
  112. package/src/i18n/config.js +209 -0
  113. package/src/index.js +60 -0
  114. package/src/main.jsx +510 -0
  115. package/src/providers/I18nProvider.jsx +13 -0
  116. package/src/style.css +721 -0
package/dist/vite.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "anima-ds-nucleus",
3
+ "version": "1.0.0",
4
+ "description": "Anima Design System - A comprehensive React component library",
5
+ "author": "Nucleus Labs <ipvasallo@nucleus.com.ar>",
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/NucleusNova/anima-ds.git"
10
+ },
11
+ "keywords": [
12
+ "react",
13
+ "design system",
14
+ "ui",
15
+ "components",
16
+ "anima",
17
+ "nucleus"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/anima-ds.cjs.js",
21
+ "module": "./dist/anima-ds.esm.js",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/anima-ds.esm.js",
25
+ "require": "./dist/anima-ds.cjs.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "scripts": {
33
+ "dev": "vite",
34
+ "build": "vite build",
35
+ "build:app": "vite build --config vite.app.config.js",
36
+ "preview": "vite preview",
37
+ "storybook": "storybook dev -p 6006",
38
+ "build-storybook": "storybook build"
39
+ },
40
+ "devDependencies": {
41
+ "react": "^19.2.0",
42
+ "react-dom": "^19.2.0",
43
+ "@chromatic-com/storybook": "^4.1.3",
44
+ "@storybook/addon-a11y": "^10.0.8",
45
+ "@storybook/addon-docs": "^10.0.8",
46
+ "@storybook/addon-onboarding": "^10.0.8",
47
+ "@storybook/addon-vitest": "^10.0.8",
48
+ "@storybook/react-vite": "^10.0.8",
49
+ "@tailwindcss/postcss": "^4.1.17",
50
+ "@vitejs/plugin-react": "^5.1.1",
51
+ "@vitest/browser-playwright": "^4.0.10",
52
+ "@vitest/coverage-v8": "^4.0.10",
53
+ "autoprefixer": "^10.4.22",
54
+ "playwright": "^1.56.1",
55
+ "postcss": "^8.5.6",
56
+ "storybook": "^10.0.8",
57
+ "tailwindcss": "^4.1.17",
58
+ "vite": "^7.2.2",
59
+ "vitest": "^4.0.10"
60
+ },
61
+ "dependencies": {
62
+ "@emotion/react": "^11.14.0",
63
+ "@emotion/styled": "^11.14.1",
64
+ "@heroicons/react": "^2.1.5",
65
+ "@mui/material": "^7.3.5",
66
+ "@mui/x-data-grid": "^8.18.0",
67
+ "apexcharts": "^5.3.6",
68
+ "i18next": "^25.6.2",
69
+ "react-apexcharts": "^1.8.0",
70
+ "react-i18next": "^16.3.3"
71
+ },
72
+ "peerDependencies": {
73
+ "react": ">=18 <21",
74
+ "react-dom": ">=18 <21"
75
+ }
76
+ }
Binary file
@@ -0,0 +1,52 @@
1
+ import { Icon } from '../Icon/Icon';
2
+
3
+ export const Alert = ({
4
+ variant = 'info',
5
+ title,
6
+ children,
7
+ onClose,
8
+ className = '',
9
+ ...props
10
+ }) => {
11
+ const variantClasses = {
12
+ info: 'bg-blue-50 border-blue-200 text-blue-800',
13
+ success: 'bg-green-50 border-green-200 text-green-800',
14
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
15
+ danger: 'bg-red-50 border-red-200 text-red-800',
16
+ };
17
+
18
+ const iconMap = {
19
+ info: 'InformationCircleIcon',
20
+ success: 'CheckCircleIcon',
21
+ warning: 'ExclamationTriangleIcon',
22
+ danger: 'XCircleIcon',
23
+ };
24
+
25
+ const classes = `border rounded-lg p-4 ${variantClasses[variant]} ${className}`;
26
+
27
+ return (
28
+ <div className={classes} role="alert" {...props}>
29
+ <div className="flex items-start">
30
+ <Icon
31
+ name={iconMap[variant]}
32
+ className="mr-3 h-5 w-5 flex-shrink-0"
33
+ />
34
+ <div className="flex-1">
35
+ {title && <h3 className="font-semibold mb-1">{title}</h3>}
36
+ <div>{children}</div>
37
+ </div>
38
+ {onClose && (
39
+ <button
40
+ onClick={onClose}
41
+ className="ml-4 text-current opacity-50 hover:opacity-75"
42
+ >
43
+ <Icon name="XMarkIcon" className="h-5 w-5" />
44
+ </button>
45
+ )}
46
+ </div>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default Alert;
52
+
@@ -0,0 +1,62 @@
1
+ import { Alert } from './Alert';
2
+
3
+ export default {
4
+ title: 'Atoms/Alert',
5
+ component: Alert,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ variant: {
9
+ control: 'select',
10
+ options: ['info', 'success', 'warning', 'danger'],
11
+ },
12
+ },
13
+ };
14
+
15
+ export const Info = {
16
+ args: {
17
+ variant: 'info',
18
+ title: 'Information',
19
+ children: 'This is an informational alert message.',
20
+ },
21
+ };
22
+
23
+ export const Success = {
24
+ args: {
25
+ variant: 'success',
26
+ title: 'Success',
27
+ children: 'Your action was completed successfully.',
28
+ },
29
+ };
30
+
31
+ export const Warning = {
32
+ args: {
33
+ variant: 'warning',
34
+ title: 'Warning',
35
+ children: 'Please review this information carefully.',
36
+ },
37
+ };
38
+
39
+ export const Danger = {
40
+ args: {
41
+ variant: 'danger',
42
+ title: 'Error',
43
+ children: 'Something went wrong. Please try again.',
44
+ },
45
+ };
46
+
47
+ export const WithoutTitle = {
48
+ args: {
49
+ variant: 'info',
50
+ children: 'This is an alert without a title.',
51
+ },
52
+ };
53
+
54
+ export const Closable = {
55
+ args: {
56
+ variant: 'info',
57
+ title: 'Closable Alert',
58
+ children: 'This alert can be closed.',
59
+ onClose: () => alert('Alert closed'),
60
+ },
61
+ };
62
+
@@ -0,0 +1,60 @@
1
+ import { Icon } from '../Icon/Icon';
2
+
3
+ export const Avatar = ({
4
+ src,
5
+ alt = '',
6
+ name,
7
+ size = 'medium',
8
+ variant = 'circle',
9
+ className = '',
10
+ ...props
11
+ }) => {
12
+ const sizeClasses = {
13
+ small: 'w-8 h-8 text-xs',
14
+ medium: 'w-10 h-10 text-sm',
15
+ large: 'w-12 h-12 text-base',
16
+ xl: 'w-16 h-16 text-lg',
17
+ };
18
+
19
+ const variantClasses = {
20
+ circle: 'rounded-full',
21
+ square: 'rounded-lg',
22
+ };
23
+
24
+ const getInitials = (name) => {
25
+ if (!name) return '';
26
+ const parts = name.trim().split(' ');
27
+ if (parts.length === 1) return parts[0][0].toUpperCase();
28
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
29
+ };
30
+
31
+ const baseClasses = `inline-flex items-center justify-center bg-gray-200 text-gray-600 font-medium ${sizeClasses[size]} ${variantClasses[variant]} ${className}`;
32
+
33
+ if (src) {
34
+ return (
35
+ <img
36
+ src={src}
37
+ alt={alt || name}
38
+ className={`${sizeClasses[size]} ${variantClasses[variant]} object-cover ${className}`}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ if (name) {
45
+ return (
46
+ <div className={baseClasses} {...props}>
47
+ {getInitials(name)}
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className={baseClasses} {...props}>
54
+ <Icon name="UserIcon" className="w-1/2 h-1/2" />
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export default Avatar;
60
+
@@ -0,0 +1,61 @@
1
+ import { Avatar } from './Avatar';
2
+
3
+ export default {
4
+ title: 'Atoms/Avatar',
5
+ component: Avatar,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ size: {
9
+ control: 'select',
10
+ options: ['small', 'medium', 'large', 'xl'],
11
+ },
12
+ variant: {
13
+ control: 'select',
14
+ options: ['circle', 'square'],
15
+ },
16
+ },
17
+ };
18
+
19
+ export const WithImage = {
20
+ args: {
21
+ src: 'https://i.pravatar.cc/150?img=1',
22
+ alt: 'User avatar',
23
+ size: 'medium',
24
+ },
25
+ };
26
+
27
+ export const WithInitials = {
28
+ args: {
29
+ name: 'John Doe',
30
+ size: 'medium',
31
+ },
32
+ };
33
+
34
+ export const WithoutName = {
35
+ args: {
36
+ size: 'medium',
37
+ },
38
+ };
39
+
40
+ export const Small = {
41
+ args: {
42
+ name: 'Jane Smith',
43
+ size: 'small',
44
+ },
45
+ };
46
+
47
+ export const Large = {
48
+ args: {
49
+ name: 'Bob Johnson',
50
+ size: 'large',
51
+ },
52
+ };
53
+
54
+ export const Square = {
55
+ args: {
56
+ name: 'Alice Williams',
57
+ variant: 'square',
58
+ size: 'medium',
59
+ },
60
+ };
61
+
@@ -0,0 +1,34 @@
1
+ export const Badge = ({
2
+ children,
3
+ variant = 'default',
4
+ size = 'medium',
5
+ className = '',
6
+ ...props
7
+ }) => {
8
+ const baseClasses = 'inline-flex items-center font-medium rounded-full';
9
+
10
+ const variantClasses = {
11
+ default: 'bg-gray-100 text-gray-800',
12
+ primary: 'bg-blue-100 text-blue-800',
13
+ success: 'bg-green-100 text-green-800',
14
+ warning: 'bg-yellow-100 text-yellow-800',
15
+ danger: 'bg-red-100 text-red-800',
16
+ };
17
+
18
+ const sizeClasses = {
19
+ small: 'px-2 py-0.5 text-xs',
20
+ medium: 'px-2.5 py-1 text-sm',
21
+ large: 'px-3 py-1.5 text-base',
22
+ };
23
+
24
+ const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`;
25
+
26
+ return (
27
+ <span className={classes} {...props}>
28
+ {children}
29
+ </span>
30
+ );
31
+ };
32
+
33
+ export default Badge;
34
+
@@ -0,0 +1,55 @@
1
+ import { Badge } from './Badge';
2
+
3
+ export default {
4
+ title: 'Atoms/Badge',
5
+ component: Badge,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ variant: {
9
+ control: 'select',
10
+ options: ['default', 'primary', 'success', 'warning', 'danger'],
11
+ },
12
+ size: {
13
+ control: 'select',
14
+ options: ['small', 'medium', 'large'],
15
+ },
16
+ },
17
+ };
18
+
19
+ export const Default = {
20
+ args: {
21
+ children: 'Badge',
22
+ variant: 'default',
23
+ },
24
+ };
25
+
26
+ export const Primary = {
27
+ args: {
28
+ children: 'Primary',
29
+ variant: 'primary',
30
+ },
31
+ };
32
+
33
+ export const Success = {
34
+ args: {
35
+ children: 'Success',
36
+ variant: 'success',
37
+ },
38
+ };
39
+
40
+ export const Small = {
41
+ args: {
42
+ children: 'Small',
43
+ variant: 'primary',
44
+ size: 'small',
45
+ },
46
+ };
47
+
48
+ export const Large = {
49
+ args: {
50
+ children: 'Large Badge',
51
+ variant: 'primary',
52
+ size: 'large',
53
+ },
54
+ };
55
+
@@ -0,0 +1,281 @@
1
+ import { useState } from 'react';
2
+
3
+ /**
4
+ * Button Component
5
+ *
6
+ * Componente de botón reutilizable que sigue el design system de Anima DS.
7
+ * Soporta múltiples tipos (Primary, Secondary, Ghost), colores (Teal, Grape, Honey) y tamaños (Small, Default, Large).
8
+ * Los estados deshabilitados se manejan con el prop disabled.
9
+ *
10
+ * @param {string} texto - Texto a mostrar en el botón (alias de children)
11
+ * @param {React.ReactNode} children - Contenido del botón
12
+ * @param {string} tipo - Tipo del botón: 'Primary', 'Secondary', 'Ghost' (alias de variant)
13
+ * @param {string} variant - Alias de tipo para mayor compatibilidad
14
+ * @param {string} color - Color del botón: 'Teal', 'Grape', 'Honey'
15
+ * @param {string} tamaño - Tamaño del botón: 'Small', 'Default', 'Large'
16
+ * @param {function} onClick - Función a ejecutar cuando se hace click
17
+ * @param {string} type - Tipo de botón HTML (button, submit, reset)
18
+ * @param {boolean} disabled - Estado deshabilitado
19
+ * @param {string} className - Clases CSS adicionales
20
+ * @param {object} props - Props adicionales del botón HTML
21
+ */
22
+ export const Button = ({
23
+ texto,
24
+ children,
25
+ tipo,
26
+ variant,
27
+ color = 'Teal',
28
+ tamaño = 'Default',
29
+ onClick,
30
+ type = 'button',
31
+ disabled = false,
32
+ className = '',
33
+ ...props
34
+ }) => {
35
+ // Normalizar props para compatibilidad:
36
+ // - permitir usar variant además de tipo
37
+ // - permitir usar children además de texto
38
+ const resolvedTipo = tipo || variant || 'Primary';
39
+ const resolvedContent = children != null ? children : texto;
40
+ // Colores del design system
41
+ const COLOR_PALETTE = {
42
+ Teal: '#2D5C63',
43
+ Grape: '#6D3856', // morado/plum profundo
44
+ Honey: '#E8B061', // naranja/ámbar claro
45
+ };
46
+
47
+ // Color de texto para Primary según el color del botón
48
+ const PRIMARY_TEXT_COLOR = {
49
+ Teal: '#ffffff',
50
+ Grape: '#ffffff',
51
+ Honey: '#374151', // gray-700 (gris oscuro/carbón)
52
+ };
53
+
54
+ // Color para Secondary según el color del botón (puede diferir del Primary)
55
+ const SECONDARY_COLOR = {
56
+ Teal: COLOR_PALETTE.Teal,
57
+ Grape: COLOR_PALETTE.Grape,
58
+ Honey: '#80501F', // marrón medio-oscuro para Secondary
59
+ };
60
+
61
+ // Color para Ghost según el color del botón
62
+ const GHOST_COLOR = {
63
+ Teal: COLOR_PALETTE.Teal,
64
+ Grape: COLOR_PALETTE.Grape,
65
+ Honey: '#80501F', // marrón medio-oscuro para Ghost (igual que Secondary)
66
+ };
67
+
68
+ // Color de texto para Ghost según el color del botón
69
+ const GHOST_TEXT_COLOR = {
70
+ Teal: COLOR_PALETTE.Teal,
71
+ Grape: COLOR_PALETTE.Grape,
72
+ Honey: '#6E4D11', // marrón oscuro para Ghost
73
+ };
74
+
75
+ // Colores para estados deshabilitados (grises)
76
+ const DISABLED_COLORS = {
77
+ border: '#D1D5DB', // gray-300
78
+ text: '#9CA3AF', // gray-400
79
+ background: '#F3F4F6', // gray-100
80
+ };
81
+
82
+ const BUTTON_COLOR = COLOR_PALETTE[color] || COLOR_PALETTE.Teal;
83
+
84
+ // Estado para manejar hover
85
+ const [isHovered, setIsHovered] = useState(false);
86
+
87
+ // Determinar si el botón está deshabilitado
88
+ const isDisabled = disabled;
89
+
90
+ // Clases base comunes a todos los botones
91
+ const baseClasses = 'font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2';
92
+
93
+ // Clases de tamaño con padding y texto
94
+ const sizeClasses = {
95
+ Small: 'px-3 py-1.5 text-sm rounded-md',
96
+ Default: 'px-4 py-2 text-base rounded-lg',
97
+ Large: 'px-6 py-3 text-lg rounded-lg',
98
+ };
99
+
100
+ // Función helper para convertir hex a rgba
101
+ const hexToRgba = (hex, alpha = 1) => {
102
+ const r = parseInt(hex.slice(1, 3), 16);
103
+ const g = parseInt(hex.slice(3, 5), 16);
104
+ const b = parseInt(hex.slice(5, 7), 16);
105
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
106
+ };
107
+
108
+ // Estilos inline con colores según el tipo y color
109
+ const getInlineStyles = () => {
110
+ const styles = {};
111
+
112
+ // Si está deshabilitado, usar colores grises
113
+ if (isDisabled) {
114
+ switch (resolvedTipo) {
115
+ case 'Primary':
116
+ styles.backgroundColor = DISABLED_COLORS.background;
117
+ styles.color = DISABLED_COLORS.text;
118
+ break;
119
+
120
+ case 'Secondary':
121
+ styles.borderColor = DISABLED_COLORS.border;
122
+ styles.color = DISABLED_COLORS.text;
123
+ styles.borderWidth = '1px';
124
+ styles.borderStyle = 'solid';
125
+ styles.backgroundColor = 'transparent';
126
+ break;
127
+
128
+ case 'Ghost':
129
+ styles.color = DISABLED_COLORS.text;
130
+ styles.backgroundColor = 'transparent';
131
+ break;
132
+ }
133
+ return styles;
134
+ }
135
+
136
+ // Estilos para estados habilitados
137
+ switch (resolvedTipo) {
138
+ case 'Primary':
139
+ styles.backgroundColor = BUTTON_COLOR;
140
+ styles.color = PRIMARY_TEXT_COLOR[color] || '#ffffff';
141
+ if (isHovered) {
142
+ styles.opacity = '0.9';
143
+ }
144
+ break;
145
+
146
+ case 'Secondary':
147
+ const secondaryColor = SECONDARY_COLOR[color] || BUTTON_COLOR;
148
+ styles.borderColor = secondaryColor;
149
+ styles.color = secondaryColor;
150
+ styles.borderWidth = '1px';
151
+ styles.borderStyle = 'solid';
152
+ if (isHovered) {
153
+ styles.backgroundColor = hexToRgba(secondaryColor, 0.1);
154
+ } else {
155
+ // Fondo blanco para Honey y Grape Secondary, transparente para los demás
156
+ styles.backgroundColor = (color === 'Honey' || color === 'Grape') ? '#ffffff' : 'transparent';
157
+ }
158
+ break;
159
+
160
+ case 'Ghost':
161
+ const ghostColor = GHOST_COLOR[color] || BUTTON_COLOR;
162
+ const ghostTextColor = GHOST_TEXT_COLOR[color] || BUTTON_COLOR;
163
+
164
+ // Para Honey, Ghost tiene fondo blanco y texto oscuro, sin borde
165
+ if (color === 'Honey') {
166
+ styles.color = ghostTextColor;
167
+ styles.backgroundColor = '#ffffff';
168
+ if (isHovered) {
169
+ styles.backgroundColor = hexToRgba(ghostColor, 0.1);
170
+ }
171
+ } else {
172
+ // Para otros colores, Ghost es sin borde
173
+ styles.color = ghostTextColor;
174
+ styles.backgroundColor = isHovered
175
+ ? hexToRgba(ghostColor, 0.1)
176
+ : 'transparent';
177
+ }
178
+ break;
179
+ }
180
+
181
+ return styles;
182
+ };
183
+
184
+ // Clases según el tipo/variante
185
+ const getVariantClasses = () => {
186
+ if (isDisabled) {
187
+ switch (resolvedTipo) {
188
+ case 'Primary':
189
+ return {
190
+ base: 'active:opacity-80',
191
+ disabled: 'cursor-not-allowed',
192
+ };
193
+
194
+ case 'Secondary':
195
+ return {
196
+ base: 'border active:opacity-80',
197
+ disabled: 'cursor-not-allowed',
198
+ };
199
+
200
+ case 'Ghost':
201
+ return {
202
+ base: 'active:opacity-80',
203
+ disabled: 'cursor-not-allowed',
204
+ };
205
+
206
+ default:
207
+ return {
208
+ base: 'active:opacity-80',
209
+ disabled: 'cursor-not-allowed',
210
+ };
211
+ }
212
+ }
213
+
214
+ switch (resolvedTipo) {
215
+ case 'Primary':
216
+ // Para Honey, el texto no debe ser blanco
217
+ const primaryTextClass = color === 'Honey' ? '' : 'text-white';
218
+ return {
219
+ base: `${primaryTextClass} active:opacity-80`.trim(),
220
+ disabled: 'opacity-50 cursor-not-allowed',
221
+ };
222
+
223
+ case 'Secondary':
224
+ return {
225
+ base: 'border active:opacity-80',
226
+ disabled: 'opacity-50 cursor-not-allowed',
227
+ };
228
+
229
+ case 'Ghost':
230
+ return {
231
+ base: 'active:opacity-80',
232
+ disabled: 'opacity-50 cursor-not-allowed',
233
+ };
234
+
235
+ default:
236
+ return {
237
+ base: 'text-white active:opacity-80',
238
+ disabled: 'opacity-50 cursor-not-allowed',
239
+ };
240
+ }
241
+ };
242
+
243
+ const variantClasses = getVariantClasses();
244
+ const sizeClass = sizeClasses[tamaño] || sizeClasses.Default;
245
+ const inlineStyles = getInlineStyles();
246
+
247
+ // Construir clases finales
248
+ const classes = `
249
+ ${baseClasses}
250
+ ${sizeClass}
251
+ ${variantClasses.base}
252
+ ${isDisabled ? variantClasses.disabled : 'cursor-pointer'}
253
+ ${className}
254
+ `.trim().replace(/\s+/g, ' ');
255
+
256
+ // Manejar el click - no ejecutar si está deshabilitado
257
+ const handleClick = (e) => {
258
+ if (!isDisabled && onClick) {
259
+ onClick(e);
260
+ }
261
+ };
262
+
263
+ return (
264
+ <button
265
+ type={type}
266
+ className={classes}
267
+ style={inlineStyles}
268
+ disabled={isDisabled}
269
+ onClick={handleClick}
270
+ onMouseEnter={() => !isDisabled && setIsHovered(true)}
271
+ onMouseLeave={() => setIsHovered(false)}
272
+ {...props}
273
+ >
274
+ {resolvedContent}
275
+ </button>
276
+ );
277
+ };
278
+
279
+
280
+ export default Button;
281
+