@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,602 @@
|
|
|
1
|
+
export const SimpleSelectDefinition = `
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
4
|
+
import { Box, Typography } from '@mui/material';
|
|
5
|
+
|
|
6
|
+
const basicOptions = [
|
|
7
|
+
{ value: '10', label: '10' },
|
|
8
|
+
{ value: '25', label: '25' },
|
|
9
|
+
{ value: '50', label: '50' },
|
|
10
|
+
{ value: '100', label: '100' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const SimpleSelectExample = () => {
|
|
14
|
+
const [value, setValue] = useState<string>('25');
|
|
15
|
+
return (
|
|
16
|
+
<Box sx={{ width: 200 }}>
|
|
17
|
+
<Select
|
|
18
|
+
label="Registros por página"
|
|
19
|
+
options={basicOptions}
|
|
20
|
+
value={value}
|
|
21
|
+
onChange={(val) => setValue(val as string)}
|
|
22
|
+
maxWidth={150}
|
|
23
|
+
/>
|
|
24
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
25
|
+
</Box>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const MultiSelectDefinition = `
|
|
31
|
+
import React, { useState } from 'react';
|
|
32
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
33
|
+
import { Box, Typography } from '@mui/material';
|
|
34
|
+
|
|
35
|
+
export const MultiSelectExample = () => {
|
|
36
|
+
const [value, setValue] = useState<string[]>([]);
|
|
37
|
+
return (
|
|
38
|
+
<Box sx={{ width: 400 }}>
|
|
39
|
+
<Select
|
|
40
|
+
label="Seleccionar estados"
|
|
41
|
+
multiple
|
|
42
|
+
maxChipsToShow={2}
|
|
43
|
+
options={[
|
|
44
|
+
{ value: 'pending', label: 'Pendiente' },
|
|
45
|
+
{ value: 'approved', label: 'Aprobado' },
|
|
46
|
+
{ value: 'rejected', label: 'Rechazado' },
|
|
47
|
+
{ value: 'canceled', label: 'Cancelado' },
|
|
48
|
+
{ value: 'completed', label: 'Completado' },
|
|
49
|
+
{ value: 'invoiced', label: 'Facturado' },
|
|
50
|
+
]}
|
|
51
|
+
value={value}
|
|
52
|
+
onChange={(val) => setValue(val as string[])}
|
|
53
|
+
/>
|
|
54
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
export const WithPlaceholderDefinition = `
|
|
61
|
+
import React, { useState } from 'react';
|
|
62
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
63
|
+
import { Box, Typography } from '@mui/material';
|
|
64
|
+
|
|
65
|
+
const basicOptions = [
|
|
66
|
+
{ value: '10', label: '10' },
|
|
67
|
+
{ value: '25', label: '25' },
|
|
68
|
+
{ value: '50', label: '50' },
|
|
69
|
+
{ value: '100', label: '100' },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export const WithPlaceholderExample = () => {
|
|
73
|
+
const [value, setValue] = useState('');
|
|
74
|
+
return (
|
|
75
|
+
<Box sx={{ width: 300 }}>
|
|
76
|
+
<Select
|
|
77
|
+
label="Seleccione una opción"
|
|
78
|
+
options={basicOptions}
|
|
79
|
+
value={value}
|
|
80
|
+
onChange={(val) => setValue(val as string)}
|
|
81
|
+
placeholder="Ninguna opción seleccionada"
|
|
82
|
+
/>
|
|
83
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
84
|
+
</Box>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
export const WithFilterDefinition = `
|
|
90
|
+
import React, { useState } from 'react';
|
|
91
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
92
|
+
import { Box, Typography } from '@mui/material';
|
|
93
|
+
|
|
94
|
+
const groupedOptions = [
|
|
95
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
96
|
+
{ value: 'sv', label: 'El Salvador', group: 'Centroamérica' },
|
|
97
|
+
{ value: 'mx', label: 'México', group: 'Norteamérica' },
|
|
98
|
+
{ value: 'us', label: 'EE.UU.', group: 'Norteamérica' },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
export const WithFilterExample = () => {
|
|
102
|
+
const [value, setValue] = useState('');
|
|
103
|
+
return (
|
|
104
|
+
<Box sx={{ width: 300 }}>
|
|
105
|
+
<Select
|
|
106
|
+
label="Buscar país"
|
|
107
|
+
filterable
|
|
108
|
+
options={groupedOptions}
|
|
109
|
+
value={value}
|
|
110
|
+
onChange={(val) => setValue(val as string)}
|
|
111
|
+
/>
|
|
112
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
113
|
+
</Box>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
export const WithGroupDefinition = `
|
|
119
|
+
import React, { useState } from 'react';
|
|
120
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
121
|
+
import { Box, Typography } from '@mui/material';
|
|
122
|
+
|
|
123
|
+
const groupedOptions = [
|
|
124
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
125
|
+
{ value: 'sv', label: 'El Salvador', group: 'Centroamérica' },
|
|
126
|
+
{ value: 'mx', label: 'México', group: 'Norteamérica' },
|
|
127
|
+
{ value: 'us', label: 'EE.UU.', group: 'Norteamérica' },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
export const WithGroupExample = () => {
|
|
131
|
+
const [value, setValue] = useState('');
|
|
132
|
+
return (
|
|
133
|
+
<Box sx={{ width: 300 }}>
|
|
134
|
+
<Select
|
|
135
|
+
label="País por región"
|
|
136
|
+
options={groupedOptions}
|
|
137
|
+
value={value}
|
|
138
|
+
onChange={(val) => setValue(val as string)}
|
|
139
|
+
/>
|
|
140
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
141
|
+
</Box>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
export const CustomRenderWithAvatarDefinition = `
|
|
147
|
+
import React, { useState } from 'react';
|
|
148
|
+
import { Select, Option } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
149
|
+
import { Avatar, Box, Typography } from '@mui/material';
|
|
150
|
+
|
|
151
|
+
const userOptions = [
|
|
152
|
+
{ value: 'admin', label: 'Administrador', img: 'https://placehold.co/40x40?text=A' },
|
|
153
|
+
{ value: 'user', label: 'Usuario', img: 'https://placehold.co/40x40?text=U' },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
export const CustomRenderWithAvatarExample = () => {
|
|
157
|
+
const [value, setValue] = useState<string[]>([]);
|
|
158
|
+
return (
|
|
159
|
+
<Box sx={{ width: 300 }}>
|
|
160
|
+
<Select
|
|
161
|
+
options={userOptions}
|
|
162
|
+
multiple
|
|
163
|
+
value={value}
|
|
164
|
+
onChange={(val) => setValue(val as string[])}
|
|
165
|
+
label="Usuarios"
|
|
166
|
+
placeholder="Selecciona usuarios"
|
|
167
|
+
>
|
|
168
|
+
<Option>
|
|
169
|
+
{({ img, label }) => (
|
|
170
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
171
|
+
<Avatar src={img} sx={{ width: 24, height: 24 }} />
|
|
172
|
+
<Typography variant="body2">{label}</Typography>
|
|
173
|
+
</Box>
|
|
174
|
+
)}
|
|
175
|
+
</Option>
|
|
176
|
+
</Select>
|
|
177
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
178
|
+
</Box>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
export const MultiSelectCustomChipRenderDefinition = `
|
|
184
|
+
import React, { useState } from 'react';
|
|
185
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
186
|
+
import { Box, Typography, Chip } from '@mui/material';
|
|
187
|
+
|
|
188
|
+
const transactionStatuses = [
|
|
189
|
+
{ value: 'PENDING', label: 'Pendiente' },
|
|
190
|
+
{ value: 'REJECTED', label: 'Rechazado' },
|
|
191
|
+
{ value: 'CANCELED', label: 'Cancelado' },
|
|
192
|
+
{ value: 'REFUNDED', label: 'Reembolsado' },
|
|
193
|
+
{ value: 'COMPLETED', label: 'Completado' },
|
|
194
|
+
{ value: 'EXPIRED', label: 'Expirado' },
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
export const MultiSelectCustomChipRenderExample = () => {
|
|
198
|
+
const [value, setValue] = useState<string[]>(['pending', 'approved', 'rejected', 'canceled']);
|
|
199
|
+
return (
|
|
200
|
+
<Box sx={{ width: 400 }}>
|
|
201
|
+
<Select
|
|
202
|
+
label="Estados de Transacción"
|
|
203
|
+
multiple
|
|
204
|
+
maxChipsToShow={3}
|
|
205
|
+
options={transactionStatuses}
|
|
206
|
+
value={value}
|
|
207
|
+
onChange={(val) => setValue(val as string[])}
|
|
208
|
+
placeholder="Selecciona estados"
|
|
209
|
+
renderChipLabel={(item) => (
|
|
210
|
+
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
|
|
211
|
+
{item.label.charAt(0).toUpperCase()}
|
|
212
|
+
</Typography>
|
|
213
|
+
)}
|
|
214
|
+
/>
|
|
215
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
216
|
+
</Box>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
export const MultiSelectCustomChipRenderFullLabelDefinition = `
|
|
222
|
+
import React, { useState } from 'react';
|
|
223
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
224
|
+
import { Avatar, Box, Typography } from '@mui/material';
|
|
225
|
+
|
|
226
|
+
const groupedOptions = [
|
|
227
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
228
|
+
{ value: 'sv', label: 'El Salvador', group: 'Centroamérica' },
|
|
229
|
+
{ value: 'mx', label: 'México', group: 'Norteamérica' },
|
|
230
|
+
{ value: 'us', label: 'EE.UU.', group: 'Norteamérica' },
|
|
231
|
+
{ value: 'ca', label: 'Canadá', group: 'Norteamérica' },
|
|
232
|
+
{ value: 'br', label: 'Brasil', group: 'Sudamérica' },
|
|
233
|
+
{ value: 'ar', label: 'Argentina', group: 'Sudamérica' },
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
export const MultiSelectCustomChipRenderFullLabelExample = () => {
|
|
237
|
+
const [value, setValue] = useState<string[]>(['gt', 'mx', 'us', 'ca', 'br']);
|
|
238
|
+
return (
|
|
239
|
+
<Box sx={{ width: 400 }}>
|
|
240
|
+
<Select
|
|
241
|
+
label="Países seleccionados"
|
|
242
|
+
multiple
|
|
243
|
+
maxChipsToShow={3}
|
|
244
|
+
options={groupedOptions}
|
|
245
|
+
value={value}
|
|
246
|
+
onChange={(val) => setValue(val as string[])}
|
|
247
|
+
placeholder="Selecciona países"
|
|
248
|
+
renderChipLabel={(item) => (
|
|
249
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
250
|
+
{item.img && <Avatar src={item.img} sx={{ width: 18, height: 18 }} />}
|
|
251
|
+
<Typography variant="caption" sx={{ fontWeight: 'medium' }}>
|
|
252
|
+
{item.label}
|
|
253
|
+
</Typography>
|
|
254
|
+
</Box>
|
|
255
|
+
)}
|
|
256
|
+
/>
|
|
257
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
258
|
+
</Box>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
export const ConstrainedHeightDefinition = `
|
|
264
|
+
import React, { useState } from 'react';
|
|
265
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
266
|
+
import { Box, Typography } from '@mui/material';
|
|
267
|
+
|
|
268
|
+
const optionsWithManyItems = [
|
|
269
|
+
{ value: 'gt', label: 'Guatemala' }, { value: 'sv', label: 'El Salvador' },
|
|
270
|
+
{ value: 'mx', label: 'México' }, { value: 'us', label: 'EE.UU.' },
|
|
271
|
+
{ value: 'ca', label: 'Canadá' }, { value: 'br', label: 'Brasil' },
|
|
272
|
+
{ value: 'ar', label: 'Argentina' }, { value: 'cl', label: 'Chile' },
|
|
273
|
+
{ value: 'co', label: 'Colombia' }, { value: 'pe', label: 'Perú' },
|
|
274
|
+
{ value: 'ec', label: 'Ecuador' }, { value: 've', label: 'Venezuela' },
|
|
275
|
+
{ value: 'py', label: 'Paraguay' }, { value: 'uy', label: 'Uruguay' },
|
|
276
|
+
{ value: 'bo', label: 'Bolivia' }, { value: 'cr', label: 'Costa Rica' },
|
|
277
|
+
{ value: 'pa', label: 'Panamá' }, { value: 'hn', label: 'Honduras' },
|
|
278
|
+
{ value: 'ni', label: 'Nicaragua' }, { value: 'do', label: 'Rep. Dominicana' },
|
|
279
|
+
{ value: 'cu', label: 'Cuba' }, { value: 'pr', label: 'Puerto Rico' },
|
|
280
|
+
{ value: 'es', label: 'España' }, { value: 'fr', label: 'Francia' },
|
|
281
|
+
{ value: 'de', label: 'Alemania' }, { value: 'it', label: 'Italia' },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
export const ConstrainedHeightExample = () => {
|
|
285
|
+
const [value, setValue] = useState('');
|
|
286
|
+
return (
|
|
287
|
+
<Box sx={{ width: 300 }}>
|
|
288
|
+
<Select
|
|
289
|
+
label="Opciones (Altura Limitada)"
|
|
290
|
+
options={optionsWithManyItems}
|
|
291
|
+
value={value}
|
|
292
|
+
onChange={(val) => setValue(val as string)}
|
|
293
|
+
maxHeight={150}
|
|
294
|
+
/>
|
|
295
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
296
|
+
</Box>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
`;
|
|
300
|
+
|
|
301
|
+
export const ConstrainedWidthDefinition = `
|
|
302
|
+
import React, { useState } from 'react';
|
|
303
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
304
|
+
import { Box, Typography } from '@mui/material';
|
|
305
|
+
|
|
306
|
+
const basicOptions = [
|
|
307
|
+
{ value: '10', label: '10' },
|
|
308
|
+
{ value: '25', label: '25' },
|
|
309
|
+
];
|
|
310
|
+
const groupedOptions = [
|
|
311
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
312
|
+
{ value: 'sv', label: 'El Salvador', group: 'Centroamérica' },
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
export const ConstrainedWidthExample = () => {
|
|
316
|
+
const [value, setValue] = useState('');
|
|
317
|
+
return (
|
|
318
|
+
<Box sx={{ width: 200 }}>
|
|
319
|
+
<Select
|
|
320
|
+
label="Opciones (Ancho Limitado)"
|
|
321
|
+
options={basicOptions.concat(groupedOptions)}
|
|
322
|
+
value={value}
|
|
323
|
+
onChange={(val) => setValue(val as string)}
|
|
324
|
+
maxWidth={100}
|
|
325
|
+
/>
|
|
326
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
327
|
+
</Box>
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
`;
|
|
331
|
+
|
|
332
|
+
export const AllFeaturesCombinedDefinition = `
|
|
333
|
+
import React, { useState } from 'react';
|
|
334
|
+
import { Select, Option } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
335
|
+
import { Avatar, Box, Typography, Chip } from '@mui/material';
|
|
336
|
+
|
|
337
|
+
const allOptions = [
|
|
338
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
339
|
+
{ value: 'admin', label: 'Administrador', img: 'https://placehold.co/40x40?text=A' },
|
|
340
|
+
{ value: 'PENDING', label: 'Pendiente' },
|
|
341
|
+
{ value: 'ca', label: 'Canadá', group: 'Norteamérica' },
|
|
342
|
+
{ value: 'user', label: 'Usuario', img: 'https://placehold.co/40x40?text=U' },
|
|
343
|
+
{ value: 'COMPLETED', label: 'Completado' },
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
export const AllFeaturesCombinedExample = () => {
|
|
347
|
+
const [value, setValue] = useState<string[]>(['gt', 'admin', 'PENDING', 'ca', 'user', 'COMPLETED']);
|
|
348
|
+
return (
|
|
349
|
+
<Box sx={{ width: 500 }}>
|
|
350
|
+
<Select
|
|
351
|
+
label="Selección Completa"
|
|
352
|
+
multiple
|
|
353
|
+
filterable
|
|
354
|
+
maxChipsToShow={3}
|
|
355
|
+
maxHeight={250}
|
|
356
|
+
maxWidth="400px"
|
|
357
|
+
options={allOptions}
|
|
358
|
+
value={value}
|
|
359
|
+
onChange={(val) => setValue(val as string[])}
|
|
360
|
+
placeholder="Busca y selecciona todo tipo de elementos"
|
|
361
|
+
renderChipLabel={(item) => (
|
|
362
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
363
|
+
{item.img && <Avatar src={item.img} sx={{ width: 16, height: 16 }} />}
|
|
364
|
+
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
|
|
365
|
+
{item.label.length > 10 ? item.label.substring(0, 7) + '...' : item.label}
|
|
366
|
+
</Typography>
|
|
367
|
+
</Box>
|
|
368
|
+
)}
|
|
369
|
+
>
|
|
370
|
+
<Option>
|
|
371
|
+
{(item) => (
|
|
372
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, my: 0.5 }}>
|
|
373
|
+
{item.img && <Avatar src={item.img} sx={{ width: 28, height: 28 }} />}
|
|
374
|
+
<Box>
|
|
375
|
+
<Typography variant="body2" fontWeight="medium">{item.label}</Typography>
|
|
376
|
+
{item.group && (
|
|
377
|
+
<Typography variant="caption" color="text.secondary">
|
|
378
|
+
Grupo: {item.group}
|
|
379
|
+
</Typography>
|
|
380
|
+
)}
|
|
381
|
+
{item.disabled && <Chip label="No disponible" size="small" color="warning" sx={{ ml: 1 }} />}
|
|
382
|
+
</Box>
|
|
383
|
+
</Box>
|
|
384
|
+
)}
|
|
385
|
+
</Option>
|
|
386
|
+
</Select>
|
|
387
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
388
|
+
</Box>
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
`;
|
|
392
|
+
|
|
393
|
+
export const EmptyOptionsDefinition = `
|
|
394
|
+
import React, { useState } from 'react';
|
|
395
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
396
|
+
import { Box, Typography } from '@mui/material';
|
|
397
|
+
|
|
398
|
+
export const EmptyOptionsExample = () => {
|
|
399
|
+
const [value, setValue] = useState(null);
|
|
400
|
+
return (
|
|
401
|
+
<Box sx={{ width: 300 }}>
|
|
402
|
+
<Select
|
|
403
|
+
label="Seleccionar (Vacío)"
|
|
404
|
+
options={[]}
|
|
405
|
+
value={value}
|
|
406
|
+
onChange={setValue}
|
|
407
|
+
placeholder="No hay opciones disponibles"
|
|
408
|
+
/>
|
|
409
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
410
|
+
</Box>
|
|
411
|
+
);
|
|
412
|
+
};
|
|
413
|
+
`;
|
|
414
|
+
|
|
415
|
+
export const SelectWithManyOptionsDefinition = `
|
|
416
|
+
import React, { useState } from 'react';
|
|
417
|
+
import { Select } from './Select'; // Asumiendo que Select.tsx está en el mismo directorio
|
|
418
|
+
import { Box, Typography } from '@mui/material';
|
|
419
|
+
|
|
420
|
+
const manyOptions = Array.from({ length: 50 }, (_, i) => ({
|
|
421
|
+
value: \`option-\${i}\`,
|
|
422
|
+
label: \`Opción \${i + 1}\`,
|
|
423
|
+
group: i < 25 ? 'Grupo A' : 'Grupo B',
|
|
424
|
+
}));
|
|
425
|
+
|
|
426
|
+
export const SelectWithManyOptionsExample = () => {
|
|
427
|
+
const [value, setValue] = useState('');
|
|
428
|
+
return (
|
|
429
|
+
<Box sx={{ width: 300 }}>
|
|
430
|
+
<Select
|
|
431
|
+
label="Muchas Opciones"
|
|
432
|
+
options={manyOptions}
|
|
433
|
+
value={value}
|
|
434
|
+
onChange={(val) => setValue(val as string)}
|
|
435
|
+
filterable
|
|
436
|
+
maxHeight={200}
|
|
437
|
+
/>
|
|
438
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
439
|
+
</Box>
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
export const LabelPositionFloatingDefinition = `
|
|
445
|
+
import React, { useState } from 'react';
|
|
446
|
+
import { Select } from './Select';
|
|
447
|
+
import { Box, Typography } from '@mui/material';
|
|
448
|
+
|
|
449
|
+
const basicOptions = [
|
|
450
|
+
{ value: '10', label: '10' },
|
|
451
|
+
{ value: '25', label: '25' },
|
|
452
|
+
{ value: '50', label: '50' },
|
|
453
|
+
{ value: '100', label: '100' },
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
export const LabelPositionFloatingExample = () => {
|
|
457
|
+
const [value, setValue] = useState('');
|
|
458
|
+
return (
|
|
459
|
+
<Box sx={{ width: 300 }}>
|
|
460
|
+
<Select
|
|
461
|
+
label="Label flotante (MUI nativo)"
|
|
462
|
+
options={basicOptions}
|
|
463
|
+
value={value}
|
|
464
|
+
onChange={(val) => setValue(val as string)}
|
|
465
|
+
labelPosition="floating"
|
|
466
|
+
/>
|
|
467
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
468
|
+
</Box>
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
export const CustomBorderRadiusDefinition = `
|
|
474
|
+
import React, { useState } from 'react';
|
|
475
|
+
import { Select } from './Select';
|
|
476
|
+
import { Box } from '@mui/material';
|
|
477
|
+
|
|
478
|
+
const basicOptions = [
|
|
479
|
+
{ value: '10', label: '10' },
|
|
480
|
+
{ value: '25', label: '25' },
|
|
481
|
+
{ value: '50', label: '50' },
|
|
482
|
+
{ value: '100', label: '100' },
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
export const CustomBorderRadiusExample = () => {
|
|
486
|
+
const [valueA, setValueA] = useState('25');
|
|
487
|
+
const [valueB, setValueB] = useState('25');
|
|
488
|
+
const [valueC, setValueC] = useState('25');
|
|
489
|
+
return (
|
|
490
|
+
<Box sx={{ width: 300, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
491
|
+
<Select
|
|
492
|
+
label="Sin border radius"
|
|
493
|
+
options={basicOptions}
|
|
494
|
+
value={valueA}
|
|
495
|
+
onChange={(val) => setValueA(val as string)}
|
|
496
|
+
borderRadius={0}
|
|
497
|
+
/>
|
|
498
|
+
<Select
|
|
499
|
+
label="Border radius estándar"
|
|
500
|
+
options={basicOptions}
|
|
501
|
+
value={valueB}
|
|
502
|
+
onChange={(val) => setValueB(val as string)}
|
|
503
|
+
borderRadius={10}
|
|
504
|
+
/>
|
|
505
|
+
<Select
|
|
506
|
+
label="Pill-shaped"
|
|
507
|
+
options={basicOptions}
|
|
508
|
+
value={valueC}
|
|
509
|
+
onChange={(val) => setValueC(val as string)}
|
|
510
|
+
borderRadius={999}
|
|
511
|
+
/>
|
|
512
|
+
</Box>
|
|
513
|
+
);
|
|
514
|
+
};
|
|
515
|
+
`;
|
|
516
|
+
|
|
517
|
+
export const CustomStylingDefinition = `
|
|
518
|
+
import React, { useState } from 'react';
|
|
519
|
+
import { Select } from './Select';
|
|
520
|
+
import { Box, Typography } from '@mui/material';
|
|
521
|
+
|
|
522
|
+
const userOptions = [
|
|
523
|
+
{ value: 'admin', label: 'Administrador', img: 'https://placehold.co/40x40?text=A' },
|
|
524
|
+
{ value: 'user', label: 'Usuario', img: 'https://placehold.co/40x40?text=U' },
|
|
525
|
+
{ value: 'moderator', label: 'Moderador', img: 'https://placehold.co/40x40?text=M' },
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
export const CustomStylingExample = () => {
|
|
529
|
+
const [value, setValue] = useState<string[]>(['admin']);
|
|
530
|
+
return (
|
|
531
|
+
<Box sx={{ width: 360 }}>
|
|
532
|
+
<Select
|
|
533
|
+
label="Select con estilos personalizados"
|
|
534
|
+
options={userOptions}
|
|
535
|
+
multiple
|
|
536
|
+
value={value}
|
|
537
|
+
onChange={(val) => setValue(val as string[])}
|
|
538
|
+
placeholder="Selecciona usuarios"
|
|
539
|
+
sx={{
|
|
540
|
+
'& .MuiInputBase-root': {
|
|
541
|
+
backgroundColor: 'action.hover',
|
|
542
|
+
},
|
|
543
|
+
'& .MuiOutlinedInput-notchedOutline': {
|
|
544
|
+
borderColor: 'primary.main',
|
|
545
|
+
borderWidth: '2px',
|
|
546
|
+
},
|
|
547
|
+
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|
548
|
+
borderColor: 'primary.dark',
|
|
549
|
+
},
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
553
|
+
</Box>
|
|
554
|
+
);
|
|
555
|
+
};
|
|
556
|
+
`;
|
|
557
|
+
|
|
558
|
+
export const AsyncSelectDefinition = `
|
|
559
|
+
import React, { useState } from 'react';
|
|
560
|
+
import { Select } from './Select';
|
|
561
|
+
import { Box, Typography } from '@mui/material';
|
|
562
|
+
|
|
563
|
+
const mockAsyncOptions = [
|
|
564
|
+
{ value: 'apple', label: 'Apple' },
|
|
565
|
+
{ value: 'banana', label: 'Banana' },
|
|
566
|
+
{ value: 'orange', label: 'Orange' },
|
|
567
|
+
{ value: 'grape', label: 'Grape' },
|
|
568
|
+
{ value: 'strawberry', label: 'Strawberry' },
|
|
569
|
+
{ value: 'blueberry', label: 'Blueberry' },
|
|
570
|
+
{ value: 'pineapple', label: 'Pineapple' },
|
|
571
|
+
];
|
|
572
|
+
|
|
573
|
+
// Simulate an API call
|
|
574
|
+
const simulatedLoadOptions = (inputValue: string): Promise<any[]> => {
|
|
575
|
+
return new Promise((resolve) => {
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
const filtered = mockAsyncOptions.filter(option =>
|
|
578
|
+
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
|
579
|
+
);
|
|
580
|
+
resolve(filtered);
|
|
581
|
+
}, 800); // Simulate network delay
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
export const AsyncSelectExample = () => {
|
|
586
|
+
const [value, setValue] = useState('');
|
|
587
|
+
return (
|
|
588
|
+
<Box sx={{ width: 300 }}>
|
|
589
|
+
<Select
|
|
590
|
+
label="Buscar Frutas"
|
|
591
|
+
loadOptions={simulatedLoadOptions}
|
|
592
|
+
value={value}
|
|
593
|
+
onChange={(val) => setValue(val as string)}
|
|
594
|
+
placeholder="Escribe para buscar..."
|
|
595
|
+
loadingMessage="Buscando frutas..."
|
|
596
|
+
noOptionsMessage="No se encontraron frutas."
|
|
597
|
+
/>
|
|
598
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
599
|
+
</Box>
|
|
600
|
+
);
|
|
601
|
+
};
|
|
602
|
+
`;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { SelectOption } from './Select';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filtra opciones por `label` (case-insensitive). Si `search` es vacío,
|
|
5
|
+
* devuelve el array completo. Solo se usa en modo no-async.
|
|
6
|
+
*/
|
|
7
|
+
export const filterOptionsByLabel = (
|
|
8
|
+
options: SelectOption[],
|
|
9
|
+
search: string,
|
|
10
|
+
): SelectOption[] => {
|
|
11
|
+
if (!search) return options;
|
|
12
|
+
const needle = search.toLowerCase();
|
|
13
|
+
return options.filter((opt) => opt.label.toLowerCase().includes(needle));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Agrupa opciones por la propiedad `group`. Las opciones sin group caen
|
|
18
|
+
* bajo la key especial `__default`. Devuelve un Record para iterar en orden
|
|
19
|
+
* de inserción con Object.entries.
|
|
20
|
+
*/
|
|
21
|
+
export const groupOptions = (
|
|
22
|
+
options: SelectOption[],
|
|
23
|
+
): Record<string, SelectOption[]> => {
|
|
24
|
+
const groups: Record<string, SelectOption[]> = {};
|
|
25
|
+
options.forEach((opt) => {
|
|
26
|
+
const group = opt.group || '__default';
|
|
27
|
+
if (!groups[group]) groups[group] = [];
|
|
28
|
+
groups[group].push(opt);
|
|
29
|
+
});
|
|
30
|
+
return groups;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Determina si un conjunto de grupos está efectivamente vacío
|
|
35
|
+
* (no hay grupos o solo existe `__default` sin items).
|
|
36
|
+
*/
|
|
37
|
+
export const isGroupedOptionsEmpty = (
|
|
38
|
+
groups: Record<string, SelectOption[]>,
|
|
39
|
+
): boolean => {
|
|
40
|
+
const keys = Object.keys(groups);
|
|
41
|
+
if (keys.length === 0) return true;
|
|
42
|
+
if (keys.length === 1 && groups['__default'] && groups['__default'].length === 0) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normaliza el valor actual al shape que espera el MUI Select:
|
|
50
|
+
* - multiple: siempre array (aunque esté vacío).
|
|
51
|
+
* - single: string vacío si null/undefined.
|
|
52
|
+
*/
|
|
53
|
+
export const normalizeSelectValue = <T>(
|
|
54
|
+
value: T | T[] | null | undefined,
|
|
55
|
+
multiple: boolean,
|
|
56
|
+
): T | T[] | '' => {
|
|
57
|
+
if (multiple) return (value ?? []) as T[];
|
|
58
|
+
return (value ?? '') as T | '';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Evalúa si el valor actual está vacío (para decidir shrink del label
|
|
63
|
+
* y visibilidad del placeholder).
|
|
64
|
+
*/
|
|
65
|
+
export const isSelectValueEmpty = (
|
|
66
|
+
normalizedValue: unknown,
|
|
67
|
+
multiple: boolean,
|
|
68
|
+
): boolean => {
|
|
69
|
+
if (multiple) return Array.isArray(normalizedValue) && normalizedValue.length === 0;
|
|
70
|
+
return normalizedValue === '' || normalizedValue === null || normalizedValue === undefined;
|
|
71
|
+
};
|