@soyfri/shared-library 2.0.0-beta.2 → 2.0.0-beta.4

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 (187) hide show
  1. package/.dockerignore +8 -0
  2. package/.github/workflows/publish.yml +107 -0
  3. package/.prettierrc +3 -0
  4. package/.storybook/main.ts +19 -0
  5. package/.storybook/preview.ts +14 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/Dockerfile +37 -0
  8. package/build.js +102 -0
  9. package/chromatic.config.json +5 -0
  10. package/cleanDirectories.js +40 -0
  11. package/dist/README.md +243 -0
  12. package/dist/components/Icon/Icon.js +1 -1
  13. package/dist/components/Table/Table.js +1 -1
  14. package/dist/index.cjs +24 -0
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +7 -1
  17. package/dist/mui.d.ts +1 -0
  18. package/dist/package.json +197 -0
  19. package/package.json +4 -32
  20. package/rollup.config.cjs +87 -0
  21. package/src/components/ActionMenu/ActionMenu.stories.tsx +230 -0
  22. package/src/components/ActionMenu/ActionMenu.tsx +174 -0
  23. package/src/components/ActionMenu/index.ts +2 -0
  24. package/src/components/AppBar/AppBar.stories.tsx +272 -0
  25. package/src/components/AppBar/AppBar.sx.ts +32 -0
  26. package/src/components/AppBar/AppBar.tsx +123 -0
  27. package/src/components/AppBar/AppBarBrand.tsx +120 -0
  28. package/src/components/AppBar/AppBarContext.ts +25 -0
  29. package/src/components/AppBar/AppBarMenuToggle.tsx +90 -0
  30. package/src/components/AppBar/AppBarUserMenu.tsx +217 -0
  31. package/src/components/AppBar/index.ts +25 -0
  32. package/src/components/Autocomplete/Autocomplete.definitions.ts +477 -0
  33. package/src/components/Autocomplete/Autocomplete.helpers.ts +60 -0
  34. package/src/components/Autocomplete/Autocomplete.stories.tsx +748 -0
  35. package/src/components/Autocomplete/Autocomplete.sx.ts +30 -0
  36. package/src/components/Autocomplete/Autocomplete.tsx +361 -0
  37. package/src/components/Autocomplete/Autocomplete.types.ts +13 -0
  38. package/src/components/Autocomplete/_parts/AutocompleteChips.tsx +55 -0
  39. package/src/components/Autocomplete/_parts/AutocompleteLoader.tsx +17 -0
  40. package/src/components/Autocomplete/_parts/AutocompleteOption.tsx +31 -0
  41. package/src/components/Autocomplete/index.ts +12 -0
  42. package/src/components/Avatar/Avatar.definitions.ts +162 -0
  43. package/src/components/Avatar/Avatar.stories.tsx +258 -0
  44. package/src/components/Avatar/Avatar.tsx +206 -0
  45. package/src/components/Avatar/index.ts +1 -0
  46. package/src/components/Button/Button.definition.ts +97 -0
  47. package/src/components/Button/Button.stories.tsx +285 -0
  48. package/src/components/Button/Button.tsx +67 -0
  49. package/src/components/Button/index.ts +1 -0
  50. package/src/components/Card/Card.definition.ts +5 -0
  51. package/src/components/Card/Card.stories.tsx +221 -0
  52. package/src/components/Card/Card.sx.ts +104 -0
  53. package/src/components/Card/Card.tsx +200 -0
  54. package/src/components/Card/index.ts +9 -0
  55. package/src/components/Chip/Chip.definitions.ts +167 -0
  56. package/src/components/Chip/Chip.stories.tsx +265 -0
  57. package/src/components/Chip/Chip.tsx +61 -0
  58. package/src/components/Chip/index.ts +1 -0
  59. package/src/components/Column/Column.tsx +29 -0
  60. package/src/components/Column/index.ts +1 -0
  61. package/src/components/DatePicker/DatePicker.definitions.ts +228 -0
  62. package/src/components/DatePicker/DatePicker.helpers.ts +24 -0
  63. package/src/components/DatePicker/DatePicker.stories.tsx +309 -0
  64. package/src/components/DatePicker/DatePicker.sx.ts +33 -0
  65. package/src/components/DatePicker/DatePicker.tsx +189 -0
  66. package/src/components/DatePicker/DatePicker.types.ts +10 -0
  67. package/src/components/DatePicker/index.ts +9 -0
  68. package/src/components/DateRangePicker/DateRangePicker.definitions.ts +191 -0
  69. package/src/components/DateRangePicker/DateRangePicker.stories.tsx +252 -0
  70. package/src/components/DateRangePicker/DateRangePicker.tsx +56 -0
  71. package/src/components/DateRangePicker/index.ts +1 -0
  72. package/src/components/DateTimePicker/DateTimePicker.definitions.ts +256 -0
  73. package/src/components/DateTimePicker/DateTimePicker.helpers.ts +38 -0
  74. package/src/components/DateTimePicker/DateTimePicker.stories.tsx +418 -0
  75. package/src/components/DateTimePicker/DateTimePicker.sx.ts +30 -0
  76. package/src/components/DateTimePicker/DateTimePicker.tsx +225 -0
  77. package/src/components/DateTimePicker/DateTimePicker.types.ts +10 -0
  78. package/src/components/DateTimePicker/index.ts +9 -0
  79. package/src/components/Drawer/Drawer.stories.tsx +270 -0
  80. package/src/components/Drawer/Drawer.sx.ts +106 -0
  81. package/src/components/Drawer/Drawer.tsx +214 -0
  82. package/src/components/Drawer/DrawerContext.ts +26 -0
  83. package/src/components/Drawer/DrawerItem.tsx +110 -0
  84. package/src/components/Drawer/index.ts +10 -0
  85. package/src/components/Flyout/Flyout.stories.tsx +282 -0
  86. package/src/components/Flyout/Flyout.tsx +122 -0
  87. package/src/components/Flyout/index.ts +1 -0
  88. package/src/components/Gallery/Gallery.definition.tsx +37 -0
  89. package/src/components/Gallery/Gallery.stories.tsx +82 -0
  90. package/src/components/Gallery/Gallery.tsx +118 -0
  91. package/src/components/Gallery/GalleryLightbox.tsx +170 -0
  92. package/src/components/Gallery/GalleryMain.tsx +84 -0
  93. package/src/components/Gallery/GalleryThumbnails.tsx +106 -0
  94. package/src/components/Gallery/index.ts +1 -0
  95. package/src/components/Icon/Icon.stories.tsx +121 -0
  96. package/src/components/Icon/Icon.tsx +175 -0
  97. package/src/components/Icon/index.ts +2 -0
  98. package/src/components/Input/Input.definitions.ts +324 -0
  99. package/src/components/Input/Input.helpers.ts +49 -0
  100. package/src/components/Input/Input.stories.tsx +499 -0
  101. package/src/components/Input/Input.sx.ts +42 -0
  102. package/src/components/Input/Input.tsx +141 -0
  103. package/src/components/Input/Input.types.ts +10 -0
  104. package/src/components/Input/index.ts +9 -0
  105. package/src/components/InputGroup/InputGroup.definitions.ts +158 -0
  106. package/src/components/InputGroup/InputGroup.stories.tsx +267 -0
  107. package/src/components/InputGroup/InputGroup.tsx +179 -0
  108. package/src/components/InputGroup/index.ts +1 -0
  109. package/src/components/MenuButton/MenuButton.stories.tsx +197 -0
  110. package/src/components/MenuButton/MenuButton.tsx +100 -0
  111. package/src/components/MenuButton/index.ts +1 -0
  112. package/src/components/Modal/Modal.stories.tsx +721 -0
  113. package/src/components/Modal/Modal.tsx +355 -0
  114. package/src/components/Modal/ModalBody.tsx +16 -0
  115. package/src/components/Modal/ModalFooter.tsx +71 -0
  116. package/src/components/Modal/ModalHeader.tsx +18 -0
  117. package/src/components/Modal/index.ts +6 -0
  118. package/src/components/PageLoader/PageLoader.stories.tsx +217 -0
  119. package/src/components/PageLoader/PageLoader.tsx +96 -0
  120. package/src/components/PageLoader/index.ts +2 -0
  121. package/src/components/ScrollTopButton/ScrollTopButton.stories.tsx +158 -0
  122. package/src/components/ScrollTopButton/ScrollTopButton.tsx +135 -0
  123. package/src/components/ScrollTopButton/index.ts +8 -0
  124. package/src/components/ScrollTopButton/scrollToTop.ts +37 -0
  125. package/src/components/Select/Select.definitions.ts +602 -0
  126. package/src/components/Select/Select.helpers.ts +71 -0
  127. package/src/components/Select/Select.stories.tsx +687 -0
  128. package/src/components/Select/Select.sx.ts +14 -0
  129. package/src/components/Select/Select.tsx +429 -0
  130. package/src/components/Select/Select.types.ts +15 -0
  131. package/src/components/Select/_parts/SelectMenuItem.tsx +40 -0
  132. package/src/components/Select/_parts/SelectSearchHeader.tsx +51 -0
  133. package/src/components/Select/_parts/SelectValue.tsx +96 -0
  134. package/src/components/Select/index.ts +14 -0
  135. package/src/components/Stat/Stat.stories.tsx +85 -0
  136. package/src/components/Stat/Stat.tsx +117 -0
  137. package/src/components/Stat/index.ts +2 -0
  138. package/src/components/StatusMessage/StatusMessage.stories.tsx +130 -0
  139. package/src/components/StatusMessage/StatusMessage.tsx +162 -0
  140. package/src/components/StatusMessage/index.ts +2 -0
  141. package/src/components/Stepper/Step.tsx +21 -0
  142. package/src/components/Stepper/Stepper.definition.ts +75 -0
  143. package/src/components/Stepper/Stepper.stories.tsx +122 -0
  144. package/src/components/Stepper/Stepper.tsx +75 -0
  145. package/src/components/Stepper/index.ts +2 -0
  146. package/src/components/Table/EmptyTable.png +0 -0
  147. package/src/components/Table/Table.definition.ts +580 -0
  148. package/src/components/Table/Table.stories.tsx +853 -0
  149. package/src/components/Table/Table.tsx +495 -0
  150. package/src/components/Table/data.ts +134 -0
  151. package/src/components/Table/exportsUtils.ts +195 -0
  152. package/src/components/Table/index.ts +3 -0
  153. package/src/components/Table/types.ts +34 -0
  154. package/src/components/Tabs/Tab.definition.ts +53 -0
  155. package/src/components/Tabs/Tab.tsx +19 -0
  156. package/src/components/Tabs/Tabs.stories.tsx +118 -0
  157. package/src/components/Tabs/Tabs.tsx +99 -0
  158. package/src/components/Tabs/_tabUtils.tsx +4 -0
  159. package/src/components/Tabs/index.ts +2 -0
  160. package/src/components/Timeline/Timeline.definition.ts +43 -0
  161. package/src/components/Timeline/Timeline.stories.tsx +108 -0
  162. package/src/components/Timeline/Timeline.tsx +49 -0
  163. package/src/components/Timeline/TimelineItem.tsx +31 -0
  164. package/src/components/Timeline/index.ts +2 -0
  165. package/src/components/Tooltip/Tooltip.stories.tsx +129 -0
  166. package/src/components/Tooltip/Tooltip.tsx +58 -0
  167. package/src/components/Tooltip/index.ts +1 -0
  168. package/src/components/_shared/formField.sx.ts +118 -0
  169. package/src/components/_shared/resolvePreset.ts +35 -0
  170. package/src/hooks/ClipBoard/ClipBoard.stories.tsx +168 -0
  171. package/src/hooks/ClipBoard/ClipBoard.tsx +131 -0
  172. package/src/hooks/ClipBoard/ClipboardUnifiedDemo.tsx +111 -0
  173. package/src/hooks/ClipBoard/index.ts +1 -0
  174. package/src/hooks/Wizard/Wizard.stories.tsx +301 -0
  175. package/src/hooks/Wizard/WizardContext.tsx +166 -0
  176. package/src/hooks/Wizard/index.ts +6 -0
  177. package/src/hooks/Wizard/useWizard.ts +13 -0
  178. package/src/index.ts +17 -0
  179. package/src/mui.ts +54 -0
  180. package/src/styles.css +3 -0
  181. package/src/theme/componentStyles.ts +47 -0
  182. package/src/theme/tokens.ts +43 -0
  183. package/tailwind.config.js +10 -0
  184. package/tsconfig.json +48 -0
  185. package/tsup.config.js +41 -0
  186. package/vite.config.js +132 -0
  187. package/vitest.config.ts +35 -0
