@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,687 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Chip,
|
|
5
|
+
Avatar,
|
|
6
|
+
Box,
|
|
7
|
+
Typography,
|
|
8
|
+
} from '@mui/material';
|
|
9
|
+
import ClearIcon from '@mui/icons-material/Clear'; // Icono de cerrar
|
|
10
|
+
|
|
11
|
+
// Importar las definiciones de las historias
|
|
12
|
+
import {
|
|
13
|
+
SimpleSelectDefinition,
|
|
14
|
+
MultiSelectDefinition,
|
|
15
|
+
WithPlaceholderDefinition,
|
|
16
|
+
WithFilterDefinition,
|
|
17
|
+
WithGroupDefinition,
|
|
18
|
+
CustomRenderWithAvatarDefinition,
|
|
19
|
+
MultiSelectCustomChipRenderDefinition,
|
|
20
|
+
MultiSelectCustomChipRenderFullLabelDefinition,
|
|
21
|
+
ConstrainedHeightDefinition,
|
|
22
|
+
ConstrainedWidthDefinition,
|
|
23
|
+
AllFeaturesCombinedDefinition,
|
|
24
|
+
EmptyOptionsDefinition,
|
|
25
|
+
SelectWithManyOptionsDefinition,
|
|
26
|
+
AsyncSelectDefinition,
|
|
27
|
+
LabelPositionFloatingDefinition,
|
|
28
|
+
CustomBorderRadiusDefinition,
|
|
29
|
+
CustomStylingDefinition,
|
|
30
|
+
} from "./Select.definitions";
|
|
31
|
+
import Select, { SelectOption, Option } from './Select';
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Datos de ejemplo para las historias
|
|
35
|
+
// =============================================================================
|
|
36
|
+
const basicOptions: SelectOption[] = [
|
|
37
|
+
{ value: '10', label: '10' },
|
|
38
|
+
{ value: '25', label: '25' },
|
|
39
|
+
{ value: '50', label: '50' },
|
|
40
|
+
{ value: '100', label: '100' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const groupedOptions: SelectOption[] = [
|
|
44
|
+
{ value: 'gt', label: 'Guatemala', group: 'Centroamérica' },
|
|
45
|
+
{ value: 'sv', label: 'El Salvador', group: 'Centroamérica' },
|
|
46
|
+
{ value: 'mx', label: 'México', group: 'Norteamérica' },
|
|
47
|
+
{ value: 'us', label: 'EE.UU.', group: 'Norteamérica' },
|
|
48
|
+
{ value: 'ca', label: 'Canadá', group: 'Norteamérica' },
|
|
49
|
+
{ value: 'br', label: 'Brasil', group: 'Sudamérica' },
|
|
50
|
+
{ value: 'ar', label: 'Argentina', group: 'Sudamérica' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const userOptions: SelectOption[] = [
|
|
54
|
+
{ value: 'admin', label: 'Administrador', img: 'https://placehold.co/40x40?text=A' },
|
|
55
|
+
{ value: 'user', label: 'Usuario', img: 'https://placehold.co/40x40?text=U' },
|
|
56
|
+
{ value: 'moderator', label: 'Moderador', img: 'https://placehold.co/40x40?text=M' },
|
|
57
|
+
{ value: 'guest', label: 'Invitado', img: 'https://placehold.co/40x40?text=G', disabled: true },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const transactionStatuses: SelectOption[] = [
|
|
61
|
+
{ value: 'PENDING', label: 'Pendiente' },
|
|
62
|
+
{ value: 'REJECTED', label: 'Rechazado' },
|
|
63
|
+
{ value: 'CANCELED', label: 'Cancelado' },
|
|
64
|
+
{ value: 'REFUNDED', label: 'Reembolsado' },
|
|
65
|
+
{ value: 'COMPLETED', label: 'Completado' },
|
|
66
|
+
{ value: 'EXPIRED', label: 'Expirado' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Definición de las meta-propiedades para Storybook
|
|
71
|
+
// =============================================================================
|
|
72
|
+
const meta: Meta<typeof Select> = {
|
|
73
|
+
title: 'Components/Select',
|
|
74
|
+
component: Select,
|
|
75
|
+
tags: ['autodocs'],
|
|
76
|
+
parameters: {
|
|
77
|
+
layout: 'padded',
|
|
78
|
+
docs: {
|
|
79
|
+
description: {
|
|
80
|
+
component: 'Componente select personalizado con soporte para múltiples variantes como agrupación, chips, filtros, y renderizado personalizado.',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
argTypes: {
|
|
85
|
+
label: { control: 'text', description: 'Etiqueta para el campo de selección.' },
|
|
86
|
+
options: { control: 'object', description: 'Array de objetos `SelectOption` para las opciones del menú.' },
|
|
87
|
+
value: { control: 'object', description: 'Valor(es) seleccionado(s) del select.' },
|
|
88
|
+
defaultValue: { control: 'object', description: 'Valor(es) por defecto del select.' },
|
|
89
|
+
onChange: { action: 'changed', description: 'Función de callback que se llama cuando el valor del select cambia.' },
|
|
90
|
+
size: { control: 'radio', options: ['small', 'medium'], description: 'Define el tamaño del componente Select.' },
|
|
91
|
+
multiple: { control: 'boolean', description: 'Si es verdadero, permite la selección de múltiples opciones.' },
|
|
92
|
+
filterable: { control: 'boolean', description: 'Si es verdadero, añade un campo de búsqueda para filtrar las opciones.' },
|
|
93
|
+
placeholder: { control: 'text', description: 'Texto que se muestra cuando no hay nada seleccionado.' },
|
|
94
|
+
children: { control: false, description: 'Componente `Option` para un renderizado personalizado de las opciones del menú.' },
|
|
95
|
+
maxHeight: { control: 'number', description: 'Altura máxima del menú desplegable.' },
|
|
96
|
+
maxWidth: { control: 'text', description: 'Ancho máximo del menú desplegable.' },
|
|
97
|
+
maxChipsToShow: { control: 'number', description: 'Número máximo de chips a mostrar en selección múltiple antes de agrupar.', if: { arg: 'multiple', eq: true } },
|
|
98
|
+
renderChipLabel: { control: false, description: 'Función para personalizar el contenido del label de cada chip seleccionado (para múltiple) o del valor mostrado (para individual).', },
|
|
99
|
+
labelPosition: {
|
|
100
|
+
control: 'radio',
|
|
101
|
+
options: ['outside', 'floating'],
|
|
102
|
+
description: 'Posición del label. `outside` mantiene el label por encima del input con animación al enfocar. `floating` usa el comportamiento nativo de MUI (label flotando dentro del notched outline).',
|
|
103
|
+
},
|
|
104
|
+
borderRadius: {
|
|
105
|
+
control: 'number',
|
|
106
|
+
description: 'Radio del borde del select. Acepta un número (en px) o un string CSS.',
|
|
107
|
+
},
|
|
108
|
+
sx: {
|
|
109
|
+
control: false,
|
|
110
|
+
description: 'Prop `sx` que se pasa al MUI Select para overrides personalizados. Se mergea con los estilos por defecto.',
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default meta;
|
|
116
|
+
type Story = StoryObj<typeof Select>;
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Historias existentes (proporcionadas por el usuario)
|
|
120
|
+
// =============================================================================
|
|
121
|
+
export const SimpleSelect: Story = {
|
|
122
|
+
render: () => {
|
|
123
|
+
const [value, setValue] = useState<string>('25');
|
|
124
|
+
return (
|
|
125
|
+
<Box sx={{ width: 200 }}>
|
|
126
|
+
<Select
|
|
127
|
+
label="Registros por página"
|
|
128
|
+
options={basicOptions}
|
|
129
|
+
value={value}
|
|
130
|
+
onChange={(val) => setValue(val as string)}
|
|
131
|
+
maxWidth={150}
|
|
132
|
+
/>
|
|
133
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
134
|
+
</Box>
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
parameters: {
|
|
138
|
+
docs: {
|
|
139
|
+
description: {
|
|
140
|
+
story: "Select simple con opciones básicas y valor por defecto."
|
|
141
|
+
},
|
|
142
|
+
source: { code: SimpleSelectDefinition.trim() } // Referencia a la definición
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const MultiSelect: Story = {
|
|
148
|
+
render: () => {
|
|
149
|
+
const [value, setValue] = useState<string[]>([]);
|
|
150
|
+
return (
|
|
151
|
+
<Box sx={{ width: 400 }}>
|
|
152
|
+
<Select
|
|
153
|
+
label="Seleccionar estados"
|
|
154
|
+
multiple
|
|
155
|
+
maxChipsToShow={2}
|
|
156
|
+
options={[
|
|
157
|
+
{ value: 'pending', label: 'Pendiente' },
|
|
158
|
+
{ value: 'approved', label: 'Aprobado' },
|
|
159
|
+
{ value: 'rejected', label: 'Rechazado' },
|
|
160
|
+
{ value: 'canceled', label: 'Cancelado' },
|
|
161
|
+
{ value: 'completed', label: 'Completado' },
|
|
162
|
+
{ value: 'invoiced', label: 'Facturado' },
|
|
163
|
+
]}
|
|
164
|
+
value={value}
|
|
165
|
+
onChange={(val) => setValue(val as string[])}
|
|
166
|
+
/>
|
|
167
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
168
|
+
</Box>
|
|
169
|
+
);
|
|
170
|
+
},
|
|
171
|
+
parameters: {
|
|
172
|
+
docs: {
|
|
173
|
+
description: {
|
|
174
|
+
story: "Select múltiple que permite seleccionar varias opciones, limitando a 2 chips."
|
|
175
|
+
},
|
|
176
|
+
source: { code: MultiSelectDefinition.trim() } // Referencia a la definición
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const WithPlaceholder: Story = {
|
|
182
|
+
render: () => {
|
|
183
|
+
const [value, setValue] = useState('');
|
|
184
|
+
return (
|
|
185
|
+
<Box sx={{ width: 300 }}>
|
|
186
|
+
<Select
|
|
187
|
+
label="Seleccione una opción"
|
|
188
|
+
options={basicOptions}
|
|
189
|
+
value={value}
|
|
190
|
+
onChange={(val) => setValue(val as string)}
|
|
191
|
+
placeholder="Ninguna opción seleccionada"
|
|
192
|
+
/>
|
|
193
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
194
|
+
</Box>
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
parameters: {
|
|
198
|
+
docs: {
|
|
199
|
+
description: {
|
|
200
|
+
story: "Select con placeholder sin valor inicial seleccionado."
|
|
201
|
+
},
|
|
202
|
+
source: { code: WithPlaceholderDefinition.trim() } // Referencia a la definición
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const WithFilter: Story = {
|
|
208
|
+
render: () => {
|
|
209
|
+
const [value, setValue] = useState('');
|
|
210
|
+
return (
|
|
211
|
+
<Box sx={{ width: 300 }}>
|
|
212
|
+
<Select
|
|
213
|
+
label="Buscar país"
|
|
214
|
+
filterable
|
|
215
|
+
options={groupedOptions}
|
|
216
|
+
value={value}
|
|
217
|
+
onChange={(val) => setValue(val as string)}
|
|
218
|
+
/>
|
|
219
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
220
|
+
</Box>
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
parameters: {
|
|
224
|
+
docs: {
|
|
225
|
+
description: {
|
|
226
|
+
story: "Select con capacidad de filtrado de opciones."
|
|
227
|
+
},
|
|
228
|
+
source: { code: WithFilterDefinition.trim() } // Referencia a la definición
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const WithGroup: Story = {
|
|
234
|
+
render: () => {
|
|
235
|
+
const [value, setValue] = useState('');
|
|
236
|
+
return (
|
|
237
|
+
<Box sx={{ width: 300 }}>
|
|
238
|
+
<Select
|
|
239
|
+
label="País por región"
|
|
240
|
+
options={groupedOptions}
|
|
241
|
+
value={value}
|
|
242
|
+
onChange={(val) => setValue(val as string)}
|
|
243
|
+
/>
|
|
244
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
245
|
+
</Box>
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
parameters: {
|
|
249
|
+
docs: {
|
|
250
|
+
description: {
|
|
251
|
+
story: "Select con opciones agrupadas por categorías."
|
|
252
|
+
},
|
|
253
|
+
source: { code: WithGroupDefinition.trim() } // Referencia a la definición
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const CustomRenderWithAvatar: Story = {
|
|
259
|
+
render: () => {
|
|
260
|
+
const [value, setValue] = useState<string[]>([]);
|
|
261
|
+
return (
|
|
262
|
+
<Box sx={{ width: 300 }}>
|
|
263
|
+
<Select
|
|
264
|
+
options={userOptions}
|
|
265
|
+
multiple
|
|
266
|
+
value={value}
|
|
267
|
+
onChange={(val) => setValue(val as string[])}
|
|
268
|
+
label="Usuarios"
|
|
269
|
+
placeholder="Selecciona usuarios"
|
|
270
|
+
>
|
|
271
|
+
<Option>
|
|
272
|
+
{({ img, label }) => (
|
|
273
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
274
|
+
<Avatar src={img} sx={{ width: 24, height: 24 }} />
|
|
275
|
+
<Typography variant="body2">{label}</Typography>
|
|
276
|
+
</Box>
|
|
277
|
+
)}
|
|
278
|
+
</Option>
|
|
279
|
+
</Select>
|
|
280
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
281
|
+
</Box>
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
parameters: {
|
|
285
|
+
docs: {
|
|
286
|
+
description: {
|
|
287
|
+
story: "Select con renderizado personalizado de opciones usando avatares."
|
|
288
|
+
},
|
|
289
|
+
source: { code: CustomRenderWithAvatarDefinition.trim() } // Referencia a la definición
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
// =============================================================================
|
|
296
|
+
// Nuevas historias (para cubrir más posibilidades)
|
|
297
|
+
// =============================================================================
|
|
298
|
+
|
|
299
|
+
export const MultiSelectCustomChipRender: Story = {
|
|
300
|
+
render: () => {
|
|
301
|
+
const [value, setValue] = useState<string[]>(['pending', 'approved', 'rejected', 'canceled']);
|
|
302
|
+
return (
|
|
303
|
+
<Box sx={{ width: 400 }}>
|
|
304
|
+
<Select
|
|
305
|
+
label="Estados de Transacción"
|
|
306
|
+
multiple
|
|
307
|
+
maxChipsToShow={3} // Muestra 3 chips, luego (+X más)
|
|
308
|
+
options={transactionStatuses}
|
|
309
|
+
value={value}
|
|
310
|
+
onChange={(val) => setValue(val as string[])}
|
|
311
|
+
placeholder="Selecciona estados"
|
|
312
|
+
renderChipLabel={(item) => ( // Renderizado personalizado para el chip
|
|
313
|
+
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
|
|
314
|
+
{item.label.charAt(0).toUpperCase()} {/* Solo la inicial */}
|
|
315
|
+
</Typography>
|
|
316
|
+
)}
|
|
317
|
+
/>
|
|
318
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
319
|
+
</Box>
|
|
320
|
+
);
|
|
321
|
+
},
|
|
322
|
+
parameters: {
|
|
323
|
+
docs: {
|
|
324
|
+
description: {
|
|
325
|
+
story: "Select múltiple con un número limitado de chips visibles y un renderizado personalizado para el label de cada chip (solo la inicial)."
|
|
326
|
+
},
|
|
327
|
+
source: { code: MultiSelectCustomChipRenderDefinition.trim() } // Referencia a la definición
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export const MultiSelectCustomChipRenderFullLabel: Story = {
|
|
333
|
+
render: () => {
|
|
334
|
+
const [value, setValue] = useState<string[]>(['gt', 'mx', 'us', 'ca', 'br']);
|
|
335
|
+
return (
|
|
336
|
+
<Box sx={{ width: 400 }}>
|
|
337
|
+
<Select
|
|
338
|
+
label="Países seleccionados"
|
|
339
|
+
multiple
|
|
340
|
+
maxChipsToShow={3} // Muestra 3 chips, luego (+X más)
|
|
341
|
+
options={groupedOptions}
|
|
342
|
+
value={value}
|
|
343
|
+
onChange={(val) => setValue(val as string[])}
|
|
344
|
+
placeholder="Selecciona países"
|
|
345
|
+
renderChipLabel={(item) => ( // Renderizado personalizado para el chip
|
|
346
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
347
|
+
{item.img && <Avatar src={item.img} sx={{ width: 18, height: 18 }} />} {/* Opcional: avatar en el chip */}
|
|
348
|
+
<Typography variant="caption" sx={{ fontWeight: 'medium' }}>
|
|
349
|
+
{item.label}
|
|
350
|
+
</Typography>
|
|
351
|
+
</Box>
|
|
352
|
+
)}
|
|
353
|
+
/>
|
|
354
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
355
|
+
</Box>
|
|
356
|
+
);
|
|
357
|
+
},
|
|
358
|
+
parameters: {
|
|
359
|
+
docs: {
|
|
360
|
+
story: "Select múltiple con renderizado personalizado completo de los chips, incluyendo avatar y texto completo, y limitando el número de chips visibles."
|
|
361
|
+
},
|
|
362
|
+
source: { code: MultiSelectCustomChipRenderFullLabelDefinition.trim() } // Referencia a la definición
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
export const ConstrainedHeight: Story = {
|
|
368
|
+
render: () => {
|
|
369
|
+
const [value, setValue] = useState('');
|
|
370
|
+
return (
|
|
371
|
+
<Box sx={{ width: 300 }}>
|
|
372
|
+
<Select
|
|
373
|
+
label="Opciones (Altura Limitada)"
|
|
374
|
+
options={groupedOptions.concat(userOptions).concat(transactionStatuses)} // Muchas opciones
|
|
375
|
+
value={value}
|
|
376
|
+
onChange={(val) => setValue(val as string)}
|
|
377
|
+
maxHeight={150} // Altura máxima del menú
|
|
378
|
+
/>
|
|
379
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
380
|
+
</Box>
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
parameters: {
|
|
384
|
+
docs: {
|
|
385
|
+
story: "Select con altura máxima limitada para el menú desplegable, mostrando scroll si es necesario."
|
|
386
|
+
},
|
|
387
|
+
source: { code: ConstrainedHeightDefinition.trim() } // Referencia a la definición
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const ConstrainedWidth: Story = {
|
|
392
|
+
render: () => {
|
|
393
|
+
const [value, setValue] = useState('');
|
|
394
|
+
return (
|
|
395
|
+
<Box sx={{ width: 200 }}> {/* El Select en sí es más angosto */}
|
|
396
|
+
<Select
|
|
397
|
+
label="Opciones (Ancho Limitado)"
|
|
398
|
+
options={basicOptions.concat(groupedOptions)}
|
|
399
|
+
value={value}
|
|
400
|
+
onChange={(val) => setValue(val as string)}
|
|
401
|
+
maxWidth={100} // Ancho máximo del menú
|
|
402
|
+
/>
|
|
403
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
404
|
+
</Box>
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
parameters: {
|
|
408
|
+
docs: {
|
|
409
|
+
story: "Select con ancho máximo limitado para el menú desplegable."
|
|
410
|
+
},
|
|
411
|
+
source: { code: ConstrainedWidthDefinition.trim() } // Referencia a la definición
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export const AllFeaturesCombined: Story = {
|
|
416
|
+
render: () => {
|
|
417
|
+
const [value, setValue] = useState<string[]>(['gt', 'admin', 'PENDING', 'ca', 'user', 'COMPLETED']);
|
|
418
|
+
return (
|
|
419
|
+
<Box sx={{ width: 500 }}>
|
|
420
|
+
<Select
|
|
421
|
+
label="Selección Completa"
|
|
422
|
+
multiple
|
|
423
|
+
filterable
|
|
424
|
+
maxChipsToShow={3}
|
|
425
|
+
maxHeight={250}
|
|
426
|
+
maxWidth="400px"
|
|
427
|
+
options={groupedOptions.concat(userOptions).concat(transactionStatuses)}
|
|
428
|
+
value={value}
|
|
429
|
+
onChange={(val) => setValue(val as string[])}
|
|
430
|
+
placeholder="Busca y selecciona todo tipo de elementos"
|
|
431
|
+
renderChipLabel={(item) => (
|
|
432
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
433
|
+
{item.img && <Avatar src={item.img} sx={{ width: 16, height: 16 }} />}
|
|
434
|
+
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
|
|
435
|
+
{item.label.length > 10 ? item.label.substring(0, 7) + '...' : item.label}
|
|
436
|
+
</Typography>
|
|
437
|
+
</Box>
|
|
438
|
+
)}
|
|
439
|
+
>
|
|
440
|
+
<Option>
|
|
441
|
+
{(item) => (
|
|
442
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, my: 0.5 }}>
|
|
443
|
+
{item.img && <Avatar src={item.img} sx={{ width: 28, height: 28 }} />}
|
|
444
|
+
<Box>
|
|
445
|
+
<Typography variant="body2" fontWeight="medium">{item.label}</Typography>
|
|
446
|
+
{item.group && (
|
|
447
|
+
<Typography variant="caption" color="text.secondary">
|
|
448
|
+
Grupo: {item.group}
|
|
449
|
+
</Typography>
|
|
450
|
+
)}
|
|
451
|
+
{item.disabled && <Chip label="No disponible" size="small" color="warning" sx={{ ml: 1 }} />}
|
|
452
|
+
</Box>
|
|
453
|
+
</Box>
|
|
454
|
+
)}
|
|
455
|
+
</Option>
|
|
456
|
+
</Select>
|
|
457
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
458
|
+
</Box>
|
|
459
|
+
);
|
|
460
|
+
},
|
|
461
|
+
parameters: {
|
|
462
|
+
docs: {
|
|
463
|
+
description: {
|
|
464
|
+
story: "Un `Select` que demuestra una combinación de todas las características: selección múltiple, filtrado, opciones agrupadas, renderizado personalizado de opciones y chips, y límites de altura/ancho."
|
|
465
|
+
},
|
|
466
|
+
source: { code: AllFeaturesCombinedDefinition.trim() } // Referencia a la definición
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
export const EmptyOptions: Story = {
|
|
472
|
+
args: {
|
|
473
|
+
label: 'Seleccionar (Vacío)',
|
|
474
|
+
options: [],
|
|
475
|
+
value: undefined,
|
|
476
|
+
placeholder: 'No hay opciones disponibles',
|
|
477
|
+
},
|
|
478
|
+
render: (args) => {
|
|
479
|
+
const [value, setValue] = useState(args.value);
|
|
480
|
+
return (
|
|
481
|
+
<Box sx={{ width: 300 }}>
|
|
482
|
+
<Select {...args} value={value ?? ''} onChange={setValue} />
|
|
483
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
484
|
+
</Box>
|
|
485
|
+
);
|
|
486
|
+
},
|
|
487
|
+
parameters: {
|
|
488
|
+
docs: {
|
|
489
|
+
story: "Select que se muestra cuando no hay opciones disponibles."
|
|
490
|
+
},
|
|
491
|
+
source: { code: EmptyOptionsDefinition.trim() } // Referencia a la definición
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
export const SelectWithManyOptions: Story = {
|
|
496
|
+
render: () => {
|
|
497
|
+
const manyOptions = Array.from({ length: 50 }, (_, i) => ({
|
|
498
|
+
value: `option-${i}`,
|
|
499
|
+
label: `Opción ${i + 1}`,
|
|
500
|
+
group: i < 25 ? 'Grupo A' : 'Grupo B',
|
|
501
|
+
}));
|
|
502
|
+
const [value, setValue] = useState('');
|
|
503
|
+
return (
|
|
504
|
+
<Box sx={{ width: 300 }}>
|
|
505
|
+
<Select
|
|
506
|
+
label="Muchas Opciones"
|
|
507
|
+
options={manyOptions}
|
|
508
|
+
value={value}
|
|
509
|
+
onChange={(val) => setValue(val as string)}
|
|
510
|
+
filterable
|
|
511
|
+
maxHeight={200}
|
|
512
|
+
/>
|
|
513
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
514
|
+
</Box>
|
|
515
|
+
);
|
|
516
|
+
},
|
|
517
|
+
parameters: {
|
|
518
|
+
docs: {
|
|
519
|
+
story: "Select con un gran número de opciones para demostrar el scroll y el filtrado eficiente."
|
|
520
|
+
},
|
|
521
|
+
source: { code: SelectWithManyOptionsDefinition.trim() } // Referencia a la definición
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// =============================================================================
|
|
526
|
+
// Historias de personalización (labelPosition / borderRadius / sx)
|
|
527
|
+
// =============================================================================
|
|
528
|
+
export const LabelPositionFloating: Story = {
|
|
529
|
+
render: () => {
|
|
530
|
+
const [value, setValue] = useState('');
|
|
531
|
+
return (
|
|
532
|
+
<Box sx={{ width: 300 }}>
|
|
533
|
+
<Select
|
|
534
|
+
label="Label flotante (MUI nativo)"
|
|
535
|
+
options={basicOptions}
|
|
536
|
+
value={value}
|
|
537
|
+
onChange={(val) => setValue(val as string)}
|
|
538
|
+
labelPosition="floating"
|
|
539
|
+
/>
|
|
540
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value || 'Ninguno'}</Typography>
|
|
541
|
+
</Box>
|
|
542
|
+
);
|
|
543
|
+
},
|
|
544
|
+
parameters: {
|
|
545
|
+
docs: {
|
|
546
|
+
description: {
|
|
547
|
+
story: "Select con `labelPosition='floating'`, que usa el comportamiento nativo de MUI (label flotando dentro del notched outline al enfocar). Útil cuando se necesita el look estándar de Material UI."
|
|
548
|
+
},
|
|
549
|
+
source: { code: LabelPositionFloatingDefinition.trim() }
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export const CustomBorderRadius: Story = {
|
|
555
|
+
render: () => {
|
|
556
|
+
const [valueA, setValueA] = useState('25');
|
|
557
|
+
const [valueB, setValueB] = useState('25');
|
|
558
|
+
const [valueC, setValueC] = useState('25');
|
|
559
|
+
return (
|
|
560
|
+
<Box sx={{ width: 300, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
561
|
+
<Select
|
|
562
|
+
label="Sin border radius"
|
|
563
|
+
options={basicOptions}
|
|
564
|
+
value={valueA}
|
|
565
|
+
onChange={(val) => setValueA(val as string)}
|
|
566
|
+
borderRadius={0}
|
|
567
|
+
/>
|
|
568
|
+
<Select
|
|
569
|
+
label="Border radius estándar"
|
|
570
|
+
options={basicOptions}
|
|
571
|
+
value={valueB}
|
|
572
|
+
onChange={(val) => setValueB(val as string)}
|
|
573
|
+
borderRadius={10}
|
|
574
|
+
/>
|
|
575
|
+
<Select
|
|
576
|
+
label="Pill-shaped"
|
|
577
|
+
options={basicOptions}
|
|
578
|
+
value={valueC}
|
|
579
|
+
onChange={(val) => setValueC(val as string)}
|
|
580
|
+
borderRadius={999}
|
|
581
|
+
/>
|
|
582
|
+
</Box>
|
|
583
|
+
);
|
|
584
|
+
},
|
|
585
|
+
parameters: {
|
|
586
|
+
docs: {
|
|
587
|
+
description: {
|
|
588
|
+
story: "Control del radio del borde mediante la prop `borderRadius`. Acepta un número (en px) o un string CSS como `'0.5rem'`."
|
|
589
|
+
},
|
|
590
|
+
source: { code: CustomBorderRadiusDefinition.trim() }
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
export const CustomStyling: Story = {
|
|
596
|
+
render: () => {
|
|
597
|
+
const [value, setValue] = useState<string[]>(['admin']);
|
|
598
|
+
return (
|
|
599
|
+
<Box sx={{ width: 360 }}>
|
|
600
|
+
<Select
|
|
601
|
+
label="Select con estilos personalizados"
|
|
602
|
+
options={userOptions}
|
|
603
|
+
multiple
|
|
604
|
+
value={value}
|
|
605
|
+
onChange={(val) => setValue(val as string[])}
|
|
606
|
+
placeholder="Selecciona usuarios"
|
|
607
|
+
sx={{
|
|
608
|
+
'& .MuiInputBase-root': {
|
|
609
|
+
backgroundColor: 'action.hover',
|
|
610
|
+
},
|
|
611
|
+
'& .MuiOutlinedInput-notchedOutline': {
|
|
612
|
+
borderColor: 'primary.main',
|
|
613
|
+
borderWidth: '2px',
|
|
614
|
+
},
|
|
615
|
+
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|
616
|
+
borderColor: 'primary.dark',
|
|
617
|
+
},
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {JSON.stringify(value)}</Typography>
|
|
621
|
+
</Box>
|
|
622
|
+
);
|
|
623
|
+
},
|
|
624
|
+
parameters: {
|
|
625
|
+
docs: {
|
|
626
|
+
description: {
|
|
627
|
+
story: "Demuestra cómo el consumidor puede pasar un `sx` personalizado. El sx del consumidor se mergea sobre los defaults del componente, permitiendo overrides granulares (fondo, borde, hover, etc.) sin romper el comportamiento del label."
|
|
628
|
+
},
|
|
629
|
+
source: { code: CustomStylingDefinition.trim() }
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
export const AsyncSelect: Story = {
|
|
635
|
+
render: () => {
|
|
636
|
+
const [value, setValue] = useState('');
|
|
637
|
+
|
|
638
|
+
const mockAsyncOptions = [
|
|
639
|
+
{ value: 'apple', label: 'Apple' },
|
|
640
|
+
{ value: 'banana', label: 'Banana' },
|
|
641
|
+
{ value: 'orange', label: 'Orange' },
|
|
642
|
+
{ value: 'grape', label: 'Grape' },
|
|
643
|
+
{ value: 'strawberry', label: 'Strawberry' },
|
|
644
|
+
{ value: 'blueberry', label: 'Blueberry' },
|
|
645
|
+
{ value: 'pineapple', label: 'Pineapple' },
|
|
646
|
+
{ value: 'watermelon', label: 'Watermelon' },
|
|
647
|
+
{ value: 'kiwi', label: 'Kiwi' },
|
|
648
|
+
{ value: 'lemon', label: 'Lemon' },
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
// Simulate an API call
|
|
652
|
+
const simulatedLoadOptions = (inputValue: string): Promise<SelectOption[]> => {
|
|
653
|
+
return new Promise((resolve) => {
|
|
654
|
+
setTimeout(() => {
|
|
655
|
+
const filtered = mockAsyncOptions.filter(option =>
|
|
656
|
+
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
|
657
|
+
);
|
|
658
|
+
resolve(filtered);
|
|
659
|
+
}, 800); // Simulate network delay
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
return (
|
|
664
|
+
<Box sx={{ width: 300 }}>
|
|
665
|
+
<Select
|
|
666
|
+
|
|
667
|
+
loadOptions={simulatedLoadOptions}
|
|
668
|
+
value={value}
|
|
669
|
+
onChange={(val) => setValue(val as string)}
|
|
670
|
+
placeholder="Escribe para buscar..."
|
|
671
|
+
loadingMessage="Buscando frutas..."
|
|
672
|
+
noOptionsMessage="No se encontraron frutas."
|
|
673
|
+
debounceTimeout={500} // tiempo que tardara el input en buscar despues de haber escrito la ultima letra
|
|
674
|
+
/>
|
|
675
|
+
<Typography sx={{ mt: 2 }}>Valor seleccionado: {value}</Typography>
|
|
676
|
+
</Box>
|
|
677
|
+
);
|
|
678
|
+
},
|
|
679
|
+
parameters: {
|
|
680
|
+
docs: {
|
|
681
|
+
description: {
|
|
682
|
+
story: "Un `Select` que carga opciones de forma asíncrona a medida que el usuario escribe, con un retraso simulado."
|
|
683
|
+
},
|
|
684
|
+
source: { code: AsyncSelectDefinition.trim() }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|