@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.
- package/.dockerignore +8 -0
- package/.github/workflows/publish.yml +107 -0
- package/.prettierrc +3 -0
- package/.storybook/main.ts +19 -0
- package/.storybook/preview.ts +14 -0
- package/.storybook/vitest.setup.ts +9 -0
- package/Dockerfile +37 -0
- package/build.js +102 -0
- package/chromatic.config.json +5 -0
- package/cleanDirectories.js +40 -0
- package/dist/README.md +243 -0
- package/dist/components/Icon/Icon.js +1 -1
- package/dist/components/Table/Table.js +1 -1
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -1
- package/dist/mui.d.ts +1 -0
- package/dist/package.json +197 -0
- package/package.json +4 -32
- package/rollup.config.cjs +87 -0
- package/src/components/ActionMenu/ActionMenu.stories.tsx +230 -0
- package/src/components/ActionMenu/ActionMenu.tsx +174 -0
- package/src/components/ActionMenu/index.ts +2 -0
- package/src/components/AppBar/AppBar.stories.tsx +272 -0
- package/src/components/AppBar/AppBar.sx.ts +32 -0
- package/src/components/AppBar/AppBar.tsx +123 -0
- package/src/components/AppBar/AppBarBrand.tsx +120 -0
- package/src/components/AppBar/AppBarContext.ts +25 -0
- package/src/components/AppBar/AppBarMenuToggle.tsx +90 -0
- package/src/components/AppBar/AppBarUserMenu.tsx +217 -0
- package/src/components/AppBar/index.ts +25 -0
- package/src/components/Autocomplete/Autocomplete.definitions.ts +477 -0
- package/src/components/Autocomplete/Autocomplete.helpers.ts +60 -0
- package/src/components/Autocomplete/Autocomplete.stories.tsx +748 -0
- package/src/components/Autocomplete/Autocomplete.sx.ts +30 -0
- package/src/components/Autocomplete/Autocomplete.tsx +361 -0
- package/src/components/Autocomplete/Autocomplete.types.ts +13 -0
- package/src/components/Autocomplete/_parts/AutocompleteChips.tsx +55 -0
- package/src/components/Autocomplete/_parts/AutocompleteLoader.tsx +17 -0
- package/src/components/Autocomplete/_parts/AutocompleteOption.tsx +31 -0
- package/src/components/Autocomplete/index.ts +12 -0
- package/src/components/Avatar/Avatar.definitions.ts +162 -0
- package/src/components/Avatar/Avatar.stories.tsx +258 -0
- package/src/components/Avatar/Avatar.tsx +206 -0
- package/src/components/Avatar/index.ts +1 -0
- package/src/components/Button/Button.definition.ts +97 -0
- package/src/components/Button/Button.stories.tsx +285 -0
- package/src/components/Button/Button.tsx +67 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Card/Card.definition.ts +5 -0
- package/src/components/Card/Card.stories.tsx +221 -0
- package/src/components/Card/Card.sx.ts +104 -0
- package/src/components/Card/Card.tsx +200 -0
- package/src/components/Card/index.ts +9 -0
- package/src/components/Chip/Chip.definitions.ts +167 -0
- package/src/components/Chip/Chip.stories.tsx +265 -0
- package/src/components/Chip/Chip.tsx +61 -0
- package/src/components/Chip/index.ts +1 -0
- package/src/components/Column/Column.tsx +29 -0
- package/src/components/Column/index.ts +1 -0
- package/src/components/DatePicker/DatePicker.definitions.ts +228 -0
- package/src/components/DatePicker/DatePicker.helpers.ts +24 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +309 -0
- package/src/components/DatePicker/DatePicker.sx.ts +33 -0
- package/src/components/DatePicker/DatePicker.tsx +189 -0
- package/src/components/DatePicker/DatePicker.types.ts +10 -0
- package/src/components/DatePicker/index.ts +9 -0
- package/src/components/DateRangePicker/DateRangePicker.definitions.ts +191 -0
- package/src/components/DateRangePicker/DateRangePicker.stories.tsx +252 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +56 -0
- package/src/components/DateRangePicker/index.ts +1 -0
- package/src/components/DateTimePicker/DateTimePicker.definitions.ts +256 -0
- package/src/components/DateTimePicker/DateTimePicker.helpers.ts +38 -0
- package/src/components/DateTimePicker/DateTimePicker.stories.tsx +418 -0
- package/src/components/DateTimePicker/DateTimePicker.sx.ts +30 -0
- package/src/components/DateTimePicker/DateTimePicker.tsx +225 -0
- package/src/components/DateTimePicker/DateTimePicker.types.ts +10 -0
- package/src/components/DateTimePicker/index.ts +9 -0
- package/src/components/Drawer/Drawer.stories.tsx +270 -0
- package/src/components/Drawer/Drawer.sx.ts +106 -0
- package/src/components/Drawer/Drawer.tsx +214 -0
- package/src/components/Drawer/DrawerContext.ts +26 -0
- package/src/components/Drawer/DrawerItem.tsx +110 -0
- package/src/components/Drawer/index.ts +10 -0
- package/src/components/Flyout/Flyout.stories.tsx +282 -0
- package/src/components/Flyout/Flyout.tsx +122 -0
- package/src/components/Flyout/index.ts +1 -0
- package/src/components/Gallery/Gallery.definition.tsx +37 -0
- package/src/components/Gallery/Gallery.stories.tsx +82 -0
- package/src/components/Gallery/Gallery.tsx +118 -0
- package/src/components/Gallery/GalleryLightbox.tsx +170 -0
- package/src/components/Gallery/GalleryMain.tsx +84 -0
- package/src/components/Gallery/GalleryThumbnails.tsx +106 -0
- package/src/components/Gallery/index.ts +1 -0
- package/src/components/Icon/Icon.stories.tsx +121 -0
- package/src/components/Icon/Icon.tsx +175 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Input/Input.definitions.ts +324 -0
- package/src/components/Input/Input.helpers.ts +49 -0
- package/src/components/Input/Input.stories.tsx +499 -0
- package/src/components/Input/Input.sx.ts +42 -0
- package/src/components/Input/Input.tsx +141 -0
- package/src/components/Input/Input.types.ts +10 -0
- package/src/components/Input/index.ts +9 -0
- package/src/components/InputGroup/InputGroup.definitions.ts +158 -0
- package/src/components/InputGroup/InputGroup.stories.tsx +267 -0
- package/src/components/InputGroup/InputGroup.tsx +179 -0
- package/src/components/InputGroup/index.ts +1 -0
- package/src/components/MenuButton/MenuButton.stories.tsx +197 -0
- package/src/components/MenuButton/MenuButton.tsx +100 -0
- package/src/components/MenuButton/index.ts +1 -0
- package/src/components/Modal/Modal.stories.tsx +721 -0
- package/src/components/Modal/Modal.tsx +355 -0
- package/src/components/Modal/ModalBody.tsx +16 -0
- package/src/components/Modal/ModalFooter.tsx +71 -0
- package/src/components/Modal/ModalHeader.tsx +18 -0
- package/src/components/Modal/index.ts +6 -0
- package/src/components/PageLoader/PageLoader.stories.tsx +217 -0
- package/src/components/PageLoader/PageLoader.tsx +96 -0
- package/src/components/PageLoader/index.ts +2 -0
- package/src/components/ScrollTopButton/ScrollTopButton.stories.tsx +158 -0
- package/src/components/ScrollTopButton/ScrollTopButton.tsx +135 -0
- package/src/components/ScrollTopButton/index.ts +8 -0
- package/src/components/ScrollTopButton/scrollToTop.ts +37 -0
- package/src/components/Select/Select.definitions.ts +602 -0
- package/src/components/Select/Select.helpers.ts +71 -0
- package/src/components/Select/Select.stories.tsx +687 -0
- package/src/components/Select/Select.sx.ts +14 -0
- package/src/components/Select/Select.tsx +429 -0
- package/src/components/Select/Select.types.ts +15 -0
- package/src/components/Select/_parts/SelectMenuItem.tsx +40 -0
- package/src/components/Select/_parts/SelectSearchHeader.tsx +51 -0
- package/src/components/Select/_parts/SelectValue.tsx +96 -0
- package/src/components/Select/index.ts +14 -0
- package/src/components/Stat/Stat.stories.tsx +85 -0
- package/src/components/Stat/Stat.tsx +117 -0
- package/src/components/Stat/index.ts +2 -0
- package/src/components/StatusMessage/StatusMessage.stories.tsx +130 -0
- package/src/components/StatusMessage/StatusMessage.tsx +162 -0
- package/src/components/StatusMessage/index.ts +2 -0
- package/src/components/Stepper/Step.tsx +21 -0
- package/src/components/Stepper/Stepper.definition.ts +75 -0
- package/src/components/Stepper/Stepper.stories.tsx +122 -0
- package/src/components/Stepper/Stepper.tsx +75 -0
- package/src/components/Stepper/index.ts +2 -0
- package/src/components/Table/EmptyTable.png +0 -0
- package/src/components/Table/Table.definition.ts +580 -0
- package/src/components/Table/Table.stories.tsx +853 -0
- package/src/components/Table/Table.tsx +495 -0
- package/src/components/Table/data.ts +134 -0
- package/src/components/Table/exportsUtils.ts +195 -0
- package/src/components/Table/index.ts +3 -0
- package/src/components/Table/types.ts +34 -0
- package/src/components/Tabs/Tab.definition.ts +53 -0
- package/src/components/Tabs/Tab.tsx +19 -0
- package/src/components/Tabs/Tabs.stories.tsx +118 -0
- package/src/components/Tabs/Tabs.tsx +99 -0
- package/src/components/Tabs/_tabUtils.tsx +4 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Timeline/Timeline.definition.ts +43 -0
- package/src/components/Timeline/Timeline.stories.tsx +108 -0
- package/src/components/Timeline/Timeline.tsx +49 -0
- package/src/components/Timeline/TimelineItem.tsx +31 -0
- package/src/components/Timeline/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +129 -0
- package/src/components/Tooltip/Tooltip.tsx +58 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/_shared/formField.sx.ts +118 -0
- package/src/components/_shared/resolvePreset.ts +35 -0
- package/src/hooks/ClipBoard/ClipBoard.stories.tsx +168 -0
- package/src/hooks/ClipBoard/ClipBoard.tsx +131 -0
- package/src/hooks/ClipBoard/ClipboardUnifiedDemo.tsx +111 -0
- package/src/hooks/ClipBoard/index.ts +1 -0
- package/src/hooks/Wizard/Wizard.stories.tsx +301 -0
- package/src/hooks/Wizard/WizardContext.tsx +166 -0
- package/src/hooks/Wizard/index.ts +6 -0
- package/src/hooks/Wizard/useWizard.ts +13 -0
- package/src/index.ts +17 -0
- package/src/mui.ts +54 -0
- package/src/styles.css +3 -0
- package/src/theme/componentStyles.ts +47 -0
- package/src/theme/tokens.ts +43 -0
- package/tailwind.config.js +10 -0
- package/tsconfig.json +48 -0
- package/tsup.config.js +41 -0
- package/vite.config.js +132 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Box, Typography, Avatar } from '@mui/material';
|
|
4
|
+
import HomeIcon from '@mui/icons-material/Home';
|
|
5
|
+
import PersonIcon from '@mui/icons-material/Person';
|
|
6
|
+
import SettingsIcon from '@mui/icons-material/Settings';
|
|
7
|
+
import ReceiptIcon from '@mui/icons-material/Receipt';
|
|
8
|
+
import LogoutIcon from '@mui/icons-material/Logout';
|
|
9
|
+
import NotificationsIcon from '@mui/icons-material/Notifications';
|
|
10
|
+
|
|
11
|
+
import { Drawer } from './Drawer';
|
|
12
|
+
import { DrawerItem } from './DrawerItem';
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof Drawer> = {
|
|
15
|
+
title: 'Components/Drawer',
|
|
16
|
+
component: Drawer,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
parameters: {
|
|
19
|
+
layout: 'fullscreen',
|
|
20
|
+
docs: {
|
|
21
|
+
description: {
|
|
22
|
+
component:
|
|
23
|
+
'Drawer lateral para navegación. Soporta modo `collapsed` (mini-variant con solo iconos) y reemplaza el `DrawerComponent` de Metronic. Los sub-componentes `DrawerItem` reaccionan al estado `collapsed` vía context y muestran tooltip automático al hover cuando el drawer está mini.',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
argTypes: {
|
|
28
|
+
variant: {
|
|
29
|
+
control: 'select',
|
|
30
|
+
options: ['permanent', 'persistent', 'temporary'],
|
|
31
|
+
description:
|
|
32
|
+
'`permanent` (default) = siempre visible en desktop. `persistent` = oculta/muestra empujando contenido. `temporary` = flotante con backdrop (típico móvil).',
|
|
33
|
+
},
|
|
34
|
+
anchor: {
|
|
35
|
+
control: 'select',
|
|
36
|
+
options: ['left', 'right', 'top', 'bottom'],
|
|
37
|
+
},
|
|
38
|
+
collapsed: {
|
|
39
|
+
control: 'boolean',
|
|
40
|
+
description: 'Estado mini (solo iconos). Solo aplica en `permanent`/`persistent`.',
|
|
41
|
+
},
|
|
42
|
+
expandedWidth: { control: { type: 'number', min: 180, max: 400, step: 10 } },
|
|
43
|
+
collapsedWidth: { control: { type: 'number', min: 48, max: 120, step: 4 } },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default meta;
|
|
48
|
+
type Story = StoryObj<typeof Drawer>;
|
|
49
|
+
|
|
50
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const SampleLogo = () => (
|
|
53
|
+
<Typography variant="h6" sx={{ fontWeight: 700, color: 'primary.main' }}>
|
|
54
|
+
soyfri
|
|
55
|
+
</Typography>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const SampleFooter = ({ collapsed }: { collapsed: boolean }) => (
|
|
59
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
60
|
+
<Avatar sx={{ width: 36, height: 36 }}>AP</Avatar>
|
|
61
|
+
{!collapsed && (
|
|
62
|
+
<Box sx={{ minWidth: 0 }}>
|
|
63
|
+
<Typography variant="body2" noWrap sx={{ fontWeight: 600 }}>
|
|
64
|
+
Andrea Pérez
|
|
65
|
+
</Typography>
|
|
66
|
+
<Typography variant="caption" color="text.secondary" noWrap>
|
|
67
|
+
admin@soyfri.com
|
|
68
|
+
</Typography>
|
|
69
|
+
</Box>
|
|
70
|
+
)}
|
|
71
|
+
</Box>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// ── Stories ──────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export const ExpandedPermanent: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
variant: 'permanent',
|
|
79
|
+
anchor: 'left',
|
|
80
|
+
collapsed: false,
|
|
81
|
+
expandedWidth: 260,
|
|
82
|
+
collapsedWidth: 72,
|
|
83
|
+
header: <SampleLogo />,
|
|
84
|
+
},
|
|
85
|
+
render: (args) => (
|
|
86
|
+
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
87
|
+
<Drawer {...args}>
|
|
88
|
+
<DrawerItem icon={<HomeIcon />} label="Inicio" active />
|
|
89
|
+
<DrawerItem icon={<PersonIcon />} label="Perfil" />
|
|
90
|
+
<DrawerItem
|
|
91
|
+
icon={<NotificationsIcon />}
|
|
92
|
+
label="Notificaciones"
|
|
93
|
+
endAdornment={
|
|
94
|
+
<Typography
|
|
95
|
+
variant="caption"
|
|
96
|
+
sx={{
|
|
97
|
+
bgcolor: 'primary.main',
|
|
98
|
+
color: '#fff',
|
|
99
|
+
borderRadius: 10,
|
|
100
|
+
px: 1,
|
|
101
|
+
py: 0.25,
|
|
102
|
+
fontWeight: 600,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
3
|
|
106
|
+
</Typography>
|
|
107
|
+
}
|
|
108
|
+
/>
|
|
109
|
+
<DrawerItem icon={<ReceiptIcon />} label="Facturación" />
|
|
110
|
+
<DrawerItem icon={<SettingsIcon />} label="Configuración" />
|
|
111
|
+
</Drawer>
|
|
112
|
+
<Box sx={{ flex: 1, p: 3 }}>
|
|
113
|
+
<Typography variant="h5">Contenido principal</Typography>
|
|
114
|
+
<Typography color="text.secondary">
|
|
115
|
+
El drawer permanente convive con el contenido y no lo tapa.
|
|
116
|
+
</Typography>
|
|
117
|
+
</Box>
|
|
118
|
+
</Box>
|
|
119
|
+
),
|
|
120
|
+
parameters: {
|
|
121
|
+
docs: {
|
|
122
|
+
description: {
|
|
123
|
+
story:
|
|
124
|
+
'Drawer permanente expandido con logo en header y lista de items. Uno de los items tiene un badge como `endAdornment`.',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const CollapsedPermanent: Story = {
|
|
131
|
+
...ExpandedPermanent,
|
|
132
|
+
args: {
|
|
133
|
+
...ExpandedPermanent.args,
|
|
134
|
+
collapsed: true,
|
|
135
|
+
},
|
|
136
|
+
parameters: {
|
|
137
|
+
docs: {
|
|
138
|
+
description: {
|
|
139
|
+
story:
|
|
140
|
+
'El mismo drawer en modo mini. Los labels se ocultan y al hacer hover sobre cada item aparece un tooltip con su texto. Notar que el badge `endAdornment` se oculta también en modo collapsed.',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const ToggleableDrawer: Story = {
|
|
147
|
+
render: () => {
|
|
148
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
152
|
+
<Drawer
|
|
153
|
+
variant="permanent"
|
|
154
|
+
collapsed={collapsed}
|
|
155
|
+
onToggleCollapse={() => setCollapsed((v) => !v)}
|
|
156
|
+
header={<SampleLogo />}
|
|
157
|
+
footer={<SampleFooter collapsed={collapsed} />}
|
|
158
|
+
>
|
|
159
|
+
<DrawerItem icon={<HomeIcon />} label="Inicio" active />
|
|
160
|
+
<DrawerItem icon={<PersonIcon />} label="Perfil" />
|
|
161
|
+
<DrawerItem icon={<ReceiptIcon />} label="Facturación" />
|
|
162
|
+
<DrawerItem icon={<SettingsIcon />} label="Configuración" />
|
|
163
|
+
<DrawerItem icon={<LogoutIcon />} label="Cerrar sesión" danger />
|
|
164
|
+
</Drawer>
|
|
165
|
+
<Box sx={{ flex: 1, p: 3 }}>
|
|
166
|
+
<Typography variant="h5">
|
|
167
|
+
Drawer: {collapsed ? 'colapsado' : 'expandido'}
|
|
168
|
+
</Typography>
|
|
169
|
+
<Typography color="text.secondary">
|
|
170
|
+
Usá el chevron del header para alternar. El footer se adapta
|
|
171
|
+
(avatar solo cuando está mini, avatar + texto cuando está
|
|
172
|
+
expandido).
|
|
173
|
+
</Typography>
|
|
174
|
+
</Box>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
parameters: {
|
|
179
|
+
docs: {
|
|
180
|
+
description: {
|
|
181
|
+
story:
|
|
182
|
+
'Drawer con botón chevron funcional (el que provee el paquete) y footer que reacciona al estado. Incluye un item `danger` (cerrar sesión) que se pinta en color error.',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const TemporaryDrawer: Story = {
|
|
189
|
+
render: () => {
|
|
190
|
+
const [open, setOpen] = useState(false);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<Box sx={{ height: '100vh', p: 3 }}>
|
|
194
|
+
<Typography variant="h5" gutterBottom>
|
|
195
|
+
Drawer temporal (para móvil)
|
|
196
|
+
</Typography>
|
|
197
|
+
<Typography
|
|
198
|
+
sx={{ cursor: 'pointer', color: 'primary.main' }}
|
|
199
|
+
onClick={() => setOpen(true)}
|
|
200
|
+
>
|
|
201
|
+
→ Abrir drawer
|
|
202
|
+
</Typography>
|
|
203
|
+
|
|
204
|
+
<Drawer
|
|
205
|
+
variant="temporary"
|
|
206
|
+
open={open}
|
|
207
|
+
onClose={() => setOpen(false)}
|
|
208
|
+
header={<SampleLogo />}
|
|
209
|
+
>
|
|
210
|
+
<DrawerItem
|
|
211
|
+
icon={<HomeIcon />}
|
|
212
|
+
label="Inicio"
|
|
213
|
+
onClick={() => setOpen(false)}
|
|
214
|
+
/>
|
|
215
|
+
<DrawerItem
|
|
216
|
+
icon={<PersonIcon />}
|
|
217
|
+
label="Perfil"
|
|
218
|
+
onClick={() => setOpen(false)}
|
|
219
|
+
/>
|
|
220
|
+
<DrawerItem
|
|
221
|
+
icon={<SettingsIcon />}
|
|
222
|
+
label="Configuración"
|
|
223
|
+
onClick={() => setOpen(false)}
|
|
224
|
+
/>
|
|
225
|
+
</Drawer>
|
|
226
|
+
</Box>
|
|
227
|
+
);
|
|
228
|
+
},
|
|
229
|
+
parameters: {
|
|
230
|
+
docs: {
|
|
231
|
+
description: {
|
|
232
|
+
story:
|
|
233
|
+
'Drawer `temporary`: flota por encima del contenido con backdrop. Cierra con ESC, click en backdrop, o handler explícito en cada item. El modo `collapsed` no aplica acá.',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const RightAnchored: Story = {
|
|
240
|
+
args: {
|
|
241
|
+
variant: 'permanent',
|
|
242
|
+
anchor: 'right',
|
|
243
|
+
expandedWidth: 320,
|
|
244
|
+
header: (
|
|
245
|
+
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
|
246
|
+
Panel lateral
|
|
247
|
+
</Typography>
|
|
248
|
+
),
|
|
249
|
+
},
|
|
250
|
+
render: (args) => (
|
|
251
|
+
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
252
|
+
<Box sx={{ flex: 1, p: 3 }}>
|
|
253
|
+
<Typography variant="h5">Contenido principal</Typography>
|
|
254
|
+
</Box>
|
|
255
|
+
<Drawer {...args}>
|
|
256
|
+
<DrawerItem icon={<NotificationsIcon />} label="Notificaciones" />
|
|
257
|
+
<DrawerItem icon={<PersonIcon />} label="Contactos" />
|
|
258
|
+
<DrawerItem icon={<SettingsIcon />} label="Ajustes" />
|
|
259
|
+
</Drawer>
|
|
260
|
+
</Box>
|
|
261
|
+
),
|
|
262
|
+
parameters: {
|
|
263
|
+
docs: {
|
|
264
|
+
description: {
|
|
265
|
+
story:
|
|
266
|
+
'Drawer anclado a la derecha. Útil para paneles secundarios (inbox, notificaciones, detalles) en lugar de navegación principal.',
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material/styles';
|
|
2
|
+
|
|
3
|
+
export interface BuildDrawerSxArgs {
|
|
4
|
+
/** Ancho actual del drawer (expanded o collapsed). */
|
|
5
|
+
width: number;
|
|
6
|
+
/** Duración de la transición de ancho en ms. */
|
|
7
|
+
transitionDuration?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* sx aplicado al root del MuiDrawer. Controla el ancho animado y el paper
|
|
12
|
+
* interno. Los estilos visuales del paper (background, border) se dejan en
|
|
13
|
+
* manos del theme / preset.
|
|
14
|
+
*/
|
|
15
|
+
export function buildDrawerSx({
|
|
16
|
+
width,
|
|
17
|
+
transitionDuration = 200,
|
|
18
|
+
}: BuildDrawerSxArgs): SxProps<Theme> {
|
|
19
|
+
return {
|
|
20
|
+
width,
|
|
21
|
+
flexShrink: 0,
|
|
22
|
+
whiteSpace: 'nowrap',
|
|
23
|
+
boxSizing: 'border-box',
|
|
24
|
+
transition: (theme) =>
|
|
25
|
+
theme.transitions.create('width', {
|
|
26
|
+
easing: theme.transitions.easing.sharp,
|
|
27
|
+
duration: transitionDuration,
|
|
28
|
+
}),
|
|
29
|
+
'& .MuiDrawer-paper': {
|
|
30
|
+
width,
|
|
31
|
+
boxSizing: 'border-box',
|
|
32
|
+
overflowX: 'hidden',
|
|
33
|
+
transition: (theme) =>
|
|
34
|
+
theme.transitions.create('width', {
|
|
35
|
+
easing: theme.transitions.easing.sharp,
|
|
36
|
+
duration: transitionDuration,
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BuildDrawerItemSxArgs {
|
|
43
|
+
collapsed: boolean;
|
|
44
|
+
active?: boolean;
|
|
45
|
+
danger?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* sx para un item del drawer. Centra el icono cuando está colapsado y
|
|
50
|
+
* tiñe el fondo cuando el item está activo.
|
|
51
|
+
*/
|
|
52
|
+
export function buildDrawerItemSx({
|
|
53
|
+
collapsed,
|
|
54
|
+
active,
|
|
55
|
+
danger,
|
|
56
|
+
}: BuildDrawerItemSxArgs): SxProps<Theme> {
|
|
57
|
+
return (theme) => ({
|
|
58
|
+
minHeight: 44,
|
|
59
|
+
px: collapsed ? 1 : 2,
|
|
60
|
+
py: 1,
|
|
61
|
+
mx: 1,
|
|
62
|
+
my: 0.25,
|
|
63
|
+
borderRadius: 1,
|
|
64
|
+
display: 'flex',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
67
|
+
gap: collapsed ? 0 : 1.5,
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
color: danger
|
|
70
|
+
? theme.palette.error.main
|
|
71
|
+
: active
|
|
72
|
+
? theme.palette.primary.main
|
|
73
|
+
: theme.palette.text.primary,
|
|
74
|
+
backgroundColor: active
|
|
75
|
+
? theme.palette.action.selected
|
|
76
|
+
: 'transparent',
|
|
77
|
+
transition: theme.transitions.create(
|
|
78
|
+
['background-color', 'color', 'padding'],
|
|
79
|
+
{ duration: 150 },
|
|
80
|
+
),
|
|
81
|
+
'&:hover': {
|
|
82
|
+
backgroundColor: danger
|
|
83
|
+
? theme.palette.error.light + '22'
|
|
84
|
+
: theme.palette.action.hover,
|
|
85
|
+
},
|
|
86
|
+
'& .drawer-item__icon': {
|
|
87
|
+
display: 'flex',
|
|
88
|
+
alignItems: 'center',
|
|
89
|
+
justifyContent: 'center',
|
|
90
|
+
flexShrink: 0,
|
|
91
|
+
minWidth: 24,
|
|
92
|
+
},
|
|
93
|
+
'& .drawer-item__label': {
|
|
94
|
+
flex: 1,
|
|
95
|
+
minWidth: 0,
|
|
96
|
+
overflow: 'hidden',
|
|
97
|
+
textOverflow: 'ellipsis',
|
|
98
|
+
whiteSpace: 'nowrap',
|
|
99
|
+
opacity: collapsed ? 0 : 1,
|
|
100
|
+
width: collapsed ? 0 : 'auto',
|
|
101
|
+
transition: theme.transitions.create(['opacity', 'width'], {
|
|
102
|
+
duration: collapsed ? 100 : 200,
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import React, { useMemo, type ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Drawer as MuiDrawer,
|
|
4
|
+
IconButton,
|
|
5
|
+
Box,
|
|
6
|
+
type DrawerProps as MuiDrawerProps,
|
|
7
|
+
} from '@mui/material';
|
|
8
|
+
import {
|
|
9
|
+
useTheme,
|
|
10
|
+
type SxProps,
|
|
11
|
+
type Theme,
|
|
12
|
+
} from '@mui/material/styles';
|
|
13
|
+
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
14
|
+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
15
|
+
|
|
16
|
+
import { DrawerContext } from './DrawerContext';
|
|
17
|
+
import { buildDrawerSx } from './Drawer.sx';
|
|
18
|
+
import { resolvePreset } from '../_shared/resolvePreset';
|
|
19
|
+
|
|
20
|
+
// ── Tipos públicos ──────────────────────────────────────────────────────
|
|
21
|
+
export type DrawerVariant = 'permanent' | 'persistent' | 'temporary';
|
|
22
|
+
export type DrawerAnchor = 'left' | 'right' | 'top' | 'bottom';
|
|
23
|
+
|
|
24
|
+
export interface DrawerProps {
|
|
25
|
+
/** Contenido del drawer (típicamente `<DrawerItem>`s). */
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Variante del drawer:
|
|
29
|
+
* - `permanent` (default): siempre visible en desktop, soporta collapsed (mini).
|
|
30
|
+
* - `persistent`: se oculta/muestra, empuja el contenido principal.
|
|
31
|
+
* - `temporary`: flotante con backdrop (típico móvil).
|
|
32
|
+
*/
|
|
33
|
+
variant?: DrawerVariant;
|
|
34
|
+
/** Lado del viewport. Default: `'left'`. */
|
|
35
|
+
anchor?: DrawerAnchor;
|
|
36
|
+
/**
|
|
37
|
+
* Estado mini (solo iconos). Solo se aplica con `variant="permanent"` o
|
|
38
|
+
* `"persistent"`. Si no se provee, el drawer está siempre expandido.
|
|
39
|
+
*/
|
|
40
|
+
collapsed?: boolean;
|
|
41
|
+
/** Callback del botón de toggle (chevron). */
|
|
42
|
+
onToggleCollapse?: () => void;
|
|
43
|
+
/** Estado abierto/cerrado (aplica a `temporary` y `persistent`). */
|
|
44
|
+
open?: boolean;
|
|
45
|
+
/** Callback de cierre (backdrop o ESC en temporary). */
|
|
46
|
+
onClose?: MuiDrawerProps['onClose'];
|
|
47
|
+
/** Ancho en estado expandido. Default: 260. */
|
|
48
|
+
expandedWidth?: number;
|
|
49
|
+
/** Ancho en estado colapsado (solo iconos). Default: 72. */
|
|
50
|
+
collapsedWidth?: number;
|
|
51
|
+
/** Muestra el botón chevron para toggle collapsed. Default: true si `onToggleCollapse` está definido. */
|
|
52
|
+
showCollapseButton?: boolean;
|
|
53
|
+
/** Contenido del header (por encima de los items). Típicamente logo/brand. */
|
|
54
|
+
header?: ReactNode;
|
|
55
|
+
/** Contenido del footer (por debajo de los items). Típicamente user profile. */
|
|
56
|
+
footer?: ReactNode;
|
|
57
|
+
/**
|
|
58
|
+
* Nombre del preset de estilo registrado en `theme.styles.Drawer`.
|
|
59
|
+
* - `"default"` (o ausente) = estilo built-in del paquete.
|
|
60
|
+
*/
|
|
61
|
+
preset?: string;
|
|
62
|
+
/** sx aplicado al Drawer (root). Se mergea después del preset. */
|
|
63
|
+
sx?: SxProps<Theme>;
|
|
64
|
+
/** sx aplicado al Paper interno. */
|
|
65
|
+
paperSx?: SxProps<Theme>;
|
|
66
|
+
className?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function Drawer({
|
|
70
|
+
children,
|
|
71
|
+
variant = 'permanent',
|
|
72
|
+
anchor = 'left',
|
|
73
|
+
collapsed = false,
|
|
74
|
+
onToggleCollapse,
|
|
75
|
+
open,
|
|
76
|
+
onClose,
|
|
77
|
+
expandedWidth = 260,
|
|
78
|
+
collapsedWidth = 72,
|
|
79
|
+
showCollapseButton,
|
|
80
|
+
header,
|
|
81
|
+
footer,
|
|
82
|
+
preset,
|
|
83
|
+
sx,
|
|
84
|
+
paperSx,
|
|
85
|
+
className,
|
|
86
|
+
}: DrawerProps) {
|
|
87
|
+
const theme = useTheme();
|
|
88
|
+
const presetSx = resolvePreset('Drawer', preset, theme);
|
|
89
|
+
|
|
90
|
+
// El mini-variant solo tiene sentido en permanent / persistent.
|
|
91
|
+
const supportsCollapsed = variant !== 'temporary';
|
|
92
|
+
const effectiveWidth =
|
|
93
|
+
supportsCollapsed && collapsed ? collapsedWidth : expandedWidth;
|
|
94
|
+
|
|
95
|
+
const contextValue = useMemo(
|
|
96
|
+
() => ({
|
|
97
|
+
collapsed: supportsCollapsed && collapsed,
|
|
98
|
+
width: effectiveWidth,
|
|
99
|
+
}),
|
|
100
|
+
[supportsCollapsed, collapsed, effectiveWidth],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const rootSx: SxProps<Theme> = [
|
|
104
|
+
buildDrawerSx({ width: effectiveWidth }),
|
|
105
|
+
...(presetSx ? [presetSx] : []),
|
|
106
|
+
...(Array.isArray(sx) ? sx : sx ? [sx] : []),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const shouldShowToggle =
|
|
110
|
+
showCollapseButton ?? (supportsCollapsed && !!onToggleCollapse);
|
|
111
|
+
|
|
112
|
+
const openProp =
|
|
113
|
+
variant === 'permanent'
|
|
114
|
+
? true
|
|
115
|
+
: open !== undefined
|
|
116
|
+
? open
|
|
117
|
+
: false;
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<DrawerContext.Provider value={contextValue}>
|
|
121
|
+
<MuiDrawer
|
|
122
|
+
variant={variant}
|
|
123
|
+
anchor={anchor}
|
|
124
|
+
open={openProp}
|
|
125
|
+
onClose={onClose}
|
|
126
|
+
className={className}
|
|
127
|
+
sx={rootSx}
|
|
128
|
+
PaperProps={{
|
|
129
|
+
sx: paperSx,
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{header && (
|
|
133
|
+
<Box
|
|
134
|
+
sx={{
|
|
135
|
+
display: 'flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: contextValue.collapsed
|
|
138
|
+
? 'center'
|
|
139
|
+
: 'space-between',
|
|
140
|
+
px: contextValue.collapsed ? 1 : 2,
|
|
141
|
+
py: 1.5,
|
|
142
|
+
minHeight: 64,
|
|
143
|
+
flexShrink: 0,
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{!contextValue.collapsed && (
|
|
147
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>{header}</Box>
|
|
148
|
+
)}
|
|
149
|
+
{shouldShowToggle && (
|
|
150
|
+
<IconButton
|
|
151
|
+
size="small"
|
|
152
|
+
onClick={onToggleCollapse}
|
|
153
|
+
aria-label={collapsed ? 'Expandir menú' : 'Colapsar menú'}
|
|
154
|
+
>
|
|
155
|
+
{collapsed ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
|
156
|
+
</IconButton>
|
|
157
|
+
)}
|
|
158
|
+
</Box>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{!header && shouldShowToggle && (
|
|
162
|
+
<Box
|
|
163
|
+
sx={{
|
|
164
|
+
display: 'flex',
|
|
165
|
+
justifyContent: contextValue.collapsed
|
|
166
|
+
? 'center'
|
|
167
|
+
: 'flex-end',
|
|
168
|
+
px: 1,
|
|
169
|
+
py: 1,
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<IconButton
|
|
173
|
+
size="small"
|
|
174
|
+
onClick={onToggleCollapse}
|
|
175
|
+
aria-label={collapsed ? 'Expandir menú' : 'Colapsar menú'}
|
|
176
|
+
>
|
|
177
|
+
{collapsed ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
|
178
|
+
</IconButton>
|
|
179
|
+
</Box>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
<Box
|
|
183
|
+
component="nav"
|
|
184
|
+
sx={{
|
|
185
|
+
flex: 1,
|
|
186
|
+
overflowY: 'auto',
|
|
187
|
+
overflowX: 'hidden',
|
|
188
|
+
py: 1,
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
{children}
|
|
192
|
+
</Box>
|
|
193
|
+
|
|
194
|
+
{footer && (
|
|
195
|
+
<Box
|
|
196
|
+
sx={{
|
|
197
|
+
flexShrink: 0,
|
|
198
|
+
borderTop: (t) => `1px solid ${t.palette.divider}`,
|
|
199
|
+
p: contextValue.collapsed ? 1 : 1.5,
|
|
200
|
+
display: 'flex',
|
|
201
|
+
justifyContent: contextValue.collapsed
|
|
202
|
+
? 'center'
|
|
203
|
+
: 'flex-start',
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
{footer}
|
|
207
|
+
</Box>
|
|
208
|
+
)}
|
|
209
|
+
</MuiDrawer>
|
|
210
|
+
</DrawerContext.Provider>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default Drawer;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context interno del Drawer. Permite que los sub-componentes (DrawerItem,
|
|
5
|
+
* DrawerSection, etc.) reaccionen al estado `collapsed` sin necesidad de
|
|
6
|
+
* recibirlo por props explícitas.
|
|
7
|
+
*/
|
|
8
|
+
export interface DrawerContextValue {
|
|
9
|
+
/** Si el drawer está en modo mini (solo iconos). */
|
|
10
|
+
collapsed: boolean;
|
|
11
|
+
/** Ancho actual del drawer (útil para sub-componentes que necesiten layout). */
|
|
12
|
+
width: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DrawerContext = createContext<DrawerContextValue | null>(null);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook para leer el estado del drawer desde cualquier sub-componente. Si se
|
|
19
|
+
* llama fuera de un `<Drawer>`, devuelve valores por defecto (collapsed=false)
|
|
20
|
+
* para que los items renderizados sueltos no exploten.
|
|
21
|
+
*/
|
|
22
|
+
export function useDrawerContext(): DrawerContextValue {
|
|
23
|
+
const ctx = useContext(DrawerContext);
|
|
24
|
+
if (ctx) return ctx;
|
|
25
|
+
return { collapsed: false, width: 260 };
|
|
26
|
+
}
|