@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,258 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { within, expect } from 'storybook/test';
|
|
3
|
+
import { Avatar } from './Avatar';
|
|
4
|
+
import {
|
|
5
|
+
DefaultAvatarDefinition,
|
|
6
|
+
WithTypeAndTextDefinition,
|
|
7
|
+
WithBadgesAndTooltipDefinition,
|
|
8
|
+
SizeSmallDefinition,
|
|
9
|
+
SizeMediumDefinition,
|
|
10
|
+
SizeLargeDefinition,
|
|
11
|
+
SizeExtraLargeDefinition,
|
|
12
|
+
NumericSizeDefinition,
|
|
13
|
+
FallbackIconDefinition,
|
|
14
|
+
DisplayedAvatarsLimitDefinition,
|
|
15
|
+
CustomStylingDefinition,
|
|
16
|
+
} from './Avatar.definitions';
|
|
17
|
+
|
|
18
|
+
const meta: Meta<typeof Avatar> = {
|
|
19
|
+
title: 'Components/Avatar',
|
|
20
|
+
component: Avatar,
|
|
21
|
+
parameters: {
|
|
22
|
+
layout: 'centered',
|
|
23
|
+
docs: {
|
|
24
|
+
description: {
|
|
25
|
+
component:
|
|
26
|
+
'Avatar basado en MUI Avatar con soporte para stacking, tooltips, fallback a icono/badge y passthrough de sx. Admite imagen, texto y badge por cada item.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
tags: ['autodocs'],
|
|
31
|
+
argTypes: {
|
|
32
|
+
items: {
|
|
33
|
+
control: 'object',
|
|
34
|
+
description: 'Array de `AvatarItem` (text/imageUrl/badge/color/backgroundColor).',
|
|
35
|
+
},
|
|
36
|
+
type: {
|
|
37
|
+
control: 'text',
|
|
38
|
+
description: 'Etiqueta que antecede al texto cuando hay un único item (ej. "Owner: Maria").',
|
|
39
|
+
},
|
|
40
|
+
displayedAvatars: {
|
|
41
|
+
control: 'number',
|
|
42
|
+
description: 'Número máximo de avatares visibles. El resto se contabiliza como `+N`.',
|
|
43
|
+
},
|
|
44
|
+
size: {
|
|
45
|
+
control: 'select',
|
|
46
|
+
options: ['sm', 'md', 'lg', 'xl'],
|
|
47
|
+
description: 'Tamaño del avatar. Acepta presets (`sm | md | lg | xl`) o un número libre en px.',
|
|
48
|
+
},
|
|
49
|
+
showText: {
|
|
50
|
+
control: 'boolean',
|
|
51
|
+
description: 'Muestra el texto al lado del avatar.',
|
|
52
|
+
},
|
|
53
|
+
showTooltip: {
|
|
54
|
+
control: 'boolean',
|
|
55
|
+
description: 'Muestra el tooltip con `item.text` al hover.',
|
|
56
|
+
},
|
|
57
|
+
overlap: {
|
|
58
|
+
control: 'number',
|
|
59
|
+
description: 'Overlap en px entre avatares cuando hay varios (override del default por tamaño).',
|
|
60
|
+
},
|
|
61
|
+
sx: {
|
|
62
|
+
control: false,
|
|
63
|
+
description: 'sx del contenedor raíz. Se mergea sobre los defaults.',
|
|
64
|
+
},
|
|
65
|
+
avatarSx: {
|
|
66
|
+
control: false,
|
|
67
|
+
description: 'sx aplicado a cada MuiAvatar individual (borde, colores, etc).',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default meta;
|
|
73
|
+
type Story = StoryObj<typeof Avatar>;
|
|
74
|
+
|
|
75
|
+
export const Default: Story = {
|
|
76
|
+
args: {
|
|
77
|
+
items: [
|
|
78
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=1' },
|
|
79
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=2' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
parameters: {
|
|
83
|
+
docs: { source: { code: DefaultAvatarDefinition.trim() } },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const WithTypeAndText: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
type: 'Owner',
|
|
90
|
+
items: [{ text: 'Maria', imageUrl: 'https://i.pravatar.cc/150?img=5' }],
|
|
91
|
+
showText: true,
|
|
92
|
+
},
|
|
93
|
+
play: async ({ canvasElement }) => {
|
|
94
|
+
const canvas = within(canvasElement);
|
|
95
|
+
const text = await canvas.findByTestId('text');
|
|
96
|
+
const type = await canvas.findByTestId('type');
|
|
97
|
+
expect(text).toBeInTheDocument();
|
|
98
|
+
expect(type).toHaveTextContent('Owner:');
|
|
99
|
+
},
|
|
100
|
+
parameters: {
|
|
101
|
+
docs: { source: { code: WithTypeAndTextDefinition.trim() } },
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const WithBadgesAndTooltip: Story = {
|
|
106
|
+
args: {
|
|
107
|
+
showTooltip: true,
|
|
108
|
+
items: [
|
|
109
|
+
{ text: 'John D.', badge: 'JD', backgroundColor: '#EF5350', color: '#fff' },
|
|
110
|
+
{ text: 'Alice B.', badge: 'AB', backgroundColor: '#AB47BC', color: '#fff' },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
play: async ({ canvasElement }) => {
|
|
114
|
+
const canvas = within(canvasElement);
|
|
115
|
+
const badges = await canvas.findAllByTestId('badge');
|
|
116
|
+
expect(badges.length).toBe(2);
|
|
117
|
+
},
|
|
118
|
+
parameters: {
|
|
119
|
+
docs: { source: { code: WithBadgesAndTooltipDefinition.trim() } },
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const SizeSmall: Story = {
|
|
124
|
+
args: {
|
|
125
|
+
size: 'sm',
|
|
126
|
+
items: [
|
|
127
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=11' },
|
|
128
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=12' },
|
|
129
|
+
{ text: 'User Three', imageUrl: 'https://i.pravatar.cc/150?img=13' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
parameters: {
|
|
133
|
+
docs: { source: { code: SizeSmallDefinition.trim() } },
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const SizeMedium: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
size: 'md',
|
|
140
|
+
items: [
|
|
141
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=21' },
|
|
142
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=22' },
|
|
143
|
+
{ text: 'User Three', imageUrl: 'https://i.pravatar.cc/150?img=23' },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
parameters: {
|
|
147
|
+
docs: { source: { code: SizeMediumDefinition.trim() } },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const SizeLarge: Story = {
|
|
152
|
+
args: {
|
|
153
|
+
size: 'lg',
|
|
154
|
+
items: [
|
|
155
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=31' },
|
|
156
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=32' },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
parameters: {
|
|
160
|
+
docs: { source: { code: SizeLargeDefinition.trim() } },
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const SizeExtraLarge: Story = {
|
|
165
|
+
args: {
|
|
166
|
+
size: 'xl',
|
|
167
|
+
items: [{ text: 'Maria', imageUrl: 'https://i.pravatar.cc/150?img=5' }],
|
|
168
|
+
showText: false,
|
|
169
|
+
},
|
|
170
|
+
parameters: {
|
|
171
|
+
docs: { source: { code: SizeExtraLargeDefinition.trim() } },
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const NumericSize: Story = {
|
|
176
|
+
args: {
|
|
177
|
+
size: 72,
|
|
178
|
+
items: [
|
|
179
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=41' },
|
|
180
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=42' },
|
|
181
|
+
],
|
|
182
|
+
showText: false,
|
|
183
|
+
},
|
|
184
|
+
parameters: {
|
|
185
|
+
docs: {
|
|
186
|
+
description: {
|
|
187
|
+
story: 'Pasando un número en `size` se calcula automáticamente el borde, font-size y overlap proporcional.',
|
|
188
|
+
},
|
|
189
|
+
source: { code: NumericSizeDefinition.trim() },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const FallbackIcon: Story = {
|
|
195
|
+
args: {
|
|
196
|
+
showTooltip: true,
|
|
197
|
+
items: [
|
|
198
|
+
{ text: 'Without image' },
|
|
199
|
+
{ text: 'Broken image', imageUrl: 'https://example.invalid/broken.png' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
parameters: {
|
|
203
|
+
docs: {
|
|
204
|
+
description: {
|
|
205
|
+
story: 'Cuando no hay imagen (o falla la carga) y no hay badge, cae al icono `AccountCircle`.',
|
|
206
|
+
},
|
|
207
|
+
source: { code: FallbackIconDefinition.trim() },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const DisplayedAvatarsLimit: Story = {
|
|
213
|
+
args: {
|
|
214
|
+
displayedAvatars: 3,
|
|
215
|
+
items: [
|
|
216
|
+
{ text: 'User 1', imageUrl: 'https://i.pravatar.cc/150?img=51' },
|
|
217
|
+
{ text: 'User 2', imageUrl: 'https://i.pravatar.cc/150?img=52' },
|
|
218
|
+
{ text: 'User 3', imageUrl: 'https://i.pravatar.cc/150?img=53' },
|
|
219
|
+
{ text: 'User 4', imageUrl: 'https://i.pravatar.cc/150?img=54' },
|
|
220
|
+
{ text: 'User 5', imageUrl: 'https://i.pravatar.cc/150?img=55' },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
parameters: {
|
|
224
|
+
docs: {
|
|
225
|
+
description: {
|
|
226
|
+
story: 'Solo se renderizan `displayedAvatars` avatares; el resto se cuenta en el `+N` del texto.',
|
|
227
|
+
},
|
|
228
|
+
source: { code: DisplayedAvatarsLimitDefinition.trim() },
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const CustomStyling: Story = {
|
|
234
|
+
args: {
|
|
235
|
+
size: 'md',
|
|
236
|
+
items: [
|
|
237
|
+
{ text: 'User One', imageUrl: 'https://i.pravatar.cc/150?img=61' },
|
|
238
|
+
{ text: 'User Two', imageUrl: 'https://i.pravatar.cc/150?img=62' },
|
|
239
|
+
],
|
|
240
|
+
sx: {
|
|
241
|
+
p: 1,
|
|
242
|
+
borderRadius: 2,
|
|
243
|
+
bgcolor: 'action.hover',
|
|
244
|
+
},
|
|
245
|
+
avatarSx: {
|
|
246
|
+
border: (theme) => `3px solid ${theme.palette.primary.main}`,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
parameters: {
|
|
250
|
+
docs: {
|
|
251
|
+
description: {
|
|
252
|
+
story:
|
|
253
|
+
'Demo del passthrough de `sx` (contenedor) y `avatarSx` (cada avatar). El consumidor puede overridear bordes, colores y el layout del wrapper sin tocar el componente.',
|
|
254
|
+
},
|
|
255
|
+
source: { code: CustomStylingDefinition.trim() },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Avatar as MuiAvatar,
|
|
4
|
+
Box,
|
|
5
|
+
Tooltip,
|
|
6
|
+
Typography,
|
|
7
|
+
type SxProps,
|
|
8
|
+
type Theme,
|
|
9
|
+
} from '@mui/material';
|
|
10
|
+
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
|
11
|
+
|
|
12
|
+
export interface AvatarItem {
|
|
13
|
+
text?: string;
|
|
14
|
+
imageUrl?: string;
|
|
15
|
+
badge?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl' | number;
|
|
21
|
+
|
|
22
|
+
export interface AvatarProps {
|
|
23
|
+
type?: string;
|
|
24
|
+
items: AvatarItem[];
|
|
25
|
+
displayedAvatars?: number;
|
|
26
|
+
size?: AvatarSize;
|
|
27
|
+
showText?: boolean;
|
|
28
|
+
showTooltip?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* sx aplicado al contenedor raíz.
|
|
31
|
+
*/
|
|
32
|
+
sx?: SxProps<Theme>;
|
|
33
|
+
/**
|
|
34
|
+
* sx aplicado a cada MuiAvatar individual (se mergea sobre los defaults).
|
|
35
|
+
*/
|
|
36
|
+
avatarSx?: SxProps<Theme>;
|
|
37
|
+
className?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Overlap (px) entre avatares cuando hay varios. Default depende del tamaño.
|
|
40
|
+
*/
|
|
41
|
+
overlap?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Escala alineada con la escala de MUI (sm=32, md=40, lg=56, xl=96) con borde
|
|
45
|
+
// proporcional para el efecto stacked.
|
|
46
|
+
const sizeMap: Record<
|
|
47
|
+
Exclude<AvatarSize, number>,
|
|
48
|
+
{ px: number; border: number; font: number; overlap: number }
|
|
49
|
+
> = {
|
|
50
|
+
sm: { px: 32, border: 2, font: 14, overlap: 8 },
|
|
51
|
+
md: { px: 40, border: 2, font: 16, overlap: 10 },
|
|
52
|
+
lg: { px: 56, border: 3, font: 22, overlap: 14 },
|
|
53
|
+
xl: { px: 96, border: 4, font: 36, overlap: 20 },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const resolveSize = (size: AvatarSize) => {
|
|
57
|
+
if (typeof size === 'number') {
|
|
58
|
+
return {
|
|
59
|
+
px: size,
|
|
60
|
+
border: Math.max(2, Math.round(size * 0.05)),
|
|
61
|
+
font: Math.round(size * 0.4),
|
|
62
|
+
overlap: Math.round(size * 0.25),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return sizeMap[size];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const mergeSx = (base: SxProps<Theme>, extra?: SxProps<Theme>): SxProps<Theme> => {
|
|
69
|
+
if (!extra) return base;
|
|
70
|
+
const baseArr = Array.isArray(base) ? base : [base];
|
|
71
|
+
const extraArr = Array.isArray(extra) ? extra : [extra];
|
|
72
|
+
return [...baseArr, ...extraArr] as SxProps<Theme>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const Avatar: React.FC<AvatarProps> = ({
|
|
76
|
+
items,
|
|
77
|
+
type,
|
|
78
|
+
displayedAvatars = 4,
|
|
79
|
+
size = 'sm',
|
|
80
|
+
showText = true,
|
|
81
|
+
showTooltip = false,
|
|
82
|
+
sx,
|
|
83
|
+
avatarSx,
|
|
84
|
+
className,
|
|
85
|
+
overlap,
|
|
86
|
+
}) => {
|
|
87
|
+
const [errorIndex, setErrorIndex] = useState<Set<number>>(new Set());
|
|
88
|
+
|
|
89
|
+
const handleImageError = (index: number) => {
|
|
90
|
+
setErrorIndex((prev) => {
|
|
91
|
+
const next = new Set(prev);
|
|
92
|
+
next.add(index);
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (!items || items.length === 0) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const s = resolveSize(size);
|
|
102
|
+
const effectiveOverlap = overlap ?? s.overlap;
|
|
103
|
+
const visibleItems = items.slice(0, displayedAvatars);
|
|
104
|
+
|
|
105
|
+
const baseAvatarSx: SxProps<Theme> = {
|
|
106
|
+
width: s.px,
|
|
107
|
+
height: s.px,
|
|
108
|
+
fontSize: s.font,
|
|
109
|
+
fontWeight: 700,
|
|
110
|
+
border: (theme) => `${s.border}px solid ${theme.palette.background.paper}`,
|
|
111
|
+
boxSizing: 'content-box',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const renderSingleAvatar = (item: AvatarItem, i: number) => {
|
|
115
|
+
const hasImage = !!item.imageUrl && !errorIndex.has(i);
|
|
116
|
+
const showBadgeFallback = !!item.badge;
|
|
117
|
+
|
|
118
|
+
// Defaults tirando al theme; item.color / item.backgroundColor tienen prioridad.
|
|
119
|
+
const itemSx: SxProps<Theme> = {
|
|
120
|
+
bgcolor: item.backgroundColor ?? 'action.selected',
|
|
121
|
+
color: item.color ?? 'text.secondary',
|
|
122
|
+
// Stacking manual: margen negativo al segundo avatar en adelante.
|
|
123
|
+
...(i > 0 && { marginLeft: `-${effectiveOverlap}px` }),
|
|
124
|
+
zIndex: visibleItems.length - i,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const finalSx = mergeSx(mergeSx(baseAvatarSx, itemSx), avatarSx);
|
|
128
|
+
|
|
129
|
+
const avatarEl = (
|
|
130
|
+
<MuiAvatar
|
|
131
|
+
data-testid="avatar"
|
|
132
|
+
alt={item.text || 'User avatar'}
|
|
133
|
+
src={hasImage ? item.imageUrl : undefined}
|
|
134
|
+
imgProps={{
|
|
135
|
+
'data-testid': 'image',
|
|
136
|
+
onError: () => handleImageError(i),
|
|
137
|
+
} as React.ImgHTMLAttributes<HTMLImageElement>}
|
|
138
|
+
sx={finalSx}
|
|
139
|
+
>
|
|
140
|
+
{!hasImage && showBadgeFallback ? (
|
|
141
|
+
<span data-testid="badge" aria-label={item.text}>
|
|
142
|
+
{item.badge}
|
|
143
|
+
</span>
|
|
144
|
+
) : !hasImage ? (
|
|
145
|
+
<AccountCircleIcon
|
|
146
|
+
data-testid="icon"
|
|
147
|
+
aria-label={item.text}
|
|
148
|
+
sx={{ width: '100%', height: '100%' }}
|
|
149
|
+
/>
|
|
150
|
+
) : null}
|
|
151
|
+
</MuiAvatar>
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (showTooltip && item.text) {
|
|
155
|
+
return (
|
|
156
|
+
<Tooltip key={i} title={item.text}>
|
|
157
|
+
{avatarEl}
|
|
158
|
+
</Tooltip>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return <React.Fragment key={i}>{avatarEl}</React.Fragment>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<Box
|
|
166
|
+
data-testid="avatar-container"
|
|
167
|
+
className={className}
|
|
168
|
+
sx={mergeSx(
|
|
169
|
+
{
|
|
170
|
+
display: 'flex',
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
lineHeight: 1,
|
|
173
|
+
width: 'fit-content',
|
|
174
|
+
},
|
|
175
|
+
sx,
|
|
176
|
+
)}
|
|
177
|
+
>
|
|
178
|
+
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
179
|
+
{visibleItems.map((item, i) => renderSingleAvatar(item, i))}
|
|
180
|
+
</Box>
|
|
181
|
+
|
|
182
|
+
{showText && items[0]?.text && (
|
|
183
|
+
<Typography
|
|
184
|
+
data-testid="text"
|
|
185
|
+
variant="caption"
|
|
186
|
+
sx={{
|
|
187
|
+
ml: 1,
|
|
188
|
+
fontSize: '0.75rem',
|
|
189
|
+
fontWeight: 400,
|
|
190
|
+
color: 'text.primary',
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{type && items.length === 1 && (
|
|
194
|
+
<Box component="span" data-testid="type" sx={{ mr: 0.5 }}>
|
|
195
|
+
{type}:
|
|
196
|
+
</Box>
|
|
197
|
+
)}
|
|
198
|
+
{items[0].text}
|
|
199
|
+
{items.length > 1 && ` +${items.length - 1}`}
|
|
200
|
+
</Typography>
|
|
201
|
+
)}
|
|
202
|
+
</Box>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export default Avatar;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Avatar} from './Avatar'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const DefaultButtonDefinition = `
|
|
2
|
+
<Button variant="text">
|
|
3
|
+
Button
|
|
4
|
+
</Button>
|
|
5
|
+
`
|
|
6
|
+
|
|
7
|
+
export const OutlinedButtonDefinition = `
|
|
8
|
+
<Button variant="outlined">
|
|
9
|
+
Button
|
|
10
|
+
</Button>
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
export const ContainedButtonDefinition = `
|
|
14
|
+
<Button variant="contained">
|
|
15
|
+
Button
|
|
16
|
+
</Button>
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
export const WithStartIconButtonDefinition = `
|
|
20
|
+
<Button
|
|
21
|
+
data-testid="button"
|
|
22
|
+
startIcon={<NotificationsIcon/>}
|
|
23
|
+
variant="contained"
|
|
24
|
+
>
|
|
25
|
+
Button
|
|
26
|
+
</Button>
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
export const WithEndIconButtonDefinition = `
|
|
30
|
+
<Button
|
|
31
|
+
data-testid="button"
|
|
32
|
+
endIcon={<NotificationsIcon/>}
|
|
33
|
+
variant="contained"
|
|
34
|
+
>
|
|
35
|
+
Button
|
|
36
|
+
</Button>
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
export const WithSmallSizeButtonDefinition = `
|
|
40
|
+
<Button
|
|
41
|
+
data-testid="button"
|
|
42
|
+
size="small"
|
|
43
|
+
variant="contained"
|
|
44
|
+
>
|
|
45
|
+
Button
|
|
46
|
+
</Button>
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
export const WithLargeSizeButtonDefinition = `
|
|
50
|
+
<Button
|
|
51
|
+
data-testid="button"
|
|
52
|
+
size="large"
|
|
53
|
+
variant="contained"
|
|
54
|
+
>
|
|
55
|
+
Button
|
|
56
|
+
</Button>
|
|
57
|
+
`
|
|
58
|
+
|
|
59
|
+
export const DisabledStateButtonDefinition = `
|
|
60
|
+
<Button
|
|
61
|
+
data-testid="button"
|
|
62
|
+
disabled
|
|
63
|
+
variant="contained"
|
|
64
|
+
>
|
|
65
|
+
Button
|
|
66
|
+
</Button>`
|
|
67
|
+
|
|
68
|
+
export const LoadingButtonDefinition = `
|
|
69
|
+
<Button
|
|
70
|
+
data-testid="button"
|
|
71
|
+
loading
|
|
72
|
+
variant="contained"
|
|
73
|
+
>
|
|
74
|
+
Button
|
|
75
|
+
</Button>
|
|
76
|
+
`
|
|
77
|
+
|
|
78
|
+
export const LoadingIndicatorStartButtonDefinition = `
|
|
79
|
+
<Button
|
|
80
|
+
data-testid="button"
|
|
81
|
+
loading
|
|
82
|
+
loadingPosition="start"
|
|
83
|
+
variant="contained"
|
|
84
|
+
>
|
|
85
|
+
Button
|
|
86
|
+
</Button>
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
export const LoadingIndicatorEndButtonDefinition = `
|
|
90
|
+
<Button
|
|
91
|
+
data-testid="button"
|
|
92
|
+
loading
|
|
93
|
+
loadingPosition="end"
|
|
94
|
+
variant="contained"
|
|
95
|
+
>
|
|
96
|
+
Button
|
|
97
|
+
</Button>`
|