recursive-dropdown-mui 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/README.md +264 -0
- package/dist/dropdown/DropdownWithSubmenu.d.ts +8 -0
- package/dist/dropdown/core/context/DropdownContext.d.ts +71 -0
- package/dist/dropdown/core/hooks/index.d.ts +7 -0
- package/dist/dropdown/core/hooks/useClickOutside.d.ts +13 -0
- package/dist/dropdown/core/hooks/useDropdown.d.ts +37 -0
- package/dist/dropdown/core/hooks/useDropdownState.d.ts +14 -0
- package/dist/dropdown/core/hooks/useHover.d.ts +23 -0
- package/dist/dropdown/core/hooks/useItemNavigation.d.ts +27 -0
- package/dist/dropdown/core/hooks/useKeyboardNavigation.d.ts +21 -0
- package/dist/dropdown/core/hooks/useScroll.d.ts +9 -0
- package/dist/dropdown/core/types.d.ts +170 -0
- package/dist/dropdown/data/menuData.d.ts +2 -0
- package/dist/dropdown/ui/DropdownContent.d.ts +6 -0
- package/dist/dropdown/ui/DropdownTrigger.d.ts +21 -0
- package/dist/dropdown/ui/MenuItemComponent.d.ts +16 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +10117 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +10141 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Recursive Dropdown MUI
|
|
2
|
+
|
|
3
|
+
Menú desplegable recursivo con soporte para múltiples niveles,
|
|
4
|
+
navegación, scroll suave y total personalización. Construido sobre
|
|
5
|
+
**Material UI** y **React Router**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Instalación
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install recursive-dropdown-mui
|
|
13
|
+
# o
|
|
14
|
+
yarn add recursive-dropdown-mui
|
|
15
|
+
# o
|
|
16
|
+
pnpm add recursive-dropdown-mui
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Dependencias requeridas
|
|
20
|
+
|
|
21
|
+
Este paquete espera que tu proyecto ya tenga instalados:
|
|
22
|
+
|
|
23
|
+
- @mui/material
|
|
24
|
+
- @emotion/react
|
|
25
|
+
- @emotion/styled
|
|
26
|
+
- react-router-dom
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🚀 Uso básico
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { DropdownWithSubmenu } from "recursive-dropdown-mui";
|
|
34
|
+
import { menuItems } from "./menuData"; // tu array de MenuItem
|
|
35
|
+
|
|
36
|
+
function App() {
|
|
37
|
+
return <DropdownWithSubmenu menuItems={menuItems} triggerText="Explorar" />;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 📘 Estructura de datos (MenuItem)
|
|
44
|
+
|
|
45
|
+
Cada ítem del menú sigue la interfaz:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
interface MenuItem {
|
|
49
|
+
label: string; // Texto visible
|
|
50
|
+
href?: string; // Ruta de navegación (React Router)
|
|
51
|
+
hash?: string; // Ancla para scroll interno (sin #)
|
|
52
|
+
icon: React.ReactNode; // Icono (elemento JSX)
|
|
53
|
+
children?: MenuItem[]; // Subítems anidados
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- ✅ Puedes combinar `href` y `hash`.
|
|
58
|
+
- ✅ Si solo usas `hash`, se tomará el `href` del ancestro más cercano
|
|
59
|
+
como base.
|
|
60
|
+
- ✅ Ítems sin `href/hash` y sin hijos → no hacen nada al hacer clic.
|
|
61
|
+
- ✅ Ítems sin `href/hash` pero con hijos → expanden/colapsan el
|
|
62
|
+
submenú.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🧩 Props principales
|
|
67
|
+
|
|
68
|
+
### 1. Datos y contenido
|
|
69
|
+
|
|
70
|
+
| Prop | Tipo | Default | Descripción |
|
|
71
|
+
| ----------- | ------------------------- | ---------------- | ------------------------------------- |
|
|
72
|
+
| menuItems | MenuItem[] | defaultMenuItems | Array de ítems del menú |
|
|
73
|
+
| loading | boolean | false | Muestra estado de carga |
|
|
74
|
+
| loadingText | string | "Cargando..." | Texto del spinner |
|
|
75
|
+
| emptyState | React.ReactNode | undefined | Componente cuando no hay ítems |
|
|
76
|
+
| triggerText | string \| React.ReactNode | "EXPLORAR" | Texto o elemento del botón disparador |
|
|
77
|
+
| triggerIcon | React.ReactNode | undefined | Icono que acompaña al texto del botón |
|
|
78
|
+
|
|
79
|
+
### 2. Comportamiento
|
|
80
|
+
|
|
81
|
+
| Prop | Tipo | Default | Descripción |
|
|
82
|
+
| ------------------- | -------------------- | ------- | --------------------------------- |
|
|
83
|
+
| onItemClick | (item, path) => void | - | Callback al hacer clic en un ítem |
|
|
84
|
+
| onHoverItem | (item, path) => void | - | Callback al pasar el mouse |
|
|
85
|
+
| onOpen | () => void | - | Al abrir el menú |
|
|
86
|
+
| onClose | () => void | - | Al cerrar el menú |
|
|
87
|
+
| maxDepth | number | 5 | Profundidad máxima |
|
|
88
|
+
| openOnHover | boolean | true | Abrir con hover |
|
|
89
|
+
| closeOnClickOutside | boolean | true | Cerrar al hacer clic fuera |
|
|
90
|
+
| closeDelay | number (ms) | 300 | Retraso para cerrar |
|
|
91
|
+
|
|
92
|
+
### 3. Estilos y diseño
|
|
93
|
+
|
|
94
|
+
| Prop | Tipo | Default | Descripción |
|
|
95
|
+
| ------------ | ---------------------------------- | -------- | ---------------------- |
|
|
96
|
+
| sx | SxProps<Theme> | - | Estilos del contenedor |
|
|
97
|
+
| paperSx | SxProps<Theme> | - | Estilos del Paper |
|
|
98
|
+
| itemSx | SxProps<Theme> | - | Estilos del ítem |
|
|
99
|
+
| submenuSx | SxProps<Theme> | - | Estilos del submenú |
|
|
100
|
+
| density | compact \| standard \| comfortable | standard | Densidad vertical |
|
|
101
|
+
| showDividers | boolean | false | Mostrar separadores |
|
|
102
|
+
| dividerColor | string | divider | Color del divisor |
|
|
103
|
+
|
|
104
|
+
### 4. Iconos
|
|
105
|
+
|
|
106
|
+
| Prop | Tipo | Default | Descripción |
|
|
107
|
+
| ------------ | -------------------- | --------------- | ------------------ |
|
|
108
|
+
| expandIcon | React.ReactNode | ChevronRight | Submenú cerrado |
|
|
109
|
+
| collapseIcon | React.ReactNode | ArrowDropUpIcon | Submenú abierto |
|
|
110
|
+
| submenuIcon | React.ReactNode | expandIcon | Icono del submenú |
|
|
111
|
+
| iconPosition | start \| end \| none | end | Posición del icono |
|
|
112
|
+
|
|
113
|
+
### 5. Comportamiento avanzado
|
|
114
|
+
|
|
115
|
+
| Prop | Tipo | Default | Descripción |
|
|
116
|
+
| --------------- | -------------- | ------- | ------------------- |
|
|
117
|
+
| open | boolean | - | Control externo |
|
|
118
|
+
| defaultOpen | boolean | false | Estado inicial |
|
|
119
|
+
| initialDepth | number | 0 | Profundidad inicial |
|
|
120
|
+
| autoExpandDepth | number | 0 | Auto expansión |
|
|
121
|
+
| scrollIntoView | boolean | true | Scroll por hash |
|
|
122
|
+
| scrollOffset | number | 80 | Offset |
|
|
123
|
+
| scrollBehavior | auto \| smooth | smooth | Tipo de scroll |
|
|
124
|
+
| disabled | boolean | false | Deshabilitado |
|
|
125
|
+
| readOnly | boolean | false | Solo lectura |
|
|
126
|
+
|
|
127
|
+
### 6. Accesibilidad y SEO
|
|
128
|
+
|
|
129
|
+
| Prop | Tipo | Default | Descripción |
|
|
130
|
+
| ------------------ | ------- | ---------------- | ------------------ |
|
|
131
|
+
| ariaLabel | string | Menú desplegable | aria-label |
|
|
132
|
+
| ariaLabelledBy | string | - | Etiqueta |
|
|
133
|
+
| ariaDescribedBy | string | - | Descripción |
|
|
134
|
+
| role | string | menu | Rol ARIA |
|
|
135
|
+
| keyboardNavigation | boolean | true | Navegación teclado |
|
|
136
|
+
| focusOnOpen | boolean | true | Foco inicial |
|
|
137
|
+
| useAnchorTags | boolean | true | Usa `<a>` |
|
|
138
|
+
| rel | string | - | Atributo rel |
|
|
139
|
+
|
|
140
|
+
### 7. Internacionalización
|
|
141
|
+
|
|
142
|
+
| Prop | Tipo | Default | Descripción |
|
|
143
|
+
| ------------ | -------------------- | ------- | ----------- |
|
|
144
|
+
| translations | DropdownTranslations | - | Textos |
|
|
145
|
+
| direction | ltr \| rtl | ltr | Dirección |
|
|
146
|
+
|
|
147
|
+
### 8. Renderizado personalizado
|
|
148
|
+
|
|
149
|
+
| Prop | Tipo | Descripción |
|
|
150
|
+
| ------------- | -------------------------- | --------------------- |
|
|
151
|
+
| renderTrigger | (props) => React.ReactNode | Trigger personalizado |
|
|
152
|
+
| renderItem | (props) => React.ReactNode | Ítem personalizado |
|
|
153
|
+
| renderSubmenu | (props) => React.ReactNode | Submenú personalizado |
|
|
154
|
+
|
|
155
|
+
### 9. Temas y variantes
|
|
156
|
+
|
|
157
|
+
| Prop | Tipo | Default | Descripción |
|
|
158
|
+
| ----------- | ---------------------------------------------------------------- | ------- | ------------------ |
|
|
159
|
+
| variant | default \| minimal \| elevated \| borderless \| dark \| gradient | default | Variante visual |
|
|
160
|
+
| customTheme | CustomTheme | - | Tema personalizado |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 🎨 Ejemplos avanzados
|
|
167
|
+
|
|
168
|
+
### Menú con apertura por click
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<DropdownWithSubmenu
|
|
172
|
+
menuItems={menuItems}
|
|
173
|
+
openOnHover={false}
|
|
174
|
+
closeOnClickOutside={true}
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Densidad compacta
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<DropdownWithSubmenu
|
|
182
|
+
menuItems={menuItems}
|
|
183
|
+
density="compact"
|
|
184
|
+
iconPosition="none"
|
|
185
|
+
/>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Tema oscuro
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
<DropdownWithSubmenu
|
|
192
|
+
menuItems={menuItems}
|
|
193
|
+
variant="dark"
|
|
194
|
+
customTheme={{
|
|
195
|
+
colors: { background: "#1e1e2f", text: "#ffffff" },
|
|
196
|
+
borderRadius: 8,
|
|
197
|
+
spacing: 1,
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Trigger personalizado
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<DropdownWithSubmenu
|
|
206
|
+
menuItems={menuItems}
|
|
207
|
+
renderTrigger={({ isOpen, toggle, ref }) => (
|
|
208
|
+
<button ref={ref} onClick={toggle} className="mi-boton">
|
|
209
|
+
{isOpen ? "Cerrar" : "Abrir"} menú
|
|
210
|
+
</button>
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Enlaces reales para SEO
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<DropdownWithSubmenu
|
|
219
|
+
menuItems={menuItems}
|
|
220
|
+
useAnchorTags={true}
|
|
221
|
+
rel="noopener noreferrer"
|
|
222
|
+
/>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## ♿ Accesibilidad
|
|
228
|
+
|
|
229
|
+
- Rol `menu` / `menuitem`
|
|
230
|
+
- Navegación por teclado
|
|
231
|
+
- Atributos ARIA
|
|
232
|
+
- Lectores de pantalla
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 📐 TypeScript
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import type {
|
|
240
|
+
MenuItem,
|
|
241
|
+
DropdownVariant,
|
|
242
|
+
DropdownDensity,
|
|
243
|
+
IconPosition,
|
|
244
|
+
ScrollBehavior,
|
|
245
|
+
CustomTheme,
|
|
246
|
+
DropdownTranslations,
|
|
247
|
+
DropdownWithSubmenuProps,
|
|
248
|
+
} from "recursive-dropdown-mui";
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 🛠️ Desarrollo local
|
|
254
|
+
|
|
255
|
+
1. Clona el repositorio\
|
|
256
|
+
2. Instala dependencias: `pnpm install`\
|
|
257
|
+
3. Construye: `pnpm build`\
|
|
258
|
+
4. Dev: `pnpm dev`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 📄 Licencia
|
|
263
|
+
|
|
264
|
+
MIT © Blackysensei
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { DropdownWithSubmenuProps } from "./core/types";
|
|
3
|
+
/**
|
|
4
|
+
* Componente de alto nivel que integra DropdownMenu, DropdownTrigger y DropdownContent.
|
|
5
|
+
* Expone una API simplificada para el usuario final.
|
|
6
|
+
*/
|
|
7
|
+
declare const DropdownWithSubmenu: React.FC<DropdownWithSubmenuProps>;
|
|
8
|
+
export default DropdownWithSubmenu;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { type SxProps, type Theme } from "@mui/material";
|
|
3
|
+
import type { MenuItem, DropdownContextType, DropdownVariant, DropdownDensity, IconPosition, ScrollBehavior, CustomTheme, DropdownTranslations } from "../types";
|
|
4
|
+
export declare const useDropdownContext: () => DropdownContextType;
|
|
5
|
+
interface DropdownMenuProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
menuItems: MenuItem[];
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
loadingText?: string;
|
|
10
|
+
emptyState?: React.ReactNode;
|
|
11
|
+
onItemClick?: (item: MenuItem, path: number[]) => void;
|
|
12
|
+
onHoverItem?: (item: MenuItem, path: number[]) => void;
|
|
13
|
+
onOpen?: () => void;
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
maxDepth?: number;
|
|
16
|
+
openOnHover?: boolean;
|
|
17
|
+
closeOnClickOutside?: boolean;
|
|
18
|
+
closeDelay?: number;
|
|
19
|
+
sx?: SxProps<Theme>;
|
|
20
|
+
paperSx?: SxProps<Theme>;
|
|
21
|
+
itemSx?: SxProps<Theme>;
|
|
22
|
+
submenuSx?: SxProps<Theme>;
|
|
23
|
+
density?: DropdownDensity;
|
|
24
|
+
showDividers?: boolean;
|
|
25
|
+
dividerColor?: string;
|
|
26
|
+
expandIcon?: React.ReactNode;
|
|
27
|
+
collapseIcon?: React.ReactNode;
|
|
28
|
+
submenuIcon?: React.ReactNode;
|
|
29
|
+
iconPosition?: IconPosition;
|
|
30
|
+
open?: boolean;
|
|
31
|
+
defaultOpen?: boolean;
|
|
32
|
+
initialDepth?: number;
|
|
33
|
+
autoExpandDepth?: number;
|
|
34
|
+
scrollIntoView?: boolean;
|
|
35
|
+
scrollOffset?: number;
|
|
36
|
+
scrollBehavior?: ScrollBehavior;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
readOnly?: boolean;
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
ariaLabelledBy?: string;
|
|
41
|
+
ariaDescribedBy?: string;
|
|
42
|
+
role?: string;
|
|
43
|
+
keyboardNavigation?: boolean;
|
|
44
|
+
focusOnOpen?: boolean;
|
|
45
|
+
useAnchorTags?: boolean;
|
|
46
|
+
rel?: string;
|
|
47
|
+
translations?: DropdownTranslations;
|
|
48
|
+
direction?: "ltr" | "rtl";
|
|
49
|
+
renderItem?: (props: {
|
|
50
|
+
item: MenuItem;
|
|
51
|
+
path: number[];
|
|
52
|
+
depth: number;
|
|
53
|
+
isActive: boolean;
|
|
54
|
+
hasChildren: boolean;
|
|
55
|
+
onClick: () => void;
|
|
56
|
+
}) => React.ReactNode;
|
|
57
|
+
renderSubmenu?: (props: {
|
|
58
|
+
items: MenuItem[];
|
|
59
|
+
depth: number;
|
|
60
|
+
parentPath: number[];
|
|
61
|
+
}) => React.ReactNode;
|
|
62
|
+
variant?: DropdownVariant;
|
|
63
|
+
customTheme?: CustomTheme;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Proveedor del contexto de dropdown.
|
|
67
|
+
* Utiliza el hook `useDropdown` para obtener toda la lógica de estado e interacción.
|
|
68
|
+
* Las props se pasan directamente a `useDropdown` y también se usan para valores estáticos.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DropdownMenu: React.FC<DropdownMenuProps>;
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { useDropdownState } from "./useDropdownState";
|
|
2
|
+
export { useHover } from "./useHover";
|
|
3
|
+
export { useClickOutside } from "./useClickOutside";
|
|
4
|
+
export { useScroll } from "./useScroll";
|
|
5
|
+
export { useKeyboardNavigation } from "./useKeyboardNavigation";
|
|
6
|
+
export { useItemNavigation } from "./useItemNavigation";
|
|
7
|
+
export { useDropdown } from "./useDropdown";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
interface UseClickOutsideProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
closeOnClickOutside?: boolean;
|
|
5
|
+
setIsOpen: (open: boolean) => void;
|
|
6
|
+
rootRef: RefObject<HTMLElement | null>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Hook que escucha clics fuera del componente raíz y cierra el menú.
|
|
10
|
+
* Respetar la prop `closeOnClickOutside` (true por defecto).
|
|
11
|
+
*/
|
|
12
|
+
export declare const useClickOutside: ({ isOpen, closeOnClickOutside, setIsOpen, rootRef, }: UseClickOutsideProps) => void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MenuItem } from "../types";
|
|
2
|
+
interface UseDropdownProps {
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
closeDelay?: number;
|
|
5
|
+
openOnHover?: boolean;
|
|
6
|
+
closeOnClickOutside?: boolean;
|
|
7
|
+
onOpen?: () => void;
|
|
8
|
+
onClose?: () => void;
|
|
9
|
+
menuItems: MenuItem[];
|
|
10
|
+
onItemClick?: (item: MenuItem, path: number[]) => void;
|
|
11
|
+
onHoverItem?: (item: MenuItem, path: number[]) => void;
|
|
12
|
+
scrollOffset?: number;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Hook principal que orquesta todos los submódulos de comportamiento del dropdown.
|
|
18
|
+
* Expone el estado y los manejadores necesarios para el contexto.
|
|
19
|
+
*/
|
|
20
|
+
export declare const useDropdown: (props: UseDropdownProps) => {
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
activePath: number[] | null;
|
|
23
|
+
setIsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
24
|
+
setActivePath: import("react").Dispatch<import("react").SetStateAction<number[] | null>>;
|
|
25
|
+
handleMouseEnter: () => void;
|
|
26
|
+
handleMouseLeave: () => void;
|
|
27
|
+
handleItemMouseEnter: (path: number[]) => void;
|
|
28
|
+
handleItemMouseLeave: () => void;
|
|
29
|
+
handleItemClick: (item: MenuItem, itemPath: number[]) => void;
|
|
30
|
+
getItemByPath: (path: number[]) => MenuItem | null;
|
|
31
|
+
getBaseHrefForPath: (path: number[]) => string;
|
|
32
|
+
buildItemUrl: (item: MenuItem, baseHref?: string) => string;
|
|
33
|
+
getItemDepth: (path: number[]) => number;
|
|
34
|
+
itemHasChildren: (item: MenuItem) => boolean;
|
|
35
|
+
timeoutRef: import("react").RefObject<number | null>;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook base para gestionar el estado local del dropdown.
|
|
3
|
+
*
|
|
4
|
+
* - `isOpen`: indica si el menú principal está visible.
|
|
5
|
+
* - `setIsOpen`: función para cambiar el estado de apertura.
|
|
6
|
+
* - `activePath`: array de índices que representa la ruta del submenú actualmente activo (hover o click).
|
|
7
|
+
* - `setActivePath`: función para actualizar la ruta activa.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useDropdownState: (defaultOpen?: boolean) => {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
setIsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
12
|
+
activePath: number[] | null;
|
|
13
|
+
setActivePath: import("react").Dispatch<import("react").SetStateAction<number[] | null>>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface UseHoverProps {
|
|
2
|
+
openOnHover?: boolean;
|
|
3
|
+
closeDelay?: number;
|
|
4
|
+
onOpen?: () => void;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
setIsOpen: (open: boolean) => void;
|
|
7
|
+
setActivePath: (path: number[] | null) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Hook que maneja toda la interacción por hover:
|
|
11
|
+
* - Apertura/cierre del menú principal con delay configurable.
|
|
12
|
+
* - Activación de submenús al pasar el ratón sobre items.
|
|
13
|
+
* - Limpieza de timeouts para evitar cierres abruptos.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useHover: ({ openOnHover, closeDelay, onOpen, onClose, setIsOpen, setActivePath, }: UseHoverProps) => {
|
|
16
|
+
handleRootMouseEnter: () => void;
|
|
17
|
+
handleRootMouseLeave: () => void;
|
|
18
|
+
handleItemMouseEnter: (path: number[]) => void;
|
|
19
|
+
handleItemMouseLeave: () => void;
|
|
20
|
+
timeoutRef: import("react").RefObject<number | null>;
|
|
21
|
+
intervalRef: import("react").RefObject<number | null>;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MenuItem } from "../types";
|
|
2
|
+
interface UseItemNavigationProps {
|
|
3
|
+
menuItems: MenuItem[];
|
|
4
|
+
onItemClick?: (item: MenuItem, path: number[]) => void;
|
|
5
|
+
onHoverItem?: (item: MenuItem, path: number[]) => void;
|
|
6
|
+
setIsOpen: (open: boolean) => void;
|
|
7
|
+
setActivePath: (path: number[] | null) => void;
|
|
8
|
+
activePath: number[] | null;
|
|
9
|
+
navigate: (url: string) => void;
|
|
10
|
+
locationPathname: string;
|
|
11
|
+
scrollToElement: (id: string, offset: number) => void;
|
|
12
|
+
scrollOffset: number;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
readOnly: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Hook que centraliza la lógica de navegación y expansión al hacer clic en un item.
|
|
18
|
+
* También construye URLs, obtiene items por path y maneja el hover.
|
|
19
|
+
*/
|
|
20
|
+
export declare const useItemNavigation: ({ menuItems, onItemClick, onHoverItem, setIsOpen, setActivePath, activePath, navigate, locationPathname, scrollToElement, scrollOffset, disabled, readOnly, }: UseItemNavigationProps) => {
|
|
21
|
+
getItemByPath: (path: number[]) => MenuItem | null;
|
|
22
|
+
getBaseHrefForPath: (path: number[]) => string;
|
|
23
|
+
buildItemUrl: (item: MenuItem, baseHref?: string) => string;
|
|
24
|
+
handleItemClick: (item: MenuItem, itemPath: number[]) => void;
|
|
25
|
+
handleHoverItem: (item: MenuItem, path: number[]) => void;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { MenuItem } from "../types";
|
|
3
|
+
interface UseKeyboardNavigationProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
activePath: number[] | null;
|
|
6
|
+
menuItems: MenuItem[];
|
|
7
|
+
onItemSelect: (item: MenuItem, path: number[]) => void;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
rootRef: RefObject<HTMLElement | null>;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hook que implementa la navegación por teclado:
|
|
14
|
+
* - Flechas arriba/abajo: movimiento vertical entre items del mismo nivel.
|
|
15
|
+
* - Flecha derecha: abre submenú del item activo.
|
|
16
|
+
* - Flecha izquierda: cierra submenú actual.
|
|
17
|
+
* - Enter: selecciona el item activo.
|
|
18
|
+
* - Escape: cierra todo el menú.
|
|
19
|
+
*/
|
|
20
|
+
export declare const useKeyboardNavigation: ({ isOpen, activePath, menuItems, onItemSelect, onClose, rootRef, enabled, }: UseKeyboardNavigationProps) => void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook que proporciona funciones para realizar scroll suave:
|
|
3
|
+
* - `scrollToElement`: desplaza la ventana hasta un elemento con ID específico.
|
|
4
|
+
* - `scrollToTop`: vuelve al inicio de la página con un offset opcional.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useScroll: () => {
|
|
7
|
+
scrollToElement: (elementId: string, offset?: number) => void;
|
|
8
|
+
scrollToTop: (offset?: number) => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import type { SxProps, Theme } from "@mui/material";
|
|
3
|
+
export interface MenuItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
hash?: string;
|
|
7
|
+
icon: ReactNode;
|
|
8
|
+
children?: MenuItem[];
|
|
9
|
+
}
|
|
10
|
+
export interface DropdownContextType {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
activePath: number[] | null;
|
|
13
|
+
setIsOpen: (open: boolean) => void;
|
|
14
|
+
setActivePath: (path: number[] | null) => void;
|
|
15
|
+
handleMouseEnter: () => void;
|
|
16
|
+
handleMouseLeave: () => void;
|
|
17
|
+
handleItemMouseEnter: (path: number[]) => void;
|
|
18
|
+
handleItemMouseLeave: () => void;
|
|
19
|
+
handleItemClick: (item: MenuItem, itemPath: number[]) => void;
|
|
20
|
+
getItemDepth: (path: number[]) => number;
|
|
21
|
+
buildItemUrl: (item: MenuItem, baseHref?: string) => string;
|
|
22
|
+
itemHasChildren: (item: MenuItem) => boolean;
|
|
23
|
+
menuItems: MenuItem[];
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
loadingText?: string;
|
|
26
|
+
emptyState?: React.ReactNode;
|
|
27
|
+
onItemClick?: (item: MenuItem, path: number[]) => void;
|
|
28
|
+
onHoverItem?: (item: MenuItem, path: number[]) => void;
|
|
29
|
+
maxDepth: number;
|
|
30
|
+
getItemByPath: (path: number[]) => MenuItem | null;
|
|
31
|
+
getBaseHrefForPath: (path: number[]) => string;
|
|
32
|
+
paperSx?: any;
|
|
33
|
+
itemSx?: any;
|
|
34
|
+
submenuSx?: any;
|
|
35
|
+
density: DropdownDensity;
|
|
36
|
+
showDividers: boolean;
|
|
37
|
+
dividerColor: string;
|
|
38
|
+
getDensityStyles: () => any;
|
|
39
|
+
expandIcon: React.ReactNode;
|
|
40
|
+
collapseIcon: React.ReactNode;
|
|
41
|
+
submenuIcon: React.ReactNode;
|
|
42
|
+
iconPosition: IconPosition;
|
|
43
|
+
openOnHover: boolean;
|
|
44
|
+
closeDelay: number;
|
|
45
|
+
scrollIntoView: boolean;
|
|
46
|
+
scrollOffset: number;
|
|
47
|
+
scrollBehavior: ScrollBehavior;
|
|
48
|
+
disabled: boolean;
|
|
49
|
+
readOnly: boolean;
|
|
50
|
+
initialDepth: number;
|
|
51
|
+
autoExpandDepth: number;
|
|
52
|
+
ariaLabel: string;
|
|
53
|
+
ariaLabelledBy?: string;
|
|
54
|
+
ariaDescribedBy?: string;
|
|
55
|
+
role: string;
|
|
56
|
+
keyboardNavigation: boolean;
|
|
57
|
+
focusOnOpen: boolean;
|
|
58
|
+
useAnchorTags: boolean;
|
|
59
|
+
rel?: string;
|
|
60
|
+
translations?: DropdownTranslations;
|
|
61
|
+
direction: "ltr" | "rtl";
|
|
62
|
+
renderItem?: (props: {
|
|
63
|
+
item: MenuItem;
|
|
64
|
+
path: number[];
|
|
65
|
+
depth: number;
|
|
66
|
+
isActive: boolean;
|
|
67
|
+
hasChildren: boolean;
|
|
68
|
+
onClick: () => void;
|
|
69
|
+
}) => React.ReactNode;
|
|
70
|
+
renderSubmenu?: (props: {
|
|
71
|
+
items: MenuItem[];
|
|
72
|
+
depth: number;
|
|
73
|
+
parentPath: number[];
|
|
74
|
+
}) => React.ReactNode;
|
|
75
|
+
variant: DropdownVariant;
|
|
76
|
+
customTheme?: CustomTheme;
|
|
77
|
+
LoadingState: React.FC;
|
|
78
|
+
EmptyState: React.FC;
|
|
79
|
+
}
|
|
80
|
+
export type DropdownVariant = "default" | "minimal" | "elevated" | "borderless" | "dark" | "gradient";
|
|
81
|
+
export type DropdownDensity = "compact" | "standard" | "comfortable";
|
|
82
|
+
export type IconPosition = "start" | "end" | "none";
|
|
83
|
+
export type ScrollBehavior = "auto" | "smooth";
|
|
84
|
+
export interface CustomTheme {
|
|
85
|
+
text?: string;
|
|
86
|
+
colors?: {
|
|
87
|
+
primary?: string;
|
|
88
|
+
secondary?: string;
|
|
89
|
+
background?: string;
|
|
90
|
+
text?: string;
|
|
91
|
+
};
|
|
92
|
+
spacing?: number;
|
|
93
|
+
borderRadius?: number;
|
|
94
|
+
}
|
|
95
|
+
export interface DropdownTranslations {
|
|
96
|
+
expand?: string;
|
|
97
|
+
collapse?: string;
|
|
98
|
+
loading?: string;
|
|
99
|
+
empty?: string;
|
|
100
|
+
}
|
|
101
|
+
export interface DropdownWithSubmenuProps {
|
|
102
|
+
menuItems?: MenuItem[];
|
|
103
|
+
triggerText?: string | React.ReactNode;
|
|
104
|
+
triggerIcon?: React.ReactNode;
|
|
105
|
+
emptyState?: React.ReactNode;
|
|
106
|
+
loading?: boolean;
|
|
107
|
+
loadingText?: string;
|
|
108
|
+
onItemClick?: (item: MenuItem, path: number[]) => void;
|
|
109
|
+
maxDepth?: number;
|
|
110
|
+
openOnHover?: boolean;
|
|
111
|
+
closeOnClickOutside?: boolean;
|
|
112
|
+
closeDelay?: number;
|
|
113
|
+
onOpen?: () => void;
|
|
114
|
+
onClose?: () => void;
|
|
115
|
+
onHoverItem?: (item: MenuItem, path: number[]) => void;
|
|
116
|
+
sx?: SxProps<Theme>;
|
|
117
|
+
triggerSx?: SxProps<Theme>;
|
|
118
|
+
paperSx?: SxProps<Theme>;
|
|
119
|
+
itemSx?: SxProps<Theme>;
|
|
120
|
+
submenuSx?: SxProps<Theme>;
|
|
121
|
+
buttonVariant?: "text" | "outlined" | "contained";
|
|
122
|
+
buttonColor?: "primary" | "secondary" | "inherit" | "success" | "error" | "info" | "warning";
|
|
123
|
+
buttonSize?: "small" | "medium" | "large";
|
|
124
|
+
density?: DropdownDensity;
|
|
125
|
+
expandIcon?: React.ReactNode;
|
|
126
|
+
collapseIcon?: React.ReactNode;
|
|
127
|
+
submenuIcon?: React.ReactNode;
|
|
128
|
+
iconPosition?: IconPosition;
|
|
129
|
+
showDividers?: boolean;
|
|
130
|
+
dividerColor?: string;
|
|
131
|
+
open?: boolean;
|
|
132
|
+
defaultOpen?: boolean;
|
|
133
|
+
initialDepth?: number;
|
|
134
|
+
autoExpandDepth?: number;
|
|
135
|
+
scrollIntoView?: boolean;
|
|
136
|
+
scrollOffset?: number;
|
|
137
|
+
scrollBehavior?: ScrollBehavior;
|
|
138
|
+
disabled?: boolean;
|
|
139
|
+
readOnly?: boolean;
|
|
140
|
+
ariaLabel?: string;
|
|
141
|
+
ariaLabelledBy?: string;
|
|
142
|
+
ariaDescribedBy?: string;
|
|
143
|
+
role?: string;
|
|
144
|
+
keyboardNavigation?: boolean;
|
|
145
|
+
focusOnOpen?: boolean;
|
|
146
|
+
useAnchorTags?: boolean;
|
|
147
|
+
rel?: string;
|
|
148
|
+
translations?: DropdownTranslations;
|
|
149
|
+
direction?: "ltr" | "rtl";
|
|
150
|
+
renderTrigger?: (props: {
|
|
151
|
+
isOpen: boolean;
|
|
152
|
+
toggle: () => void;
|
|
153
|
+
ref: React.Ref<HTMLElement>;
|
|
154
|
+
}) => React.ReactNode;
|
|
155
|
+
renderItem?: (props: {
|
|
156
|
+
item: MenuItem;
|
|
157
|
+
path: number[];
|
|
158
|
+
depth: number;
|
|
159
|
+
isActive: boolean;
|
|
160
|
+
hasChildren: boolean;
|
|
161
|
+
onClick: () => void;
|
|
162
|
+
}) => React.ReactNode;
|
|
163
|
+
renderSubmenu?: (props: {
|
|
164
|
+
items: MenuItem[];
|
|
165
|
+
depth: number;
|
|
166
|
+
parentPath: number[];
|
|
167
|
+
}) => React.ReactNode;
|
|
168
|
+
variant?: DropdownVariant;
|
|
169
|
+
customTheme?: CustomTheme;
|
|
170
|
+
}
|