@@ -0,0 +1,174 @@
1
+ import React, { useState, type ReactNode, type MouseEvent } from 'react';
2
+ import {
3
+ IconButton,
4
+ Menu,
5
+ MenuItem,
6
+ ListItemIcon,
7
+ ListItemText,
8
+ Divider,
9
+ Tooltip,
10
+ } from '@mui/material';
11
+ import MoreVertIcon from '@mui/icons-material/MoreVert';
12
+ import type { SxProps, Theme } from '@mui/material/styles';
13
+
14
+ export interface ActionMenuItem {
15
+ /** Key única para React. Si no se provee, se usa el label. */
16
+ key?: string;
17
+ /** Texto del item. */
18
+ label: ReactNode;
19
+ /** Icono opcional a la izquierda. */
20
+ icon?: ReactNode;
21
+ /** Handler de click. Recibe el evento del MenuItem. */
22
+ onClick?: (event: MouseEvent<HTMLLIElement>) => void;
23
+ /** Deshabilita el item. */
24
+ disabled?: boolean;
25
+ /** Marca el item como destructivo (pinta color error). */
26
+ danger?: boolean;
27
+ /** Inserta un `<Divider />` ANTES de este item. */
28
+ dividerBefore?: boolean;
29
+ }
30
+
31
+ export interface ActionMenuProps {
32
+ /** Lista de items del menú. */
33
+ items: ActionMenuItem[];
34
+ /**
35
+ * Elemento trigger. Si no se provee, se renderiza un IconButton con icono
36
+ * de tres puntos (MoreVertIcon) — el patrón típico de celda de tabla.
37
+ */
38
+ trigger?: ReactNode;
39
+ /** Texto de tooltip sobre el trigger default. Default: "Acciones". */
40
+ triggerTooltip?: string;
41
+ /** Anchor origin del menu. Default: { vertical: 'bottom', horizontal: 'right' }. */
42
+ anchorOrigin?: {
43
+ vertical: 'top' | 'center' | 'bottom';
44
+ horizontal: 'left' | 'center' | 'right';
45
+ };
46
+ /** Transform origin del menu. Default: { vertical: 'top', horizontal: 'right' }. */
47
+ transformOrigin?: {
48
+ vertical: 'top' | 'center' | 'bottom';
49
+ horizontal: 'left' | 'center' | 'right';
50
+ };
51
+ /** sx del Menu (Paper interno). */
52
+ menuSx?: SxProps<Theme>;
53
+ /** Deshabilita el trigger entero. */
54
+ disabled?: boolean;
55
+ className?: string;
56
+ }
57
+
58
+ /**
59
+ * Menú de acciones compacto, pensado para celdas de tabla y cabeceras.
60
+ * Reemplaza los patrones `<Dropdown>` de react-bootstrap.
61
+ *
62
+ * ```tsx
63
+ * <ActionMenu
64
+ * items={[
65
+ * { label: 'Editar', icon: <EditIcon />, onClick: handleEdit },
66
+ * { label: 'Duplicar', icon: <CopyIcon />, onClick: handleDup },
67
+ * { label: 'Eliminar', icon: <TrashIcon />, onClick: handleDel, danger: true, dividerBefore: true },
68
+ * ]}
69
+ * />
70
+ * ```
71
+ */
72
+ export function ActionMenu({
73
+ items,
74
+ trigger,
75
+ triggerTooltip = 'Acciones',
76
+ anchorOrigin = { vertical: 'bottom', horizontal: 'right' },
77
+ transformOrigin = { vertical: 'top', horizontal: 'right' },
78
+ menuSx,
79
+ disabled = false,
80
+ className,
81
+ }: ActionMenuProps) {
82
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
83
+ const open = Boolean(anchorEl);
84
+
85
+ const handleOpen = (event: MouseEvent<HTMLElement>) => {
86
+ if (disabled) return;
87
+ event.stopPropagation();
88
+ setAnchorEl(event.currentTarget);
89
+ };
90
+
91
+ const handleClose = () => setAnchorEl(null);
92
+
93
+ const handleItemClick = (
94
+ event: MouseEvent<HTMLLIElement>,
95
+ item: ActionMenuItem,
96
+ ) => {
97
+ event.stopPropagation();
98
+ item.onClick?.(event);
99
+ handleClose();
100
+ };
101
+
102
+ const triggerElement = trigger ? (
103
+ React.isValidElement(trigger) ? (
104
+ React.cloneElement(trigger as React.ReactElement<any>, {
105
+ onClick: handleOpen,
106
+ disabled,
107
+ })
108
+ ) : (
109
+ <span onClick={handleOpen}>{trigger}</span>
110
+ )
111
+ ) : (
112
+ <Tooltip title={triggerTooltip} arrow>
113
+ <span>
114
+ <IconButton
115
+ size="small"
116
+ onClick={handleOpen}
117
+ disabled={disabled}
118
+ aria-label={triggerTooltip}
119
+ >
120
+ <MoreVertIcon fontSize="small" />
121
+ </IconButton>
122
+ </span>
123
+ </Tooltip>
124
+ );
125
+
126
+ return (
127
+ <span className={className}>
128
+ {triggerElement}
129
+ <Menu
130
+ anchorEl={anchorEl}
131
+ open={open}
132
+ onClose={handleClose}
133
+ anchorOrigin={anchorOrigin}
134
+ transformOrigin={transformOrigin}
135
+ slotProps={{ paper: { sx: { minWidth: 180, ...(menuSx as any) } } }}
136
+ >
137
+ {items.map((item, idx) => {
138
+ const key = item.key ?? `${String(item.label)}-${idx}`;
139
+ const node = (
140
+ <MenuItem
141
+ key={key}
142
+ disabled={item.disabled}
143
+ onClick={(event) => handleItemClick(event, item)}
144
+ sx={(theme) => ({
145
+ color: item.danger ? theme.palette.error.main : 'inherit',
146
+ '& .MuiListItemIcon-root': {
147
+ color: item.danger
148
+ ? theme.palette.error.main
149
+ : 'inherit',
150
+ minWidth: 32,
151
+ },
152
+ })}
153
+ >
154
+ {item.icon && <ListItemIcon>{item.icon}</ListItemIcon>}
155
+ <ListItemText primary={item.label} />
156
+ </MenuItem>
157
+ );
158
+
159
+ if (item.dividerBefore && idx > 0) {
160
+ return (
161
+ <React.Fragment key={`${key}-frag`}>
162
+ <Divider />
163
+ {node}
164
+ </React.Fragment>
165
+ );
166
+ }
167
+ return node;
168
+ })}
169
+ </Menu>
170
+ </span>
171
+ );
172
+ }
173
+
174
+ export default ActionMenu;
@@ -0,0 +1,2 @@
1
+ export { ActionMenu, default } from './ActionMenu';
2
+ export type { ActionMenuProps, ActionMenuItem } from './ActionMenu';
@@ -0,0 +1,272 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Box, Typography, Button, IconButton, Tooltip } from '@mui/material';
4
+ import NotificationsIcon from '@mui/icons-material/Notifications';
5
+ import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
6
+ import PersonIcon from '@mui/icons-material/Person';
7
+ import SettingsIcon from '@mui/icons-material/Settings';
8
+ import LogoutIcon from '@mui/icons-material/Logout';
9
+ import DashboardIcon from '@mui/icons-material/Dashboard';
10
+
11
+ import { AppBar } from './AppBar';
12
+ import { AppBarBrand } from './AppBarBrand';
13
+ import { AppBarMenuToggle } from './AppBarMenuToggle';
14
+ import { AppBarUserMenu } from './AppBarUserMenu';
15
+
16
+ const meta: Meta<typeof AppBar> = {
17
+ title: 'Components/AppBar',
18
+ component: AppBar,
19
+ tags: ['autodocs'],
20
+ parameters: {
21
+ layout: 'fullscreen',
22
+ docs: {
23
+ description: {
24
+ component:
25
+ 'AppBar (header superior) pensado como shell compositivo. El consumer arma su contenido con los sub-componentes `AppBarBrand`, `AppBarMenuToggle` y `AppBarUserMenu`, además de cualquier acción custom. Reemplaza el header de Metronic.',
26
+ },
27
+ },
28
+ },
29
+ argTypes: {
30
+ position: {
31
+ control: 'select',
32
+ options: ['fixed', 'sticky', 'static', 'absolute', 'relative'],
33
+ },
34
+ color: {
35
+ control: 'select',
36
+ options: ['default', 'primary', 'secondary', 'transparent', 'inherit'],
37
+ },
38
+ elevation: { control: { type: 'number', min: 0, max: 24, step: 1 } },
39
+ height: { control: { type: 'number', min: 48, max: 96, step: 4 } },
40
+ },
41
+ };
42
+
43
+ export default meta;
44
+ type Story = StoryObj<typeof AppBar>;
45
+
46
+ // ── Helpers ──────────────────────────────────────────────────────────────
47
+
48
+ const SampleLogo = () => (
49
+ <Box
50
+ sx={{
51
+ width: 32,
52
+ height: 32,
53
+ borderRadius: 1,
54
+ display: 'flex',
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ backgroundColor: 'primary.main',
58
+ color: 'primary.contrastText',
59
+ fontWeight: 700,
60
+ fontSize: 14,
61
+ }}
62
+ >
63
+ F
64
+ </Box>
65
+ );
66
+
67
+ const SampleBody = () => (
68
+ <Box sx={{ p: 3 }}>
69
+ <Typography variant="h5" gutterBottom>
70
+ Contenido de la página
71
+ </Typography>
72
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
73
+ Este bloque sólo demuestra el layout bajo el AppBar.
74
+ </Typography>
75
+ {Array.from({ length: 20 }).map((_, idx) => (
76
+ <Typography key={idx} variant="body2" sx={{ mb: 1 }}>
77
+ Línea {idx + 1} — contenido de ejemplo para scroll sticky.
78
+ </Typography>
79
+ ))}
80
+ </Box>
81
+ );
82
+
83
+ const userMenuItems = [
84
+ {
85
+ label: 'Mi perfil',
86
+ icon: <PersonIcon fontSize="small" />,
87
+ onClick: () => console.log('profile'),
88
+ },
89
+ {
90
+ label: 'Configuración',
91
+ icon: <SettingsIcon fontSize="small" />,
92
+ onClick: () => console.log('settings'),
93
+ },
94
+ {
95
+ label: 'Cerrar sesión',
96
+ icon: <LogoutIcon fontSize="small" />,
97
+ onClick: () => console.log('logout'),
98
+ danger: true,
99
+ dividerBefore: true,
100
+ },
101
+ ];
102
+
103
+ // ── Stories ──────────────────────────────────────────────────────────────
104
+
105
+ export const BasicHeader: Story = {
106
+ args: {
107
+ position: 'sticky',
108
+ color: 'default',
109
+ elevation: 1,
110
+ height: 64,
111
+ },
112
+ render: (args) => (
113
+ <>
114
+ <AppBar {...args}>
115
+ <AppBarBrand
116
+ logo={<SampleLogo />}
117
+ title="Afiliaciones"
118
+ subtitle="Panel administrativo"
119
+ />
120
+ </AppBar>
121
+ <SampleBody />
122
+ </>
123
+ ),
124
+ };
125
+
126
+ export const WithMenuToggle: Story = {
127
+ render: (args) => {
128
+ const [open, setOpen] = useState(false);
129
+ return (
130
+ <>
131
+ <AppBar
132
+ {...args}
133
+ onMenuToggle={() => setOpen((value) => !value)}
134
+ menuOpen={open}
135
+ >
136
+ <AppBarMenuToggle />
137
+ <AppBarBrand logo={<SampleLogo />} title="Afiliaciones" />
138
+ </AppBar>
139
+ <Box sx={{ p: 3 }}>
140
+ <Typography variant="body2">
141
+ Drawer {open ? 'abierto' : 'cerrado'} — el icono cambia según el
142
+ estado. Click en el botón hamburguesa para alternar.
143
+ </Typography>
144
+ </Box>
145
+ </>
146
+ );
147
+ },
148
+ };
149
+
150
+ export const WithUserMenu: Story = {
151
+ render: (args) => (
152
+ <>
153
+ <AppBar {...args}>
154
+ <AppBarBrand logo={<SampleLogo />} title="Afiliaciones" />
155
+ <Box sx={{ flex: 1 }} />
156
+ <AppBarUserMenu
157
+ user={{
158
+ name: 'Andrea Pineda',
159
+ email: 'andrea@soyfri.com',
160
+ }}
161
+ items={userMenuItems}
162
+ />
163
+ </AppBar>
164
+ <SampleBody />
165
+ </>
166
+ ),
167
+ };
168
+
169
+ export const CompleteDashboardHeader: Story = {
170
+ render: (args) => {
171
+ const [open, setOpen] = useState(true);
172
+ return (
173
+ <>
174
+ <AppBar
175
+ {...args}
176
+ onMenuToggle={() => setOpen((value) => !value)}
177
+ menuOpen={open}
178
+ >
179
+ <AppBarMenuToggle />
180
+ <AppBarBrand
181
+ logo={<SampleLogo />}
182
+ title="Afiliaciones"
183
+ subtitle="Panel administrativo"
184
+ onClick={() => console.log('go home')}
185
+ />
186
+ <Box sx={{ flex: 1 }} />
187
+ <Tooltip title="Notificaciones" arrow>
188
+ <IconButton color="inherit">
189
+ <NotificationsIcon />
190
+ </IconButton>
191
+ </Tooltip>
192
+ <Tooltip title="Ayuda" arrow>
193
+ <IconButton color="inherit">
194
+ <HelpOutlineIcon />
195
+ </IconButton>
196
+ </Tooltip>
197
+ <AppBarUserMenu
198
+ user={{
199
+ name: 'Andrea Pineda',
200
+ email: 'andrea@soyfri.com',
201
+ }}
202
+ items={userMenuItems}
203
+ />
204
+ </AppBar>
205
+ <SampleBody />
206
+ </>
207
+ );
208
+ },
209
+ };
210
+
211
+ export const TransparentVariant: Story = {
212
+ args: {
213
+ color: 'transparent',
214
+ elevation: 0,
215
+ },
216
+ render: (args) => (
217
+ <Box
218
+ sx={{
219
+ minHeight: '100vh',
220
+ background:
221
+ 'linear-gradient(135deg, #f0f4ff 0%, #e6f7ff 50%, #fff5f5 100%)',
222
+ }}
223
+ >
224
+ <AppBar {...args}>
225
+ <AppBarBrand logo={<SampleLogo />} title="Afiliaciones" />
226
+ <Box sx={{ flex: 1 }} />
227
+ <Button variant="contained" color="primary" size="small">
228
+ Iniciar sesión
229
+ </Button>
230
+ </AppBar>
231
+ <Box sx={{ p: 3 }}>
232
+ <Typography variant="h5">Landing page transparente</Typography>
233
+ <Typography variant="body2" color="text.secondary">
234
+ Sin sombra, con borde sutil — útil para headers sobre hero sections.
235
+ </Typography>
236
+ </Box>
237
+ </Box>
238
+ ),
239
+ };
240
+
241
+ export const PrimaryColorWithMenu: Story = {
242
+ args: {
243
+ color: 'primary',
244
+ },
245
+ render: (args) => {
246
+ const [open, setOpen] = useState(false);
247
+ return (
248
+ <>
249
+ <AppBar
250
+ {...args}
251
+ onMenuToggle={() => setOpen((value) => !value)}
252
+ menuOpen={open}
253
+ >
254
+ <AppBarMenuToggle />
255
+ <DashboardIcon sx={{ mr: 1 }} />
256
+ <Typography variant="h6" sx={{ fontWeight: 700 }}>
257
+ Dashboard
258
+ </Typography>
259
+ <Box sx={{ flex: 1 }} />
260
+ <AppBarUserMenu
261
+ user={{
262
+ name: 'Andrea Pineda',
263
+ email: 'andrea@soyfri.com',
264
+ }}
265
+ items={userMenuItems}
266
+ />
267
+ </AppBar>
268
+ <SampleBody />
269
+ </>
270
+ );
271
+ },
272
+ };
@@ -0,0 +1,32 @@
1
+ import type { SxProps, Theme } from '@mui/material/styles';
2
+
3
+ export interface BuildAppBarSxArgs {
4
+ /** Altura en px. Default: 64. */
5
+ height?: number;
6
+ /** Color del AppBar (se pasa al MuiAppBar). */
7
+ transparent?: boolean;
8
+ }
9
+
10
+ /**
11
+ * sx del root del MuiAppBar. Define altura fija y layout flex del Toolbar
12
+ * interno. El color/elevación se dejan a las props nativas de MuiAppBar.
13
+ */
14
+ export function buildAppBarSx({
15
+ height = 64,
16
+ transparent = false,
17
+ }: BuildAppBarSxArgs): SxProps<Theme> {
18
+ return (theme) => ({
19
+ minHeight: height,
20
+ justifyContent: 'center',
21
+ backgroundImage: 'none',
22
+ ...(transparent && {
23
+ backgroundColor: 'transparent',
24
+ boxShadow: 'none',
25
+ borderBottom: `1px solid ${theme.palette.divider}`,
26
+ }),
27
+ '& .MuiToolbar-root': {
28
+ minHeight: height,
29
+ gap: 1.5,
30
+ },
31
+ });
32
+ }
@@ -0,0 +1,123 @@
1
+ import React, { useMemo, type ReactNode } from 'react';
2
+ import {
3
+ AppBar as MuiAppBar,
4
+ Toolbar,
5
+ type AppBarProps as MuiAppBarProps,
6
+ } from '@mui/material';
7
+ import {
8
+ useTheme,
9
+ type SxProps,
10
+ type Theme,
11
+ } from '@mui/material/styles';
12
+
13
+ import { AppBarContext } from './AppBarContext';
14
+ import { buildAppBarSx } from './AppBar.sx';
15
+ import { resolvePreset } from '../_shared/resolvePreset';
16
+
17
+ export type AppBarPosition = 'fixed' | 'sticky' | 'static' | 'absolute' | 'relative';
18
+ export type AppBarColor =
19
+ | 'default'
20
+ | 'primary'
21
+ | 'secondary'
22
+ | 'transparent'
23
+ | 'inherit';
24
+
25
+ export interface AppBarProps {
26
+ /** Contenido del AppBar (típicamente sub-componentes + acciones custom). */
27
+ children?: ReactNode;
28
+ /** Posicionamiento. Default: `'sticky'`. */
29
+ position?: AppBarPosition;
30
+ /** Color. Default: `'default'`. */
31
+ color?: AppBarColor;
32
+ /** Nivel de sombra. Default: 1. */
33
+ elevation?: number;
34
+ /** Altura en px. Default: 64. */
35
+ height?: number;
36
+ /**
37
+ * Handler del botón hamburguesa. Se expone vía AppBarContext para que el
38
+ * sub-componente `<AppBarMenuToggle>` lo consuma sin prop drilling.
39
+ */
40
+ onMenuToggle?: () => void;
41
+ /** Estado del drawer asociado (para que el icono del toggle cambie). */
42
+ menuOpen?: boolean;
43
+ /**
44
+ * Nombre del preset de estilo registrado en `theme.styles.AppBar`.
45
+ * - `"default"` (o ausente) = estilo built-in del paquete.
46
+ */
47
+ preset?: string;
48
+ /** sx del root (se mergea después del preset). */
49
+ sx?: SxProps<Theme>;
50
+ /** sx del Toolbar interno. */
51
+ toolbarSx?: SxProps<Theme>;
52
+ className?: string;
53
+ /** Otras props nativas del MuiAppBar (ej. `enableColorOnDark`). */
54
+ appBarProps?: Omit<
55
+ MuiAppBarProps,
56
+ 'position' | 'color' | 'elevation' | 'sx' | 'children' | 'className'
57
+ >;
58
+ }
59
+
60
+ /**
61
+ * AppBar (header superior) del paquete. Se diseñó como un shell compositivo
62
+ * — el consumer arma el contenido con los sub-componentes que exporta el
63
+ * paquete (`AppBarBrand`, `AppBarMenuToggle`, `AppBarUserMenu`) + cualquier
64
+ * otra cosa custom.
65
+ *
66
+ * Patrón recomendado para navegación en dashboards:
67
+ *
68
+ * ```tsx
69
+ * <AppBar onMenuToggle={toggleDrawer}>
70
+ * <AppBarMenuToggle />
71
+ * <AppBarBrand logo={<Logo />} title="Afiliaciones" />
72
+ * <Box sx={{ flex: 1 }} />
73
+ * <AppBarUserMenu user={user} items={menuItems} />
74
+ * </AppBar>
75
+ * ```
76
+ */
77
+ export function AppBar({
78
+ children,
79
+ position = 'sticky',
80
+ color = 'default',
81
+ elevation = 1,
82
+ height = 64,
83
+ onMenuToggle,
84
+ menuOpen,
85
+ preset,
86
+ sx,
87
+ toolbarSx,
88
+ className,
89
+ appBarProps,
90
+ }: AppBarProps) {
91
+ const theme = useTheme();
92
+ const presetSx = resolvePreset('AppBar', preset, theme);
93
+
94
+ const transparent = color === 'transparent';
95
+
96
+ const rootSx: SxProps<Theme> = [
97
+ buildAppBarSx({ height, transparent }),
98
+ ...(presetSx ? [presetSx] : []),
99
+ ...(Array.isArray(sx) ? sx : sx ? [sx] : []),
100
+ ];
101
+
102
+ const contextValue = useMemo(
103
+ () => ({ onMenuToggle, menuOpen }),
104
+ [onMenuToggle, menuOpen],
105
+ );
106
+
107
+ return (
108
+ <AppBarContext.Provider value={contextValue}>
109
+ <MuiAppBar
110
+ position={position}
111
+ color={color === 'transparent' ? 'transparent' : color}
112
+ elevation={elevation}
113
+ className={className}
114
+ sx={rootSx}
115
+ {...appBarProps}
116
+ >
117
+ <Toolbar sx={toolbarSx}>{children}</Toolbar>
118
+ </MuiAppBar>
119
+ </AppBarContext.Provider>
120
+ );
121
+ }
122
+
123
+ export default AppBar;