autocasting-ui-library-padimasso 1.2.2 → 1.2.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.
Files changed (218) hide show
  1. package/dist/components/Chevron/ChevronLeft.js +7 -0
  2. package/dist/components/Chevron/ChevronRight.js +7 -0
  3. package/dist/components/Chevron/ChevronUpDown.js +7 -0
  4. package/dist/components/Chip/Chip.js +13 -0
  5. package/dist/components/DetailsView/DetailsView.js +37 -0
  6. package/dist/components/ImageCarousel/ImageCarousel.js +35 -0
  7. package/dist/components/OverflowMenu/OverflowMenu.js +162 -0
  8. package/dist/components/PhotoZoomOverlay/PhotoZoomOverlay.js +53 -0
  9. package/dist/components/Pills/Pills.js +48 -0
  10. package/dist/components/SearchWithSuggestions/SearchWithSuggestions.js +119 -0
  11. package/dist/components/Spinner/Spinner.js +15 -0
  12. package/dist/components/Trigger/TextDropdownTrigger.js +6 -0
  13. package/dist/components/UploadTile/UploadTile.js +99 -0
  14. package/dist/components/Wizard/Wizard.js +21 -0
  15. package/dist/components/Wizard/WizardStep.js +7 -0
  16. package/dist/icons/applicants-disabled.svg.js +1 -1
  17. package/dist/icons/applicants-purple.svg.js +1 -1
  18. package/dist/icons/applicants.svg.js +1 -1
  19. package/dist/icons/arrow-long-left-purple.svg.js +1 -1
  20. package/dist/icons/arrow-long-left.svg.js +1 -1
  21. package/dist/icons/behance-purple.svg.js +1 -1
  22. package/dist/icons/behance.svg.js +1 -1
  23. package/dist/icons/burger-close-purple.svg.js +1 -1
  24. package/dist/icons/burger-close.svg.js +1 -1
  25. package/dist/icons/burger-purple.svg.js +1 -1
  26. package/dist/icons/burger.svg.js +1 -1
  27. package/dist/icons/calendar-purple.svg.js +1 -1
  28. package/dist/icons/calendar.svg.js +1 -1
  29. package/dist/icons/catalogo-purple.svg.js +1 -1
  30. package/dist/icons/catalogo.svg.js +1 -1
  31. package/dist/icons/clapper-manage-purple.svg.js +1 -1
  32. package/dist/icons/clapper-manage.svg.js +1 -1
  33. package/dist/icons/clapper-purple.svg.js +1 -1
  34. package/dist/icons/clapper.svg.js +1 -1
  35. package/dist/icons/clock-purple.svg.js +1 -1
  36. package/dist/icons/clock.svg.js +1 -1
  37. package/dist/icons/copy-link-disabled.svg.js +1 -1
  38. package/dist/icons/copy-link-purple.svg.js +1 -1
  39. package/dist/icons/copy-link.svg.js +1 -1
  40. package/dist/icons/cross-purple.svg.js +1 -1
  41. package/dist/icons/cross.svg.js +1 -1
  42. package/dist/icons/delete-red.svg.js +1 -1
  43. package/dist/icons/delete.svg.js +1 -1
  44. package/dist/icons/edit-purple.svg.js +1 -1
  45. package/dist/icons/edit.svg.js +1 -1
  46. package/dist/icons/file-purple.svg.js +1 -1
  47. package/dist/icons/file.svg.js +1 -1
  48. package/dist/icons/filter-purple.svg.js +1 -1
  49. package/dist/icons/filter.svg.js +1 -1
  50. package/dist/icons/image_placeholder.svg.js +3 -0
  51. package/dist/icons/imdb-purple.svg.js +1 -1
  52. package/dist/icons/imdb.svg.js +1 -1
  53. package/dist/icons/info-purple.svg.js +1 -1
  54. package/dist/icons/info.svg.js +1 -1
  55. package/dist/icons/instagram-purple.svg.js +1 -1
  56. package/dist/icons/instagram.svg.js +1 -1
  57. package/dist/icons/linkedin-purple.svg.js +1 -1
  58. package/dist/icons/linkedin.svg.js +1 -1
  59. package/dist/icons/location-purple.svg.js +1 -1
  60. package/dist/icons/location.svg.js +1 -1
  61. package/dist/icons/logout-red.svg.js +1 -1
  62. package/dist/icons/message-purple.svg.js +1 -1
  63. package/dist/icons/message.svg.js +1 -1
  64. package/dist/icons/og-image.svg.js +1 -1
  65. package/dist/icons/open-disabled.svg.js +1 -1
  66. package/dist/icons/open-purple.svg.js +1 -1
  67. package/dist/icons/open.svg.js +1 -1
  68. package/dist/icons/overflowmenu-purple.svg.js +1 -1
  69. package/dist/icons/overflowmenu.svg.js +1 -1
  70. package/dist/icons/play-purple.svg.js +1 -1
  71. package/dist/icons/play.svg.js +1 -1
  72. package/dist/icons/plus-purple.svg.js +1 -1
  73. package/dist/icons/plus-white.svg.js +1 -1
  74. package/dist/icons/plus.svg.js +1 -1
  75. package/dist/icons/profile-purple.svg.js +1 -1
  76. package/dist/icons/profile.svg.js +1 -1
  77. package/dist/icons/publish-disabled.svg.js +1 -1
  78. package/dist/icons/publish-purple.svg.js +1 -1
  79. package/dist/icons/publish.svg.js +1 -1
  80. package/dist/icons/save-purple.svg.js +1 -1
  81. package/dist/icons/save.svg.js +1 -1
  82. package/dist/icons/search-disabled.svg.js +1 -1
  83. package/dist/icons/search-purple.svg.js +1 -1
  84. package/dist/icons/search.svg.js +1 -1
  85. package/dist/icons/settings-purple.svg.js +1 -1
  86. package/dist/icons/settings.svg.js +1 -1
  87. package/dist/icons/switcher-purple.svg.js +1 -1
  88. package/dist/icons/switcher.svg.js +1 -1
  89. package/dist/icons/tick-2-purple.svg.js +1 -1
  90. package/dist/icons/tick-2.svg.js +1 -1
  91. package/dist/icons/tikTok.svg.js +1 -1
  92. package/dist/icons/view-purple.svg.js +1 -1
  93. package/dist/icons/view.svg.js +1 -1
  94. package/dist/icons/vimeo-purple.svg.js +1 -1
  95. package/dist/icons/vimeo.svg.js +1 -1
  96. package/dist/icons/whatsapp-purple.svg.js +1 -1
  97. package/dist/icons/whatsapp.svg.js +1 -1
  98. package/dist/icons/x-purple.svg.js +1 -1
  99. package/dist/icons/x.svg.js +1 -1
  100. package/dist/index.d.ts +191 -7
  101. package/dist/index.js +15 -0
  102. package/dist/styles.css +1 -1
  103. package/package.json +1 -1
  104. package/rollup.config.mjs +3 -8
  105. package/src/components/Button/index.ts +1 -0
  106. package/src/components/Chevron/ChevronLeft.tsx +9 -0
  107. package/src/components/Chevron/ChevronRight.tsx +9 -0
  108. package/src/components/Chevron/ChevronUpDown.tsx +22 -0
  109. package/src/components/Chevron/index.ts +5 -0
  110. package/src/components/Chip/Chip.tsx +23 -0
  111. package/src/components/Chip/index.ts +3 -0
  112. package/src/components/DetailsView/DetailsView.tsx +81 -0
  113. package/src/components/DetailsView/index.ts +3 -0
  114. package/src/components/Icon/index.ts +1 -0
  115. package/src/components/ImageCarousel/ImageCarousel.tsx +97 -0
  116. package/src/components/ImageCarousel/index.ts +3 -0
  117. package/src/components/OverflowMenu/OverflowMenu.tsx +267 -0
  118. package/src/components/OverflowMenu/index.ts +4 -0
  119. package/src/components/OverflowMenu/overflowmenu.types.ts +26 -0
  120. package/src/components/PhotoZoomOverlay/PhotoZoomOverlay.tsx +108 -0
  121. package/src/components/PhotoZoomOverlay/index.ts +3 -0
  122. package/src/components/Pills/Pills.tsx +98 -0
  123. package/src/components/Pills/index.ts +3 -0
  124. package/src/components/SearchWithSuggestions/SearchWithSuggestions.tsx +178 -0
  125. package/src/components/SearchWithSuggestions/index.ts +2 -0
  126. package/src/components/Spinner/Spinner.tsx +51 -0
  127. package/src/components/Spinner/index.ts +3 -0
  128. package/src/components/Trigger/TextDropdownTrigger.tsx +12 -0
  129. package/src/components/Trigger/index.ts +3 -0
  130. package/src/components/UploadTile/UploadTile.tsx +303 -0
  131. package/src/components/UploadTile/index.ts +3 -0
  132. package/src/components/Wizard/Wizard.tsx +35 -0
  133. package/src/components/Wizard/WizardStep.tsx +19 -0
  134. package/src/components/Wizard/index.ts +5 -0
  135. package/src/index.ts +12 -0
  136. package/dist/assets/applicants-disabled.svg +0 -4
  137. package/dist/assets/applicants-purple.svg +0 -4
  138. package/dist/assets/applicants.svg +0 -4
  139. package/dist/assets/arrow-long-left-purple.svg +0 -3
  140. package/dist/assets/arrow-long-left.svg +0 -3
  141. package/dist/assets/behance-purple.svg +0 -3
  142. package/dist/assets/behance.svg +0 -3
  143. package/dist/assets/burger-close-purple.svg +0 -3
  144. package/dist/assets/burger-close.svg +0 -3
  145. package/dist/assets/burger-purple.svg +0 -3
  146. package/dist/assets/burger.svg +0 -3
  147. package/dist/assets/calendar-purple.svg +0 -6
  148. package/dist/assets/calendar.svg +0 -6
  149. package/dist/assets/catalogo-purple.svg +0 -3
  150. package/dist/assets/catalogo.svg +0 -3
  151. package/dist/assets/clapper-manage-purple.svg +0 -5
  152. package/dist/assets/clapper-manage.svg +0 -5
  153. package/dist/assets/clapper-purple.svg +0 -3
  154. package/dist/assets/clapper.svg +0 -3
  155. package/dist/assets/clock-purple.svg +0 -4
  156. package/dist/assets/clock.svg +0 -4
  157. package/dist/assets/copy-link-disabled.svg +0 -3
  158. package/dist/assets/copy-link-purple.svg +0 -3
  159. package/dist/assets/copy-link.svg +0 -3
  160. package/dist/assets/cross-purple.svg +0 -3
  161. package/dist/assets/cross.svg +0 -3
  162. package/dist/assets/delete-red.svg +0 -3
  163. package/dist/assets/delete.svg +0 -3
  164. package/dist/assets/edit-purple.svg +0 -3
  165. package/dist/assets/edit.svg +0 -3
  166. package/dist/assets/file-purple.svg +0 -3
  167. package/dist/assets/file.svg +0 -3
  168. package/dist/assets/filter-purple.svg +0 -3
  169. package/dist/assets/filter.svg +0 -3
  170. package/dist/assets/imdb-purple.svg +0 -3
  171. package/dist/assets/imdb.svg +0 -3
  172. package/dist/assets/info-purple.svg +0 -3
  173. package/dist/assets/info.svg +0 -3
  174. package/dist/assets/instagram-purple.svg +0 -3
  175. package/dist/assets/instagram.svg +0 -3
  176. package/dist/assets/linkedin-purple.svg +0 -3
  177. package/dist/assets/linkedin.svg +0 -3
  178. package/dist/assets/location-purple.svg +0 -4
  179. package/dist/assets/location.svg +0 -4
  180. package/dist/assets/logout-red.svg +0 -3
  181. package/dist/assets/message-purple.svg +0 -3
  182. package/dist/assets/message.svg +0 -3
  183. package/dist/assets/og-image.svg +0 -9
  184. package/dist/assets/open-disabled.svg +0 -3
  185. package/dist/assets/open-purple.svg +0 -3
  186. package/dist/assets/open.svg +0 -3
  187. package/dist/assets/overflowmenu-purple.svg +0 -3
  188. package/dist/assets/overflowmenu.svg +0 -3
  189. package/dist/assets/play-purple.svg +0 -3
  190. package/dist/assets/play.svg +0 -3
  191. package/dist/assets/plus-purple.svg +0 -3
  192. package/dist/assets/plus-white.svg +0 -3
  193. package/dist/assets/plus.svg +0 -3
  194. package/dist/assets/profile-purple.svg +0 -3
  195. package/dist/assets/profile.svg +0 -3
  196. package/dist/assets/publish-disabled.svg +0 -3
  197. package/dist/assets/publish-purple.svg +0 -3
  198. package/dist/assets/publish.svg +0 -3
  199. package/dist/assets/save-purple.svg +0 -3
  200. package/dist/assets/save.svg +0 -3
  201. package/dist/assets/search-disabled.svg +0 -3
  202. package/dist/assets/search-purple.svg +0 -3
  203. package/dist/assets/search.svg +0 -3
  204. package/dist/assets/settings-purple.svg +0 -3
  205. package/dist/assets/settings.svg +0 -3
  206. package/dist/assets/switcher-purple.svg +0 -3
  207. package/dist/assets/switcher.svg +0 -3
  208. package/dist/assets/tick-2-purple.svg +0 -4
  209. package/dist/assets/tick-2.svg +0 -4
  210. package/dist/assets/tikTok.svg +0 -3
  211. package/dist/assets/view-purple.svg +0 -3
  212. package/dist/assets/view.svg +0 -3
  213. package/dist/assets/vimeo-purple.svg +0 -3
  214. package/dist/assets/vimeo.svg +0 -3
  215. package/dist/assets/whatsapp-purple.svg +0 -3
  216. package/dist/assets/whatsapp.svg +0 -3
  217. package/dist/assets/x-purple.svg +0 -10
  218. package/dist/assets/x.svg +0 -10
