@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,158 @@
|
|
|
1
|
+
export const BasicInputGroupDefinition = `
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Typography } from '@mui/material';
|
|
4
|
+
import InputGroup from './InputGroup';
|
|
5
|
+
import { Input } from '../Input';
|
|
6
|
+
import { Button } from '../Button';
|
|
7
|
+
|
|
8
|
+
export const BasicInputGroupExample = () => {
|
|
9
|
+
const [text, setText] = useState('');
|
|
10
|
+
return (
|
|
11
|
+
<Box>
|
|
12
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
13
|
+
Grupo de entrada básico
|
|
14
|
+
</Typography>
|
|
15
|
+
<InputGroup>
|
|
16
|
+
<Input label="Buscar" value={text} onChange={(val) => setText(val as string)} />
|
|
17
|
+
<Button variant="outlined">Buscar</Button>
|
|
18
|
+
</InputGroup>
|
|
19
|
+
</Box>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const CombinedInputGroupDefinition = `
|
|
25
|
+
import { useState } from 'react';
|
|
26
|
+
import { Box, Typography } from '@mui/material';
|
|
27
|
+
import InputGroup from './InputGroup';
|
|
28
|
+
import { Input } from '../Input';
|
|
29
|
+
import Select, { SelectOption } from '../Select/Select';
|
|
30
|
+
import { Button } from '../Button';
|
|
31
|
+
|
|
32
|
+
const basicOptions: SelectOption[] = [
|
|
33
|
+
{ value: '10', label: '10' },
|
|
34
|
+
{ value: '25', label: '25' },
|
|
35
|
+
{ value: '50', label: '50' },
|
|
36
|
+
{ value: '100', label: '100' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const CombinedInputGroupExample = () => {
|
|
40
|
+
const [text, setText] = useState('');
|
|
41
|
+
const [selectValue, setSelectValue] = useState('25');
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Box>
|
|
45
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
46
|
+
Grupo de entrada combinado
|
|
47
|
+
</Typography>
|
|
48
|
+
<InputGroup>
|
|
49
|
+
<Select
|
|
50
|
+
label="Registros"
|
|
51
|
+
options={basicOptions}
|
|
52
|
+
value={selectValue}
|
|
53
|
+
onChange={(val) => setSelectValue(val as string)}
|
|
54
|
+
/>
|
|
55
|
+
<Input
|
|
56
|
+
label="Filtrar por nombre"
|
|
57
|
+
value={text}
|
|
58
|
+
onChange={(val) => setText(val as string)}
|
|
59
|
+
/>
|
|
60
|
+
<Button variant="contained">Aplicar</Button>
|
|
61
|
+
</InputGroup>
|
|
62
|
+
</Box>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
export const DatePickerInputGroupDefinition = `
|
|
68
|
+
import { useState } from 'react';
|
|
69
|
+
import { Box, Typography } from '@mui/material';
|
|
70
|
+
import dayjs, { Dayjs } from 'dayjs';
|
|
71
|
+
import InputGroup from './InputGroup';
|
|
72
|
+
import { DatePicker } from '../DatePicker';
|
|
73
|
+
import { Button } from '../Button';
|
|
74
|
+
|
|
75
|
+
export const DatePickerInputGroupExample = () => {
|
|
76
|
+
const [selectedDate, setSelectedDate] = useState<Dayjs | null>(dayjs());
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Box>
|
|
80
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
81
|
+
Grupo de entrada con DatePicker
|
|
82
|
+
</Typography>
|
|
83
|
+
<InputGroup>
|
|
84
|
+
<DatePicker
|
|
85
|
+
label="Fecha de inicio"
|
|
86
|
+
selectedDate={selectedDate}
|
|
87
|
+
onDateChange={(date) => setSelectedDate(date)}
|
|
88
|
+
/>
|
|
89
|
+
<Button variant="contained">Ver calendario</Button>
|
|
90
|
+
</InputGroup>
|
|
91
|
+
</Box>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
export const VerticalInputGroupDefinition = `
|
|
97
|
+
import { useState } from 'react';
|
|
98
|
+
import { Box } from '@mui/material';
|
|
99
|
+
import InputGroup from './InputGroup';
|
|
100
|
+
import { Input } from '../Input';
|
|
101
|
+
|
|
102
|
+
export const VerticalInputGroupExample = () => {
|
|
103
|
+
const [nombre, setNombre] = useState('');
|
|
104
|
+
const [email, setEmail] = useState('');
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Box sx={{ width: 320 }}>
|
|
108
|
+
<InputGroup orientation="vertical">
|
|
109
|
+
<Input label="Nombre" value={nombre} onChange={(val) => setNombre(val as string)} />
|
|
110
|
+
<Input label="Email" value={email} onChange={(val) => setEmail(val as string)} />
|
|
111
|
+
</InputGroup>
|
|
112
|
+
</Box>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
export const CustomStylingInputGroupDefinition = `
|
|
118
|
+
import { useState } from 'react';
|
|
119
|
+
import { Box } from '@mui/material';
|
|
120
|
+
import InputGroup from './InputGroup';
|
|
121
|
+
import { Input } from '../Input';
|
|
122
|
+
import { Button } from '../Button';
|
|
123
|
+
|
|
124
|
+
export const CustomStylingInputGroupExample = () => {
|
|
125
|
+
const [q, setQ] = useState('');
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Box sx={{ width: 500 }}>
|
|
129
|
+
<InputGroup
|
|
130
|
+
borderRadius={999}
|
|
131
|
+
sx={{
|
|
132
|
+
bgcolor: 'action.hover',
|
|
133
|
+
borderColor: 'primary.main',
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<Input label="Búsqueda avanzada" value={q} onChange={(val) => setQ(val as string)} />
|
|
137
|
+
<Button variant="contained" color="primary">Buscar</Button>
|
|
138
|
+
</InputGroup>
|
|
139
|
+
</Box>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
export const DisabledInputGroupDefinition = `
|
|
145
|
+
import { Box } from '@mui/material';
|
|
146
|
+
import InputGroup from './InputGroup';
|
|
147
|
+
import { Input } from '../Input';
|
|
148
|
+
import { Button } from '../Button';
|
|
149
|
+
|
|
150
|
+
export const DisabledInputGroupExample = () => (
|
|
151
|
+
<Box sx={{ width: 400 }}>
|
|
152
|
+
<InputGroup disabled>
|
|
153
|
+
<Input label="Búsqueda" value="" onChange={() => {}} disabled />
|
|
154
|
+
<Button variant="outlined" disabled>Buscar</Button>
|
|
155
|
+
</InputGroup>
|
|
156
|
+
</Box>
|
|
157
|
+
);
|
|
158
|
+
`;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Typography } from '@mui/material';
|
|
4
|
+
import dayjs, { Dayjs } from 'dayjs';
|
|
5
|
+
|
|
6
|
+
import InputGroup from './InputGroup';
|
|
7
|
+
import { Input } from '../Input';
|
|
8
|
+
import Select, { SelectOption } from '../Select/Select';
|
|
9
|
+
import { Button } from '../Button';
|
|
10
|
+
import { DatePicker } from '../DatePicker';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
BasicInputGroupDefinition,
|
|
14
|
+
CombinedInputGroupDefinition,
|
|
15
|
+
DatePickerInputGroupDefinition,
|
|
16
|
+
VerticalInputGroupDefinition,
|
|
17
|
+
CustomStylingInputGroupDefinition,
|
|
18
|
+
DisabledInputGroupDefinition,
|
|
19
|
+
} from './InputGroup.definitions';
|
|
20
|
+
|
|
21
|
+
const meta: Meta<typeof InputGroup> = {
|
|
22
|
+
title: 'Components/InputGroup',
|
|
23
|
+
component: InputGroup,
|
|
24
|
+
tags: ['autodocs'],
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: 'centered',
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
'Agrupa visualmente varios campos (Input / Select / DatePicker / Button, etc.) bajo un único borde, con separadores internos por cada slot. Respeta el espacio reservado para los labels `outside` de Input/Select, y mantiene el contenido centrado verticalmente sin importar si cada hijo tiene label o no.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (
|
|
36
|
+
<Box sx={{ width: 600, maxWidth: '100%' }}>
|
|
37
|
+
<Story />
|
|
38
|
+
</Box>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
argTypes: {
|
|
42
|
+
borderRadius: {
|
|
43
|
+
control: 'number',
|
|
44
|
+
description: 'Radio del borde del grupo. Default: 10.',
|
|
45
|
+
},
|
|
46
|
+
orientation: {
|
|
47
|
+
control: 'radio',
|
|
48
|
+
options: ['horizontal', 'vertical'],
|
|
49
|
+
description: 'Orientación del grupo.',
|
|
50
|
+
},
|
|
51
|
+
disabled: {
|
|
52
|
+
control: 'boolean',
|
|
53
|
+
description: 'Desactiva visualmente el grupo (los hijos manejan su propio `disabled` lógico).',
|
|
54
|
+
},
|
|
55
|
+
sx: {
|
|
56
|
+
control: false,
|
|
57
|
+
description: 'sx del contenedor raíz. Se mergea sobre los defaults.',
|
|
58
|
+
},
|
|
59
|
+
slotSx: {
|
|
60
|
+
control: false,
|
|
61
|
+
description: 'sx aplicado a cada slot que envuelve un hijo.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default meta;
|
|
67
|
+
type Story = StoryObj<typeof InputGroup>;
|
|
68
|
+
|
|
69
|
+
const basicOptions: SelectOption[] = [
|
|
70
|
+
{ value: '10', label: '10' },
|
|
71
|
+
{ value: '25', label: '25' },
|
|
72
|
+
{ value: '50', label: '50' },
|
|
73
|
+
{ value: '100', label: '100' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export const BasicInputGroup: Story = {
|
|
77
|
+
render: () => {
|
|
78
|
+
const [text, setText] = useState('');
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Box>
|
|
82
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
83
|
+
Grupo de entrada básico
|
|
84
|
+
</Typography>
|
|
85
|
+
<InputGroup>
|
|
86
|
+
<Input
|
|
87
|
+
label="Buscar"
|
|
88
|
+
value={text}
|
|
89
|
+
onChange={(val) => setText(val as string)}
|
|
90
|
+
/>
|
|
91
|
+
<Button variant="outlined">Buscar</Button>
|
|
92
|
+
</InputGroup>
|
|
93
|
+
</Box>
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
parameters: {
|
|
97
|
+
docs: {
|
|
98
|
+
description: {
|
|
99
|
+
story: 'Un grupo de entrada simple que combina un campo de texto y un botón.',
|
|
100
|
+
},
|
|
101
|
+
source: { code: BasicInputGroupDefinition.trim() },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const CombinedInputGroup: Story = {
|
|
107
|
+
render: () => {
|
|
108
|
+
const [text, setText] = useState('');
|
|
109
|
+
const [selectValue, setSelectValue] = useState('25');
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Box>
|
|
113
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
114
|
+
Grupo de entrada combinado
|
|
115
|
+
</Typography>
|
|
116
|
+
<InputGroup>
|
|
117
|
+
<Select
|
|
118
|
+
label="Registros"
|
|
119
|
+
options={basicOptions}
|
|
120
|
+
value={selectValue}
|
|
121
|
+
onChange={(val) => setSelectValue(val as string)}
|
|
122
|
+
/>
|
|
123
|
+
<Input
|
|
124
|
+
label="Filtrar por nombre"
|
|
125
|
+
value={text}
|
|
126
|
+
onChange={(val) => setText(val as string)}
|
|
127
|
+
/>
|
|
128
|
+
<Button variant="contained">Aplicar</Button>
|
|
129
|
+
</InputGroup>
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
parameters: {
|
|
134
|
+
docs: {
|
|
135
|
+
description: {
|
|
136
|
+
story:
|
|
137
|
+
'Select + Input + Button compartiendo un borde. Los labels de Select e Input flotan por encima del borde del grupo con la misma animación que fuera del grupo.',
|
|
138
|
+
},
|
|
139
|
+
source: { code: CombinedInputGroupDefinition.trim() },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const DatePickerInputGroup: Story = {
|
|
145
|
+
render: () => {
|
|
146
|
+
const [selectedDate, setSelectedDate] = useState<Dayjs | null>(dayjs());
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Box>
|
|
150
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
151
|
+
Grupo de entrada con DatePicker
|
|
152
|
+
</Typography>
|
|
153
|
+
<InputGroup>
|
|
154
|
+
<DatePicker
|
|
155
|
+
label="Fecha de inicio"
|
|
156
|
+
selectedDate={selectedDate}
|
|
157
|
+
onDateChange={(date) => setSelectedDate(date)}
|
|
158
|
+
/>
|
|
159
|
+
<Button variant="contained">Ver calendario</Button>
|
|
160
|
+
</InputGroup>
|
|
161
|
+
</Box>
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
parameters: {
|
|
165
|
+
docs: {
|
|
166
|
+
description: {
|
|
167
|
+
story: 'Un grupo de entrada que combina un `DatePicker` con un botón de acción.',
|
|
168
|
+
},
|
|
169
|
+
source: { code: DatePickerInputGroupDefinition.trim() },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const VerticalInputGroup: Story = {
|
|
175
|
+
render: () => {
|
|
176
|
+
const [nombre, setNombre] = useState('');
|
|
177
|
+
const [email, setEmail] = useState('');
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Box sx={{ width: 320 }}>
|
|
181
|
+
<InputGroup orientation="vertical">
|
|
182
|
+
<Input
|
|
183
|
+
label="Nombre"
|
|
184
|
+
value={nombre}
|
|
185
|
+
onChange={(val) => setNombre(val as string)}
|
|
186
|
+
/>
|
|
187
|
+
<Input
|
|
188
|
+
label="Email"
|
|
189
|
+
value={email}
|
|
190
|
+
onChange={(val) => setEmail(val as string)}
|
|
191
|
+
/>
|
|
192
|
+
</InputGroup>
|
|
193
|
+
</Box>
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
parameters: {
|
|
197
|
+
docs: {
|
|
198
|
+
description: {
|
|
199
|
+
story: 'Grupo en orientación vertical: los separadores pasan a ser horizontales entre slots.',
|
|
200
|
+
},
|
|
201
|
+
source: { code: VerticalInputGroupDefinition.trim() },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const CustomStyling: Story = {
|
|
207
|
+
render: () => {
|
|
208
|
+
const [q, setQ] = useState('');
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Box sx={{ width: 500 }}>
|
|
212
|
+
<InputGroup
|
|
213
|
+
borderRadius={999}
|
|
214
|
+
sx={{
|
|
215
|
+
bgcolor: 'action.hover',
|
|
216
|
+
borderColor: 'primary.main',
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<Input
|
|
220
|
+
label="Búsqueda avanzada"
|
|
221
|
+
value={q}
|
|
222
|
+
onChange={(val) => setQ(val as string)}
|
|
223
|
+
/>
|
|
224
|
+
<Button variant="contained" color="primary">
|
|
225
|
+
Buscar
|
|
226
|
+
</Button>
|
|
227
|
+
</InputGroup>
|
|
228
|
+
</Box>
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
parameters: {
|
|
232
|
+
docs: {
|
|
233
|
+
description: {
|
|
234
|
+
story:
|
|
235
|
+
'Demo del passthrough de `sx` y `borderRadius`. El consumidor puede pintar fondo, cambiar el color del borde o dejar el grupo completamente pill-shaped (`borderRadius={999}`) sin tocar el componente.',
|
|
236
|
+
},
|
|
237
|
+
source: { code: CustomStylingInputGroupDefinition.trim() },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const DisabledGroup: Story = {
|
|
243
|
+
render: () => (
|
|
244
|
+
<Box sx={{ width: 400 }}>
|
|
245
|
+
<InputGroup disabled>
|
|
246
|
+
<Input
|
|
247
|
+
label="Búsqueda"
|
|
248
|
+
value=""
|
|
249
|
+
onChange={() => {}}
|
|
250
|
+
disabled
|
|
251
|
+
/>
|
|
252
|
+
<Button variant="outlined" disabled>
|
|
253
|
+
Buscar
|
|
254
|
+
</Button>
|
|
255
|
+
</InputGroup>
|
|
256
|
+
</Box>
|
|
257
|
+
),
|
|
258
|
+
parameters: {
|
|
259
|
+
docs: {
|
|
260
|
+
description: {
|
|
261
|
+
story:
|
|
262
|
+
'`disabled` aplica opacidad y bloquea interacciones en el grupo. Cada hijo debe manejar su propio `disabled` lógico (validación, formularios, etc.).',
|
|
263
|
+
},
|
|
264
|
+
source: { code: DisabledInputGroupDefinition.trim() },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React, { Children, isValidElement } from 'react';
|
|
2
|
+
import { Box, type SxProps, type Theme } from '@mui/material';
|
|
3
|
+
|
|
4
|
+
export interface InputGroupProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* sx aplicado al contenedor raíz del grupo. Se mergea sobre los defaults.
|
|
8
|
+
*/
|
|
9
|
+
sx?: SxProps<Theme>;
|
|
10
|
+
/**
|
|
11
|
+
* sx aplicado a cada slot individual. Útil para controlar flex/gap entre
|
|
12
|
+
* los hijos del grupo.
|
|
13
|
+
*/
|
|
14
|
+
slotSx?: SxProps<Theme>;
|
|
15
|
+
/**
|
|
16
|
+
* Radio del borde del grupo. Default: 10.
|
|
17
|
+
*/
|
|
18
|
+
borderRadius?: number | string;
|
|
19
|
+
/**
|
|
20
|
+
* Desactiva visualmente el grupo (no cascada lógica — los hijos deben
|
|
21
|
+
* manejar su propio `disabled`).
|
|
22
|
+
*/
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Orientación del grupo. Default: 'horizontal'.
|
|
26
|
+
*/
|
|
27
|
+
orientation?: 'horizontal' | 'vertical';
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const mergeSx = (base: SxProps<Theme>, extra?: SxProps<Theme>): SxProps<Theme> => {
|
|
32
|
+
if (!extra) return base;
|
|
33
|
+
const baseArr = Array.isArray(base) ? base : [base];
|
|
34
|
+
const extraArr = Array.isArray(extra) ? extra : [extra];
|
|
35
|
+
return [...baseArr, ...extraArr] as SxProps<Theme>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Agrupa visualmente varios componentes de entrada (Input / Select / DatePicker /
|
|
40
|
+
* Button, etc.) compartiendo un solo borde y con separadores internos.
|
|
41
|
+
*
|
|
42
|
+
* El grupo respeta el espacio que Input/Select reservan para su label `outside`
|
|
43
|
+
* (marginTop=14px) moviendo ese reservado al contenedor externo y cancelándolo
|
|
44
|
+
* en los hijos. Los labels siguen flotando por encima del borde del grupo con
|
|
45
|
+
* la misma animación que fuera del grupo.
|
|
46
|
+
*
|
|
47
|
+
* No inspecciona el tipo de cada hijo; aplica los overrides a través de
|
|
48
|
+
* descendant selectors por CSS para funcionar con cualquier wrapper / memoizado.
|
|
49
|
+
*/
|
|
50
|
+
const InputGroup: React.FC<InputGroupProps> = ({
|
|
51
|
+
children,
|
|
52
|
+
sx,
|
|
53
|
+
slotSx,
|
|
54
|
+
borderRadius = 10,
|
|
55
|
+
disabled,
|
|
56
|
+
orientation = 'horizontal',
|
|
57
|
+
className,
|
|
58
|
+
}) => {
|
|
59
|
+
const radius = typeof borderRadius === 'number' ? `${borderRadius}px` : borderRadius;
|
|
60
|
+
const slots = Children.toArray(children).filter(isValidElement);
|
|
61
|
+
const isHorizontal = orientation === 'horizontal';
|
|
62
|
+
|
|
63
|
+
const baseSx: SxProps<Theme> = {
|
|
64
|
+
// Respeta el label "outside" de Input/Select: los hijos pierden su marginTop
|
|
65
|
+
// (ver más abajo) y el grupo reserva el mismo espacio arriba para que los
|
|
66
|
+
// labels floten por encima del borde del grupo.
|
|
67
|
+
marginTop: '14px',
|
|
68
|
+
|
|
69
|
+
display: 'flex',
|
|
70
|
+
flexDirection: isHorizontal ? 'row' : 'column',
|
|
71
|
+
alignItems: 'stretch',
|
|
72
|
+
width: '100%',
|
|
73
|
+
border: (theme) => `1px solid ${theme.palette.divider}`,
|
|
74
|
+
borderRadius: radius,
|
|
75
|
+
backgroundColor: 'background.paper',
|
|
76
|
+
transition: 'border-color 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms, box-shadow 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms',
|
|
77
|
+
'&:focus-within': {
|
|
78
|
+
borderColor: 'primary.main',
|
|
79
|
+
boxShadow: (theme) => `0 0 0 1px ${theme.palette.primary.main}`,
|
|
80
|
+
},
|
|
81
|
+
opacity: disabled ? 0.6 : 1,
|
|
82
|
+
pointerEvents: disabled ? 'none' : 'auto',
|
|
83
|
+
|
|
84
|
+
// Cancelar el marginTop que Input/Select reservan en labelPosition="outside".
|
|
85
|
+
// El grupo ya lo reservó en su nivel externo.
|
|
86
|
+
'& .MuiFormControl-root, & > .InputGroup__slot > .MuiTextField-root, & > .InputGroup__slot > .MuiFormControl-root': {
|
|
87
|
+
marginTop: 0,
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Quitar el borde del notched outline interno: el borde visible es el del
|
|
91
|
+
// grupo, los separadores son los borderRight/borderBottom de cada slot.
|
|
92
|
+
'& .MuiOutlinedInput-notchedOutline': {
|
|
93
|
+
border: 'none',
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Forzar que todos los campos pinten sus esquinas cuadradas (las esquinas
|
|
97
|
+
// redondeadas se aplican solo al primer/último slot más abajo).
|
|
98
|
+
'& .MuiInputBase-root, & .MuiChip-root': {
|
|
99
|
+
borderRadius: 0,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Botones integrados: sin radius propio, altura completa, sin sombra.
|
|
103
|
+
'& .MuiButton-root': {
|
|
104
|
+
borderRadius: 0,
|
|
105
|
+
height: '100%',
|
|
106
|
+
boxShadow: 'none',
|
|
107
|
+
border: 'none',
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Slots: cada hijo vive en un slot neutro que lo centra verticalmente.
|
|
111
|
+
'& > .InputGroup__slot': {
|
|
112
|
+
flex: 1,
|
|
113
|
+
display: 'flex',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
minWidth: 0,
|
|
116
|
+
// Separador entre slots (horizontal: borderRight / vertical: borderBottom),
|
|
117
|
+
// usando el color de divider del theme.
|
|
118
|
+
...(isHorizontal
|
|
119
|
+
? { borderRight: (theme: Theme) => `1px solid ${theme.palette.divider}` }
|
|
120
|
+
: { borderBottom: (theme: Theme) => `1px solid ${theme.palette.divider}` }),
|
|
121
|
+
'&:last-of-type': {
|
|
122
|
+
borderRight: 'none',
|
|
123
|
+
borderBottom: 'none',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// Esquinas redondeadas: primer slot (izquierda o arriba).
|
|
128
|
+
'& > .InputGroup__slot:first-of-type .MuiInputBase-root, & > .InputGroup__slot:first-of-type .MuiButton-root, & > .InputGroup__slot:first-of-type .MuiChip-root':
|
|
129
|
+
isHorizontal
|
|
130
|
+
? {
|
|
131
|
+
borderTopLeftRadius: radius,
|
|
132
|
+
borderBottomLeftRadius: radius,
|
|
133
|
+
}
|
|
134
|
+
: {
|
|
135
|
+
borderTopLeftRadius: radius,
|
|
136
|
+
borderTopRightRadius: radius,
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Esquinas redondeadas: último slot (derecha o abajo).
|
|
140
|
+
'& > .InputGroup__slot:last-of-type .MuiInputBase-root, & > .InputGroup__slot:last-of-type .MuiButton-root, & > .InputGroup__slot:last-of-type .MuiChip-root':
|
|
141
|
+
isHorizontal
|
|
142
|
+
? {
|
|
143
|
+
borderTopRightRadius: radius,
|
|
144
|
+
borderBottomRightRadius: radius,
|
|
145
|
+
}
|
|
146
|
+
: {
|
|
147
|
+
borderBottomLeftRadius: radius,
|
|
148
|
+
borderBottomRightRadius: radius,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const baseSlotSx: SxProps<Theme> = {
|
|
153
|
+
flex: 1,
|
|
154
|
+
display: 'flex',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
minWidth: 0,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Box
|
|
161
|
+
data-testid="input-group"
|
|
162
|
+
className={className}
|
|
163
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
164
|
+
sx={mergeSx(baseSx, sx)}
|
|
165
|
+
>
|
|
166
|
+
{slots.map((child, i) => (
|
|
167
|
+
<Box
|
|
168
|
+
key={i}
|
|
169
|
+
className="InputGroup__slot"
|
|
170
|
+
sx={mergeSx(baseSlotSx, slotSx)}
|
|
171
|
+
>
|
|
172
|
+
{child}
|
|
173
|
+
</Box>
|
|
174
|
+
))}
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default InputGroup;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InputGroup } from './InputGroup';
|