@@ -0,0 +1,7 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+
3
+ const ChevronLeft = () => {
4
+ return (jsx("svg", { width: "30", height: "30", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", className: "cursor-pointer", children: jsx("path", { d: "M15 6l-6 6 6 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }));
5
+ };
6
+
7
+ export { ChevronLeft as default };
@@ -0,0 +1,7 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+
3
+ const ChevronRight = () => {
4
+ return (jsx("svg", { width: "30", height: "30", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", className: "cursor-pointer", children: jsx("path", { d: "M9 6l6 6-6 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }));
5
+ };
6
+
7
+ export { ChevronRight as default };
@@ -0,0 +1,7 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+
3
+ const ChevronUpDown = ({ open, sizePx = 28, className }) => {
4
+ return (jsx("svg", { width: sizePx, height: sizePx, className: ['transition-transform', open ? 'rotate-180' : '', className ?? ''].filter(Boolean).join(' '), viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: jsx("path", { d: "M6 9l6 6 6-6", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" }) }));
5
+ };
6
+
7
+ export { ChevronUpDown as default };
@@ -0,0 +1,13 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Icon } from '../Icon/Icon.js';
3
+
4
+ function Chip({ label, onRemove, newItem = false }) {
5
+ return (jsxs("span", { className: [
6
+ 'flex items-center gap-[6px] rounded-xl border px-3 py-1 lg:text-[14px]',
7
+ newItem
8
+ ? 'font-semibold bg-[var(--color-secondary-white)] border-[var(--color-primary-purple)] text-[var(--color-primary-purple)]'
9
+ : 'bg-[var(--color-primary-white)] border-[var(--color-secondary-outline)] text-[var(--color-primary-black)]',
10
+ ].join(' '), children: [jsx("p", { className: "text-sm", children: label }), onRemove && jsx(Icon, { name: "cross", variant: "primary", size: 11, onClick: onRemove })] }));
11
+ }
12
+
13
+ export { Chip as default };
@@ -0,0 +1,37 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import clsx from 'clsx';
3
+ import { useRef, useEffect } from 'react';
4
+ import { Icon } from '../Icon/Icon.js';
5
+
6
+ function DetailsView({ open, onClose, headerLeft: navigation, headerRight, children, className, }) {
7
+ const bodyRef = useRef(null);
8
+ useEffect(() => {
9
+ if (!open)
10
+ return;
11
+ const prevOverflow = document.body.style.overflow;
12
+ document.body.style.overflow = 'hidden';
13
+ // 1) Panel: siempre arrancar desde arriba
14
+ if (bodyRef.current) {
15
+ bodyRef.current.scrollTop = 0;
16
+ bodyRef.current.scrollLeft = 0;
17
+ }
18
+ // 2) Root de la app (por si también querés que la pantalla general esté en top)
19
+ const root = document.querySelector('#app-scroll-root');
20
+ if (root) {
21
+ root.scrollTop = 0;
22
+ root.scrollLeft = 0;
23
+ }
24
+ else {
25
+ // fallback
26
+ window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
27
+ }
28
+ return () => {
29
+ document.body.style.overflow = prevOverflow;
30
+ };
31
+ }, [open]);
32
+ if (!open)
33
+ return null;
34
+ return (jsxs("div", { className: "fixed inset-0 z-[100] flex justify-end items-stretch", children: [jsx("button", { type: "button", "aria-label": "Cerrar", onClick: onClose, className: "absolute inset-0 bg-black/60" }), jsxs("aside", { className: clsx('relative ml-auto h-full w-full max-w-[550px] bg-[var(--color-secondary-white)] shadow-xl flex flex-col', className), children: [jsxs("header", { className: "flex items-center justify-between p-5 border-b border-[var(--color-secondary-outline)] bg-[var(--color-primary-white)]", children: [navigation, jsxs("div", { className: "flex items-center gap-4", children: [headerRight, jsx(Icon, { name: "burgerClose", onClick: onClose })] })] }), jsx("div", { ref: bodyRef, className: "flex-1 min-h-0 overflow-auto px-8 py-10", children: children })] })] }));
35
+ }
36
+
37
+ export { DetailsView as default };
@@ -0,0 +1,35 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useMemo, useEffect } from 'react';
3
+ import PLACEHOLDER_SVG from '../../icons/image_placeholder.svg.js';
4
+
5
+ function ImageCarousel({ images, className, isDesktop, isDesktopXL }) {
6
+ const [selectedIndex, setSelectedIndex] = useState(0);
7
+ // Hasta 4 slots, rellenando con placeholder
8
+ const finalImages = useMemo(() => {
9
+ const MAX_SLOTS = 4;
10
+ const base = (images ?? []).filter((src) => typeof src === 'string' && src.trim().length > 0).slice(0, MAX_SLOTS);
11
+ const missing = MAX_SLOTS - base.length;
12
+ if (missing <= 0)
13
+ return base;
14
+ return [...base, ...Array.from({ length: missing }, () => PLACEHOLDER_SVG)];
15
+ }, [images]);
16
+ useEffect(() => {
17
+ if (selectedIndex >= finalImages.length)
18
+ setSelectedIndex(0);
19
+ }, [finalImages.length, selectedIndex]);
20
+ const selectedImage = finalImages[selectedIndex];
21
+ const rightThumbIndices = useMemo(() => finalImages.map((_, i) => i).slice(1, 4), [finalImages]);
22
+ const desktopLayout = !!isDesktop || !!isDesktopXL;
23
+ const showDesktopThumbs = desktopLayout && finalImages.length > 1;
24
+ const gridCols = showDesktopThumbs ? 'grid-cols-[1fr_130px]' : 'grid-cols-1';
25
+ const figureClass = desktopLayout ? 'w-full h-full' : 'w-full aspect-[8/10]';
26
+ const wrapperClass = desktopLayout
27
+ ? `grid ${gridCols} gap-3 h-full min-h-0 items-stretch`
28
+ : 'flex flex-col gap-3 w-full';
29
+ return (jsx("div", { className: `w-full ${desktopLayout ? 'h-full min-h-0' : 'h-auto'} ${className ?? ''}`, children: jsxs("div", { className: wrapperClass, children: [jsxs("div", { className: desktopLayout ? 'col-[1] h-full min-h-0 flex flex-col' : 'flex flex-col gap-2', children: [jsx("figure", { className: `relative overflow-hidden rounded-xl ${figureClass}`, children: jsx("img", { src: selectedImage, alt: `Imagen ${selectedIndex + 1}`, className: "absolute inset-0 w-full h-full object-cover" }) }), !desktopLayout && finalImages.length > 1 && (jsx("div", { className: "flex gap-2 overflow-x-auto w-full", children: finalImages.slice(1).map((img, i) => (jsx("button", { type: "button", "aria-label": `Ver imagen ${i + 1}`, className: "w-[120px] aspect-[8/10] flex-shrink-0 rounded-lg overflow-hidden border-2 border-transparent", children: jsx("img", { src: img, alt: `Miniatura ${i + 1}`, className: "w-full h-full object-cover" }) }, `thumb-m-${i}`))) }))] }), showDesktopThumbs && (jsx("div", { className: "col-[2] h-full min-h-0 flex flex-col gap-2 items-stretch justify-center", style: { ['--g']: '12px' }, children: rightThumbIndices.map((idx) => {
30
+ const img = finalImages[idx];
31
+ return (jsx("button", { type: "button", "aria-label": `Ver imagen ${idx + 1}`, style: { height: 'calc((100% - 2*var(--g)) / 3)' }, className: "relative w-full aspect-[4/5] rounded-xl overflow-hidden border-2 border-transparent flex-shrink-0", children: jsx("img", { src: img, alt: `Miniatura ${idx + 1}`, className: "absolute inset-0 w-full h-full object-cover" }) }, `thumb-d-${idx}`));
32
+ }) }))] }) }));
33
+ }
34
+
35
+ export { ImageCarousel as default };
@@ -0,0 +1,162 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Icon } from '../Icon/Icon.js';
5
+
6
+ function cx(...xs) {
7
+ return xs.filter(Boolean).join(' ');
8
+ }
9
+ const OverflowMenu = ({ items, align = 'end', side = 'bottom', offset = 8, triggerClassName, menuClassName, itemClassName, disabled = false, trigger, }) => {
10
+ const [open, setOpen] = useState(false);
11
+ const triggerRef = useRef(null);
12
+ const menuRef = useRef(null);
13
+ const [anchor, setAnchor] = useState(null);
14
+ const [hoverKey, setHoverKey] = useState(null);
15
+ const visibleItems = useMemo(() => (items ?? []).filter((it) => it?.hidden !== true), [items]);
16
+ const close = useCallback(() => setOpen(false), []);
17
+ const toggleOpen = useCallback(() => {
18
+ if (disabled)
19
+ return;
20
+ setOpen((v) => !v);
21
+ }, [disabled]);
22
+ const measure = useCallback(() => {
23
+ const el = triggerRef.current;
24
+ if (!el)
25
+ return;
26
+ const r = el.getBoundingClientRect();
27
+ if (side === 'bottom') {
28
+ setAnchor({
29
+ x: align === 'end' ? r.right : r.left,
30
+ y: r.bottom + offset,
31
+ });
32
+ }
33
+ else {
34
+ setAnchor({
35
+ x: align === 'end' ? r.right : r.left,
36
+ y: r.top - offset,
37
+ });
38
+ }
39
+ }, [align, side, offset]);
40
+ useEffect(() => {
41
+ if (!open)
42
+ return;
43
+ measure();
44
+ }, [open, measure]);
45
+ useEffect(() => {
46
+ if (!open)
47
+ return;
48
+ const onResize = () => close();
49
+ const onScroll = (e) => {
50
+ const target = e.target;
51
+ if (!target)
52
+ return;
53
+ if (menuRef.current?.contains(target))
54
+ return;
55
+ if (triggerRef.current?.contains(target))
56
+ return;
57
+ close();
58
+ };
59
+ window.addEventListener('resize', onResize);
60
+ window.addEventListener('scroll', onScroll, true);
61
+ return () => {
62
+ window.removeEventListener('resize', onResize);
63
+ window.removeEventListener('scroll', onScroll, true);
64
+ };
65
+ }, [open, close]);
66
+ useEffect(() => {
67
+ if (!open)
68
+ return;
69
+ const onKey = (e) => {
70
+ if (e.key === 'Escape')
71
+ close();
72
+ };
73
+ const onPointerDown = (e) => {
74
+ const t = e.target;
75
+ if (!t)
76
+ return;
77
+ const inTrigger = !!triggerRef.current?.contains(t);
78
+ const inMenu = !!menuRef.current?.contains(t);
79
+ if (!inTrigger && !inMenu)
80
+ close();
81
+ };
82
+ document.addEventListener('keydown', onKey);
83
+ document.addEventListener('mousedown', onPointerDown, true);
84
+ document.addEventListener('touchstart', onPointerDown, true);
85
+ return () => {
86
+ document.removeEventListener('keydown', onKey);
87
+ document.removeEventListener('mousedown', onPointerDown, true);
88
+ document.removeEventListener('touchstart', onPointerDown, true);
89
+ };
90
+ }, [open, close]);
91
+ useEffect(() => {
92
+ if (!open)
93
+ return;
94
+ const onFocusIn = (e) => {
95
+ const t = e.target;
96
+ if (!t)
97
+ return;
98
+ const inTrigger = !!triggerRef.current?.contains(t);
99
+ const inMenu = !!menuRef.current?.contains(t);
100
+ if (!inTrigger && !inMenu)
101
+ close();
102
+ };
103
+ document.addEventListener('focusin', onFocusIn, true);
104
+ return () => {
105
+ document.removeEventListener('focusin', onFocusIn, true);
106
+ };
107
+ }, [open, close]);
108
+ const onSelectItem = async (it) => {
109
+ if (it.type === 'separator' || it.type === 'content')
110
+ return;
111
+ if (it.disabled)
112
+ return;
113
+ try {
114
+ await it.onSelect?.();
115
+ }
116
+ finally {
117
+ const closeOnSelect = it.closeOnSelect ?? true;
118
+ if (closeOnSelect)
119
+ close();
120
+ }
121
+ };
122
+ const MenuRenderer = !open || !anchor
123
+ ? null
124
+ : createPortal(jsx("div", { ref: menuRef, role: "menu", className: cx('fixed z-[999] min-w-[210px] rounded-2xl shadow-lg', 'bg-[var(--color-primary-white)] border border-[var(--color-secondary-outline)]', 'p-2', menuClassName), style: {
125
+ left: anchor.x,
126
+ top: anchor.y,
127
+ transform: align === 'end'
128
+ ? side === 'bottom'
129
+ ? 'translateX(-100%) translateY(0)'
130
+ : 'translateX(-100%) translateY(-100%)'
131
+ : side === 'bottom'
132
+ ? 'translateX(0) translateY(0)'
133
+ : 'translateX(0) translateY(-100%)',
134
+ }, children: visibleItems.map((it) => {
135
+ if (it.type === 'separator') {
136
+ return (jsx("div", { className: "my-2 h-px w-full bg-[var(--color-secondary-outline)] opacity-60" }, it.key));
137
+ }
138
+ if (it.type === 'content') {
139
+ return (jsx("div", { className: "p-2", children: it.content }, it.key));
140
+ }
141
+ const isDestructive = !!it.destructive;
142
+ const isDisabled = !!it.disabled;
143
+ const iconName = it.iconName;
144
+ const forcedIconVariant = it.iconVariant;
145
+ const resolvedIconVariant = forcedIconVariant ||
146
+ (isDisabled
147
+ ? 'disabled'
148
+ : isDestructive
149
+ ? 'danger'
150
+ : hoverKey === it.key
151
+ ? 'primary'
152
+ : 'default');
153
+ const labelNode = it.label;
154
+ const isStringLabel = typeof labelNode === 'string';
155
+ return (jsxs("button", { type: "button", role: "menuitem", "data-menuitem": "true", disabled: isDisabled, onClick: () => void onSelectItem(it), onMouseEnter: () => setHoverKey(it.key), onMouseLeave: () => setHoverKey(null), className: cx('w-full text-left px-3 py-2 rounded-xl', 'flex items-center gap-3', 'text-sm font-normal', 'cursor-pointer disabled:cursor-not-allowed', isDestructive
156
+ ? 'text-[var(--color-alert-error)] hover:bg-red-50'
157
+ : 'text-[var(--color-primary-black)] hover:text-[var(--color-primary-purple)] hover:bg-[var(--color-secondary-white)]', 'transition-colors', 'disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-inherit', itemClassName), children: [iconName && jsx(Icon, { name: iconName, variant: resolvedIconVariant }), isStringLabel ? jsx("span", { className: "select-none", children: labelNode }) : labelNode] }, it.key));
158
+ }) }), document.body);
159
+ return (jsxs(Fragment, { children: [jsx("button", { ref: triggerRef, type: "button", onClick: toggleOpen, "aria-haspopup": "menu", "aria-expanded": open, disabled: disabled, className: cx('disabled:opacity-50 disabled:cursor-not-allowed', triggerClassName), children: trigger ? trigger({ open, disabled }) : jsx(Icon, { name: "overflowmenu", variant: "default" }) }), MenuRenderer] }));
160
+ };
161
+
162
+ export { OverflowMenu as default };
@@ -0,0 +1,53 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useState, useEffect, useMemo, useCallback } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import ChevronLeft from '../Chevron/ChevronLeft.js';
5
+ import ChevronRight from '../Chevron/ChevronRight.js';
6
+
7
+ function PhotoZoomOverlay({ open, images, initialIndex = 0, onClose, onIndexChange }) {
8
+ const [idx, setIdx] = useState(initialIndex);
9
+ useEffect(() => {
10
+ if (open)
11
+ setIdx(initialIndex);
12
+ }, [open, initialIndex]);
13
+ const total = images.length;
14
+ const current = useMemo(() => images[idx] ?? images[0], [images, idx]);
15
+ const hasArrows = total > 1;
16
+ const prev = useCallback(() => setIdx((i) => (i - 1 + total) % total), [total]);
17
+ const next = useCallback(() => setIdx((i) => (i + 1) % total), [total]);
18
+ useEffect(() => {
19
+ if (!open)
20
+ return;
21
+ const prevOverflow = document.body.style.overflow;
22
+ document.body.style.overflow = 'hidden';
23
+ const onKey = (e) => {
24
+ if (e.key === 'Escape')
25
+ onClose();
26
+ if (e.key === 'ArrowLeft')
27
+ prev();
28
+ if (e.key === 'ArrowRight')
29
+ next();
30
+ };
31
+ window.addEventListener('keydown', onKey);
32
+ return () => {
33
+ document.body.style.overflow = prevOverflow;
34
+ window.removeEventListener('keydown', onKey);
35
+ };
36
+ }, [open, prev, next, onClose]);
37
+ useEffect(() => {
38
+ onIndexChange?.(idx);
39
+ }, [idx, onIndexChange]);
40
+ if (!open)
41
+ return null;
42
+ return createPortal(jsxs("div", { className: "fixed inset-0 z-[1000]", role: "dialog", "aria-modal": "true", onClick: onClose, children: [jsx("div", { className: "absolute inset-0 bg-black/80" }), jsx("div", { className: "relative z-10 flex items-center justify-center h-full px-4", children: jsxs("div", { className: "relative w-[90%] h-[90%] max-w-[600px] max-h-[700px] m-auto rounded-3xl", onClick: (e) => {
43
+ e.stopPropagation();
44
+ }, children: [jsx("button", { type: "button", "aria-label": "Cerrar", onClick: onClose, className: " cursor-pointer absolute z-10 right-3 top-3 w-9 h-9 grid place-items-center rounded-full bg-black/50 text-white", children: jsx("span", { className: "text-2xl leading-none mb-[10%]", children: "\u00D7" }) }), jsxs("figure", { className: "relative w-full h-full overflow-hidden rounded-2xl", children: [jsx("img", { src: current, alt: `Foto ${idx + 1} de ${total}`, className: "absolute inset-0 w-full h-full object-cover select-none", draggable: false }), hasArrows && (jsxs(Fragment, { children: [jsx("button", { type: "button", onClick: (e) => {
45
+ e.stopPropagation();
46
+ prev();
47
+ }, "aria-label": "Foto anterior", className: "absolute left-5 top-1/2 -translate-y-1/2 grid place-items-center w-8 h-8 rounded-full bg-white/95 hover:bg-white", children: jsx(ChevronLeft, {}) }), jsx("button", { type: "button", onClick: (e) => {
48
+ e.stopPropagation();
49
+ next();
50
+ }, "aria-label": "Foto siguiente", className: "absolute right-5 top-1/2 -translate-y-1/2 grid place-items-center w-8 h-8 rounded-full bg-white/95 hover:bg-white", children: jsx(ChevronRight, {}) })] }))] })] }) })] }), document.body);
51
+ }
52
+
53
+ export { PhotoZoomOverlay as default };
@@ -0,0 +1,48 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { clsx } from 'clsx';
3
+ import { useRef, useMemo, useEffect } from 'react';
4
+
5
+ function Pills({ items, value, onChange, className, listClassName, }) {
6
+ const listRef = useRef(null);
7
+ const btnRefs = useRef({});
8
+ const idx = useMemo(() => items.findIndex((i) => i.key === value), [items, value]);
9
+ const clamp = (n) => Math.max(0, Math.min(items.length - 1, n));
10
+ useEffect(() => {
11
+ const el = btnRefs.current[String(value)];
12
+ el?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
13
+ }, [value]);
14
+ const onKeyDown = (e) => {
15
+ if (!items.length)
16
+ return;
17
+ if (e.key === 'ArrowRight') {
18
+ const next = clamp(idx + 1);
19
+ onChange(items[next].key);
20
+ e.preventDefault();
21
+ }
22
+ if (e.key === 'ArrowLeft') {
23
+ const prev = clamp(idx - 1);
24
+ onChange(items[prev].key);
25
+ e.preventDefault();
26
+ }
27
+ if (e.key === 'Home') {
28
+ onChange(items[0].key);
29
+ e.preventDefault();
30
+ }
31
+ if (e.key === 'End') {
32
+ onChange(items[items.length - 1].key);
33
+ e.preventDefault();
34
+ }
35
+ };
36
+ return (jsx("div", { className: clsx('relative', className), children: jsx("div", { className: "flex items-center gap-2", children: jsx("div", { ref: listRef, role: "tablist", "aria-label": "pills",
37
+ // TODO Handle scrollbar hide on ""drag and scroll"" + Fade de lado derecho
38
+ className: clsx('flex-1 flex items-center gap-2 overflow-x-auto snap-x', listClassName), tabIndex: 0, onKeyDown: onKeyDown, children: items.map(({ key, label }) => {
39
+ const selected = key === value;
40
+ return (jsx("button", { ref: (el) => {
41
+ btnRefs.current[String(key)] = el;
42
+ }, role: "tab", "aria-selected": selected, "aria-controls": `panel-${String(key)}`, id: `tab-${String(key)}`, onClick: () => onChange(key), className: clsx('bg-[var(--color-primary-white)] text-base border-1 lg:text-[14px] font-semibold cursor-pointer px-4 py-1', 'snap-start whitespace-nowrap rounded-full', selected
43
+ ? 'text-[var(--color-primary-purple)] border-[var(--color-primary-purple)]'
44
+ : 'text-[var(--color-secondary-grey-fonts)]'), children: label }, String(key)));
45
+ }) }) }) }));
46
+ }
47
+
48
+ export { Pills as default };
@@ -0,0 +1,119 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useRef, useMemo, useEffect } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+
5
+ function SearchWithSuggestions({ label, placeholder, suggestions, onSelect, }) {
6
+ const [query, setQuery] = useState('');
7
+ const [open, setOpen] = useState(false);
8
+ const [active, setActive] = useState(0);
9
+ const wrapRef = useRef(null);
10
+ const inputRef = useRef(null);
11
+ const menuRef = useRef(null);
12
+ // Posición del menú en viewport coords
13
+ const [menuPos, setMenuPos] = useState(null);
14
+ // Solo buscar por la parte de la skill (después de ":")
15
+ const skillText = (s) => {
16
+ const parts = s.text.split(':');
17
+ return (parts.length > 1 ? parts.slice(1).join(':') : s.text).trim();
18
+ };
19
+ const filtered = useMemo(() => {
20
+ const q = query.trim().toLowerCase();
21
+ if (!q)
22
+ return [];
23
+ return suggestions.filter((s) => skillText(s).toLowerCase().includes(q)).slice(0, 12);
24
+ }, [query, suggestions]);
25
+ const updateMenuPos = () => {
26
+ const el = inputRef.current;
27
+ if (!el)
28
+ return;
29
+ const rect = el.getBoundingClientRect();
30
+ const width = rect.width;
31
+ const gutter = 8;
32
+ const viewportH = window.innerHeight;
33
+ // Altura máxima (no “choca” el borde inferior del modal/viewport)
34
+ const maxH = 240; // ~max-h-60
35
+ let top = rect.bottom + gutter;
36
+ // Si no entra abajo, lo “flip” hacia arriba
37
+ if (top + maxH > viewportH - gutter) {
38
+ top = Math.max(gutter, rect.top - maxH - gutter);
39
+ }
40
+ setMenuPos({ top, left: rect.left, width });
41
+ };
42
+ // Abrimos/cerramos según query y actualizamos posición
43
+ useEffect(() => {
44
+ const hasQuery = query.trim().length > 0;
45
+ setOpen(hasQuery);
46
+ if (hasQuery)
47
+ updateMenuPos();
48
+ }, [query]);
49
+ // Click-away que contempla también el portal
50
+ useEffect(() => {
51
+ const onClickAway = (e) => {
52
+ const target = e.target;
53
+ if (wrapRef.current?.contains(target))
54
+ return;
55
+ if (menuRef.current?.contains(target))
56
+ return;
57
+ setOpen(false);
58
+ };
59
+ window.addEventListener('mousedown', onClickAway);
60
+ return () => window.removeEventListener('mousedown', onClickAway);
61
+ }, []);
62
+ // Reposicionar en scroll/resize
63
+ useEffect(() => {
64
+ const onScrollOrResize = () => {
65
+ if (open)
66
+ updateMenuPos();
67
+ };
68
+ window.addEventListener('scroll', onScrollOrResize, true);
69
+ window.addEventListener('resize', onScrollOrResize);
70
+ return () => {
71
+ window.removeEventListener('scroll', onScrollOrResize, true);
72
+ window.removeEventListener('resize', onScrollOrResize);
73
+ };
74
+ }, [open]);
75
+ const onKeyDown = (e) => {
76
+ if (!open || !filtered.length)
77
+ return;
78
+ if (e.key === 'ArrowDown') {
79
+ e.preventDefault();
80
+ setActive((i) => Math.min(i + 1, filtered.length - 1));
81
+ }
82
+ else if (e.key === 'ArrowUp') {
83
+ e.preventDefault();
84
+ setActive((i) => Math.max(i - 1, 0));
85
+ }
86
+ else if (e.key === 'Enter') {
87
+ e.preventDefault();
88
+ const pick = filtered[active];
89
+ if (pick) {
90
+ onSelect(pick.id);
91
+ setQuery('');
92
+ setOpen(false);
93
+ document.activeElement?.blur?.();
94
+ }
95
+ }
96
+ else if (e.key === 'Escape') {
97
+ setOpen(false);
98
+ document.activeElement?.blur?.();
99
+ }
100
+ };
101
+ const dropdown = open && menuPos
102
+ ? createPortal(jsx("div", { ref: menuRef, style: {
103
+ position: 'fixed',
104
+ top: menuPos.top,
105
+ left: menuPos.left,
106
+ width: menuPos.width,
107
+ maxHeight: 310,
108
+ overflow: 'auto',
109
+ zIndex: 2147483647,
110
+ }, className: "\n rounded-lg border border-[var(--color-secondary-outline)]\n bg-white shadow\n ", children: filtered.map((s, i) => (jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => {
111
+ onSelect(s.id);
112
+ setOpen(false);
113
+ setQuery('');
114
+ }, onMouseEnter: () => setActive(i), className: `w-full text-left px-4 py-3 cursor-pointer ${i === active ? 'bg-[var(--color-primary-light-grey)]' : ''}`, children: s.text }, s.id))) }), document.body)
115
+ : null;
116
+ return (jsxs("div", { className: "flex flex-col gap-2", ref: wrapRef, children: [jsx("label", { className: "font-bold", htmlFor: "skill-search", children: label }), jsxs("div", { className: "relative", children: [jsx("input", { id: "skill-search", ref: inputRef, value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: onKeyDown, placeholder: placeholder, className: "w-full h-14 px-5 py-3 rounded-xl text-base placeholder:text-[var(--color-secondary-grey)] placeholder:font-normal placeholder:text-base border border-[var(--color-secondary-outline)] focus:outline-none focus:ring-0 focus:border-[var(--color-primary-black)] disabled:bg-[var(--color-secondary-offwhite)] disabled:cursor-not-allowed transition-colors" }), dropdown] })] }));
117
+ }
118
+
119
+ export { SearchWithSuggestions as default };
@@ -0,0 +1,15 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+
3
+ function Spinner({ color, strokeWidth = 2, className = 'h-9 w-9', ariaLabel = 'Loading...', arc = 0.65, speedMs = 1300, }) {
4
+ const style = {
5
+ ...(color ? { color } : {}),
6
+ animationDuration: `${speedMs}ms`,
7
+ };
8
+ const r = 9;
9
+ const C = 2 * Math.PI * r;
10
+ const visible = Math.max(0, Math.min(1, arc)) * C;
11
+ const gap = C - visible;
12
+ return (jsxs("span", { role: "status", "aria-live": "polite", "aria-label": ariaLabel, className: `inline-flex items-center justify-center ${className}`, style: style, children: [jsx("svg", { className: "animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true", children: jsx("circle", { cx: "12", cy: "12", r: r, stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeDasharray: `${visible} ${gap}`, strokeDashoffset: C * 0.25 }) }), jsx("span", { className: "sr-only", children: ariaLabel })] }));
13
+ }
14
+
15
+ export { Spinner as default };
@@ -0,0 +1,6 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import ChevronUpDown from '../Chevron/ChevronUpDown.js';
3
+
4
+ const TextDropdownTrigger = ({ label, open }) => (jsxs("span", { className: "flex items-center gap-1 cursor-pointer select-none", children: [jsx("span", { className: "underline font-normal text-sm", children: label }), jsx("span", { className: open ? 'rotate-180 transition-transform' : 'transition-transform', children: jsx(ChevronUpDown, { open: open, sizePx: 18 }) })] }));
5
+
6
+ export { TextDropdownTrigger as default };
@@ -0,0 +1,99 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import clsx from 'clsx';
3
+ import { forwardRef, useRef, useState, useCallback } from 'react';
4
+ import { Icon } from '../Icon/Icon.js';
5
+
6
+ const DEFAULT_ROUNDED = 'rounded-xl';
7
+ function withinAccept(file, accept) {
8
+ if (!accept)
9
+ return true;
10
+ const parts = accept.split(',').map((s) => s.trim());
11
+ return parts.some((p) => {
12
+ if (p.endsWith('/*')) {
13
+ const prefix = p.slice(0, -2);
14
+ return file.type.startsWith(prefix);
15
+ }
16
+ return file.type === p;
17
+ });
18
+ }
19
+ function withBust(url, bustKey) {
20
+ if (!url)
21
+ return url ?? undefined;
22
+ if (bustKey == null)
23
+ return url;
24
+ return url + (url.includes('?') ? '&' : '?') + 'v=' + encodeURIComponent(String(bustKey));
25
+ }
26
+ const UploadTile = forwardRef(function UploadTile({ label, value, previewUrl, onSelect, onClear, onEditClick, onDeleteClick, className, classes, style, aspectRatio, roundedClassName = DEFAULT_ROUNDED, dashed = true, objectFit = 'cover', disabled = false, multiple = false, accept = 'image/*', capture, maxSizeMB, onError, renderEmpty, renderPreview, bustKey, busy = false, busyText = 'Loading...', ariaLabel = 'Upload file', }, ref) {
27
+ const inputRef = useRef(null);
28
+ const [dragOver, setDragOver] = useState(false);
29
+ const openDialog = useCallback(() => {
30
+ if (!disabled)
31
+ inputRef.current?.click();
32
+ }, [disabled]);
33
+ const handleFiles = useCallback((filesList) => {
34
+ const files = Array.from(filesList);
35
+ for (const f of files) {
36
+ if (!withinAccept(f, accept)) {
37
+ onError?.(new Error('Formato no permitido.'));
38
+ return;
39
+ }
40
+ if (maxSizeMB && f.size > maxSizeMB * 1024 * 1024) {
41
+ onError?.(new Error(`Máximo ${maxSizeMB}MB.`));
42
+ return;
43
+ }
44
+ }
45
+ onSelect(multiple ? files : files[0]);
46
+ }, [accept, maxSizeMB, multiple, onSelect, onError]);
47
+ const onInputChange = (e) => {
48
+ const fl = e.target.files;
49
+ if (fl?.length)
50
+ handleFiles(fl);
51
+ e.currentTarget.value = '';
52
+ };
53
+ const onDrop = (e) => {
54
+ e.preventDefault();
55
+ e.stopPropagation();
56
+ setDragOver(false);
57
+ if (disabled)
58
+ return;
59
+ if (e.dataTransfer.files?.length)
60
+ handleFiles(e.dataTransfer.files);
61
+ };
62
+ const onDragOver = (e) => {
63
+ e.preventDefault();
64
+ if (!disabled)
65
+ setDragOver(true);
66
+ };
67
+ const onDragLeave = () => setDragOver(false);
68
+ const baseBorder = dashed ? 'border-1 border-dashed' : 'border';
69
+ const dragCls = dragOver ? 'bg-gray-100 border-gray-400' : 'bg-gray-50 border-gray-300';
70
+ const displayUrl = withBust(previewUrl ?? value ?? undefined, bustKey);
71
+ const hasImage = Boolean(displayUrl);
72
+ const rootClickable = !disabled && !busy && !hasImage;
73
+ const mergedStyle = aspectRatio ? { ...style, aspectRatio } : style;
74
+ return (jsxs("div", { ref: ref, className: clsx('relative w-full h-full select-none focus:outline-none overflow-hidden', rootClickable ? 'cursor-pointer' : 'cursor-default', classes?.root, className), style: mergedStyle, role: rootClickable ? 'button' : undefined, "aria-label": ariaLabel, "aria-disabled": disabled || undefined, "aria-busy": busy || undefined, tabIndex: rootClickable ? 0 : -1, onClick: () => {
75
+ if (rootClickable)
76
+ openDialog();
77
+ }, onKeyDown: (e) => {
78
+ if (!rootClickable)
79
+ return;
80
+ if (e.key === 'Enter' || e.key === ' ') {
81
+ e.preventDefault();
82
+ openDialog();
83
+ }
84
+ }, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, children: [jsx("div", { className: clsx('h-full w-full flex items-center justify-center', baseBorder, roundedClassName, dragCls, disabled && 'opacity-50 pointer-events-none', classes?.inner), children: displayUrl ? (renderPreview ? (renderPreview(displayUrl)) : (jsx("img", { src: displayUrl, alt: "", loading: "lazy", decoding: "async", crossOrigin: "anonymous", className: clsx('h-full w-full', roundedClassName, classes?.preview), style: { objectFit } }))) : renderEmpty ? (renderEmpty()) : (jsxs("div", { className: clsx('flex flex-col items-center gap-2 text-gray-400', classes?.empty), children: [jsx("div", { className: clsx('h-10 w-10 grid place-items-center justify-center rounded-full bg-gray-200 text-xl', classes?.icon), children: jsx("span", { className: "mb-[2px] mx-[1px]", children: "+" }) }), label && jsx("span", { className: clsx('text-xs', classes?.label), children: label })] })) }), busy && (jsx("div", { className: clsx('absolute inset-0 grid place-items-center', roundedClassName, classes?.overlay), style: { pointerEvents: 'none', background: 'rgba(0,0,0,0.25)' }, children: jsxs("div", { className: "flex items-center gap-2 text-white text-sm", children: [jsxs("svg", { className: "h-5 w-5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2", opacity: "0.25" }), jsx("path", { d: "M21 12a9 9 0 0 0-9-9", stroke: "currentColor", strokeWidth: "2" })] }), jsx("span", { children: busyText })] }) })), hasImage && !busy && (jsx("div", { className: clsx('absolute left-0 right-0 bottom-0 px-3 py-2 lg:px-4 lg:py-2 bg-black/45 z-10 pointer-events-auto', roundedClassName, classes?.actionsBar), style: { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, onClick: (e) => e.stopPropagation(), children: jsxs("div", { className: "flex flex-row justify-between items-center", children: [jsx("button", { type: "button", className: clsx('w-12 h-12 rounded-full bg-[var(--color-primary-light-grey)] grid place-items-center', 'lg:w-10 lg:h-10', classes?.actionBtn), style: { cursor: 'pointer' }, "aria-label": "Editar imagen", onClick: (e) => {
85
+ e.stopPropagation();
86
+ if (onEditClick)
87
+ onEditClick();
88
+ else
89
+ openDialog();
90
+ }, children: jsx(Icon, { name: "edit", variant: "default", size: 16 }) }), jsx("button", { type: "button", className: clsx('w-12 h-12 rounded-full bg-[var(--color-primary-light-grey)] grid place-items-center', 'lg:w-10 lg:h-10', classes?.actionBtn), style: { cursor: 'pointer' }, "aria-label": "Eliminar imagen", onClick: (e) => {
91
+ e.stopPropagation();
92
+ if (onDeleteClick)
93
+ onDeleteClick();
94
+ else
95
+ onClear?.();
96
+ }, children: jsx(Icon, { name: "delete", variant: "default", size: 16 }) })] }) })), jsx("input", { ref: inputRef, className: clsx('hidden', classes?.input), type: "file", accept: accept, multiple: multiple, capture: capture, disabled: disabled, onChange: onInputChange })] }));
97
+ });
98
+
99
+ export { UploadTile as default };
@@ -0,0 +1,21 @@
1
+ import { useMemo, Children, isValidElement, useState, cloneElement } from 'react';
2
+
3
+ function Wizard({ children }) {
4
+ const steps = useMemo(() => Children.toArray(children).filter(isValidElement), [children]);
5
+ const [activeIndex, setActiveIndex] = useState(0);
6
+ const totalSteps = steps.length;
7
+ const progress = totalSteps === 0 ? 0 : ((activeIndex + 1) / totalSteps) * 100;
8
+ const goNext = () => setActiveIndex((prev) => Math.min(prev + 1, totalSteps - 1));
9
+ const goBack = () => setActiveIndex((prev) => Math.max(prev - 1, 0));
10
+ const injectedProps = {
11
+ stepIndex: activeIndex,
12
+ totalSteps,
13
+ progress,
14
+ goNext,
15
+ goBack,
16
+ };
17
+ const CurrentStep = steps[activeIndex];
18
+ return cloneElement(CurrentStep, injectedProps);
19
+ }
20
+
21
+ export { Wizard as default };