analytica-frontend-lib 1.1.52 → 1.1.54
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/dist/Modal/index.d.mts +14 -5
- package/dist/Modal/index.d.ts +14 -5
- package/dist/Modal/index.js +216 -31
- package/dist/Modal/index.js.map +1 -1
- package/dist/Modal/index.mjs +217 -32
- package/dist/Modal/index.mjs.map +1 -1
- package/dist/Modal/utils/videoUtils/index.d.mts +28 -0
- package/dist/Modal/utils/videoUtils/index.d.ts +28 -0
- package/dist/Modal/utils/videoUtils/index.js +76 -0
- package/dist/Modal/utils/videoUtils/index.js.map +1 -0
- package/dist/Modal/utils/videoUtils/index.mjs +49 -0
- package/dist/Modal/utils/videoUtils/index.mjs.map +1 -0
- package/dist/NotificationCard/index.js +325 -99
- package/dist/NotificationCard/index.js.map +1 -1
- package/dist/NotificationCard/index.mjs +326 -100
- package/dist/NotificationCard/index.mjs.map +1 -1
- package/dist/Quiz/index.js +154 -26
- package/dist/Quiz/index.js.map +1 -1
- package/dist/Quiz/index.mjs +166 -38
- package/dist/Quiz/index.mjs.map +1 -1
- package/dist/index.css +10 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +173 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +182 -54
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +10 -0
- package/dist/styles.css.map +1 -1
- package/package.json +2 -1
package/dist/Modal/index.d.mts
CHANGED
|
@@ -12,19 +12,29 @@ type ModalProps = {
|
|
|
12
12
|
/** Modal title */
|
|
13
13
|
title: string;
|
|
14
14
|
/** Modal description/content */
|
|
15
|
-
children
|
|
15
|
+
children?: ReactNode;
|
|
16
16
|
/** Size of the modal */
|
|
17
17
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
18
18
|
/** Additional CSS classes for the modal content */
|
|
19
19
|
className?: string;
|
|
20
|
-
/** Whether clicking the backdrop should close the modal */
|
|
21
|
-
closeOnBackdropClick?: boolean;
|
|
22
20
|
/** Whether pressing Escape should close the modal */
|
|
23
21
|
closeOnEscape?: boolean;
|
|
24
22
|
/** Footer content (typically buttons) */
|
|
25
23
|
footer?: ReactNode;
|
|
26
24
|
/** Hide the close button */
|
|
27
25
|
hideCloseButton?: boolean;
|
|
26
|
+
/** Modal variant */
|
|
27
|
+
variant?: 'default' | 'activity';
|
|
28
|
+
/** Description for activity variant */
|
|
29
|
+
description?: string;
|
|
30
|
+
/** Image URL for activity variant */
|
|
31
|
+
image?: string;
|
|
32
|
+
/** Alt text for activity image (leave empty for decorative images) */
|
|
33
|
+
imageAlt?: string;
|
|
34
|
+
/** Action link for activity variant */
|
|
35
|
+
actionLink?: string;
|
|
36
|
+
/** Action button label for activity variant */
|
|
37
|
+
actionLabel?: string;
|
|
28
38
|
};
|
|
29
39
|
/**
|
|
30
40
|
* Modal component for Analytica Ensino platforms
|
|
@@ -37,7 +47,6 @@ type ModalProps = {
|
|
|
37
47
|
* @param children - The main content of the modal
|
|
38
48
|
* @param size - The size variant (xs, sm, md, lg, xl)
|
|
39
49
|
* @param className - Additional CSS classes for the modal content
|
|
40
|
-
* @param closeOnBackdropClick - Whether clicking the backdrop closes the modal (default: true)
|
|
41
50
|
* @param closeOnEscape - Whether pressing Escape closes the modal (default: true)
|
|
42
51
|
* @param footer - Footer content, typically action buttons
|
|
43
52
|
* @param hideCloseButton - Whether to hide the X close button (default: false)
|
|
@@ -61,6 +70,6 @@ type ModalProps = {
|
|
|
61
70
|
* </Modal>
|
|
62
71
|
* ```
|
|
63
72
|
*/
|
|
64
|
-
declare const Modal: ({ isOpen, onClose, title, children, size, className,
|
|
73
|
+
declare const Modal: ({ isOpen, onClose, title, children, size, className, closeOnEscape, footer, hideCloseButton, variant, description, image, imageAlt, actionLink, actionLabel, }: ModalProps) => react_jsx_runtime.JSX.Element | null;
|
|
65
74
|
|
|
66
75
|
export { Modal as default };
|
package/dist/Modal/index.d.ts
CHANGED
|
@@ -12,19 +12,29 @@ type ModalProps = {
|
|
|
12
12
|
/** Modal title */
|
|
13
13
|
title: string;
|
|
14
14
|
/** Modal description/content */
|
|
15
|
-
children
|
|
15
|
+
children?: ReactNode;
|
|
16
16
|
/** Size of the modal */
|
|
17
17
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
18
18
|
/** Additional CSS classes for the modal content */
|
|
19
19
|
className?: string;
|
|
20
|
-
/** Whether clicking the backdrop should close the modal */
|
|
21
|
-
closeOnBackdropClick?: boolean;
|
|
22
20
|
/** Whether pressing Escape should close the modal */
|
|
23
21
|
closeOnEscape?: boolean;
|
|
24
22
|
/** Footer content (typically buttons) */
|
|
25
23
|
footer?: ReactNode;
|
|
26
24
|
/** Hide the close button */
|
|
27
25
|
hideCloseButton?: boolean;
|
|
26
|
+
/** Modal variant */
|
|
27
|
+
variant?: 'default' | 'activity';
|
|
28
|
+
/** Description for activity variant */
|
|
29
|
+
description?: string;
|
|
30
|
+
/** Image URL for activity variant */
|
|
31
|
+
image?: string;
|
|
32
|
+
/** Alt text for activity image (leave empty for decorative images) */
|
|
33
|
+
imageAlt?: string;
|
|
34
|
+
/** Action link for activity variant */
|
|
35
|
+
actionLink?: string;
|
|
36
|
+
/** Action button label for activity variant */
|
|
37
|
+
actionLabel?: string;
|
|
28
38
|
};
|
|
29
39
|
/**
|
|
30
40
|
* Modal component for Analytica Ensino platforms
|
|
@@ -37,7 +47,6 @@ type ModalProps = {
|
|
|
37
47
|
* @param children - The main content of the modal
|
|
38
48
|
* @param size - The size variant (xs, sm, md, lg, xl)
|
|
39
49
|
* @param className - Additional CSS classes for the modal content
|
|
40
|
-
* @param closeOnBackdropClick - Whether clicking the backdrop closes the modal (default: true)
|
|
41
50
|
* @param closeOnEscape - Whether pressing Escape closes the modal (default: true)
|
|
42
51
|
* @param footer - Footer content, typically action buttons
|
|
43
52
|
* @param hideCloseButton - Whether to hide the X close button (default: false)
|
|
@@ -61,6 +70,6 @@ type ModalProps = {
|
|
|
61
70
|
* </Modal>
|
|
62
71
|
* ```
|
|
63
72
|
*/
|
|
64
|
-
declare const Modal: ({ isOpen, onClose, title, children, size, className,
|
|
73
|
+
declare const Modal: ({ isOpen, onClose, title, children, size, className, closeOnEscape, footer, hideCloseButton, variant, description, image, imageAlt, actionLink, actionLabel, }: ModalProps) => react_jsx_runtime.JSX.Element | null;
|
|
65
74
|
|
|
66
75
|
export { Modal as default };
|
package/dist/Modal/index.js
CHANGED
|
@@ -33,9 +33,111 @@ function cn(...inputs) {
|
|
|
33
33
|
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// src/components/
|
|
36
|
+
// src/components/Button/Button.tsx
|
|
37
37
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
|
+
var VARIANT_ACTION_CLASSES = {
|
|
39
|
+
solid: {
|
|
40
|
+
primary: "bg-primary-950 text-text border border-primary-950 hover:bg-primary-800 hover:border-primary-800 focus-visible:outline-none focus-visible:bg-primary-950 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-primary-700 active:border-primary-700 disabled:bg-primary-500 disabled:border-primary-500 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
41
|
+
positive: "bg-success-500 text-text border border-success-500 hover:bg-success-600 hover:border-success-600 focus-visible:outline-none focus-visible:bg-success-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-success-700 active:border-success-700 disabled:bg-success-500 disabled:border-success-500 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
42
|
+
negative: "bg-error-500 text-text border border-error-500 hover:bg-error-600 hover:border-error-600 focus-visible:outline-none focus-visible:bg-error-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-error-700 active:border-error-700 disabled:bg-error-500 disabled:border-error-500 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
43
|
+
},
|
|
44
|
+
outline: {
|
|
45
|
+
primary: "bg-transparent text-primary-950 border border-primary-950 hover:bg-background-50 hover:text-primary-400 hover:border-primary-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 active:border-primary-700 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
46
|
+
positive: "bg-transparent text-success-500 border border-success-300 hover:bg-background-50 hover:text-success-400 hover:border-success-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 active:border-success-700 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
47
|
+
negative: "bg-transparent text-error-500 border border-error-300 hover:bg-background-50 hover:text-error-400 hover:border-error-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 active:border-error-700 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
48
|
+
},
|
|
49
|
+
link: {
|
|
50
|
+
primary: "bg-transparent text-primary-950 hover:text-primary-400 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
51
|
+
positive: "bg-transparent text-success-500 hover:text-success-400 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
52
|
+
negative: "bg-transparent text-error-500 hover:text-error-400 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
53
|
+
}
|
|
54
|
+
};
|
|
38
55
|
var SIZE_CLASSES = {
|
|
56
|
+
"extra-small": "text-xs px-3.5 py-2",
|
|
57
|
+
small: "text-sm px-4 py-2.5",
|
|
58
|
+
medium: "text-md px-5 py-2.5",
|
|
59
|
+
large: "text-lg px-6 py-3",
|
|
60
|
+
"extra-large": "text-lg px-7 py-3.5"
|
|
61
|
+
};
|
|
62
|
+
var Button = ({
|
|
63
|
+
children,
|
|
64
|
+
iconLeft,
|
|
65
|
+
iconRight,
|
|
66
|
+
size = "medium",
|
|
67
|
+
variant = "solid",
|
|
68
|
+
action = "primary",
|
|
69
|
+
className = "",
|
|
70
|
+
disabled,
|
|
71
|
+
type = "button",
|
|
72
|
+
...props
|
|
73
|
+
}) => {
|
|
74
|
+
const sizeClasses = SIZE_CLASSES[size];
|
|
75
|
+
const variantClasses = VARIANT_ACTION_CLASSES[variant][action];
|
|
76
|
+
const baseClasses = "inline-flex items-center justify-center rounded-full cursor-pointer font-medium";
|
|
77
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
className: cn(baseClasses, variantClasses, sizeClasses, className),
|
|
81
|
+
disabled,
|
|
82
|
+
type,
|
|
83
|
+
...props,
|
|
84
|
+
children: [
|
|
85
|
+
iconLeft && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mr-2 flex items-center", children: iconLeft }),
|
|
86
|
+
children,
|
|
87
|
+
iconRight && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2 flex items-center", children: iconRight })
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
var Button_default = Button;
|
|
93
|
+
|
|
94
|
+
// src/components/Modal/utils/videoUtils.ts
|
|
95
|
+
var isYouTubeUrl = (url) => {
|
|
96
|
+
const youtubeRegex = /^(https?:\/\/)?((www|m|music)\.)?(youtube\.com|youtu\.be|youtube-nocookie\.com)\/.+/i;
|
|
97
|
+
return youtubeRegex.test(url);
|
|
98
|
+
};
|
|
99
|
+
var isValidYouTubeHost = (host) => {
|
|
100
|
+
if (host === "youtu.be") return "youtu.be";
|
|
101
|
+
const isValidYouTubeCom = host === "youtube.com" || host.endsWith(".youtube.com") && /^(www|m|music)\.youtube\.com$/.test(host);
|
|
102
|
+
if (isValidYouTubeCom) return "youtube";
|
|
103
|
+
const isValidNoCookie = host === "youtube-nocookie.com" || host.endsWith(".youtube-nocookie.com") && /^(www|m|music)\.youtube-nocookie\.com$/.test(host);
|
|
104
|
+
if (isValidNoCookie) return "nocookie";
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
var extractYoutuBeId = (pathname) => {
|
|
108
|
+
const firstSeg = pathname.split("/").filter(Boolean)[0];
|
|
109
|
+
return firstSeg || null;
|
|
110
|
+
};
|
|
111
|
+
var extractYouTubeId = (pathname, searchParams) => {
|
|
112
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
113
|
+
const [first, second] = parts;
|
|
114
|
+
if (first === "embed" && second) return second;
|
|
115
|
+
if (first === "shorts" && second) return second;
|
|
116
|
+
if (first === "live" && second) return second;
|
|
117
|
+
const v = searchParams.get("v");
|
|
118
|
+
if (v) return v;
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
var getYouTubeVideoId = (url) => {
|
|
122
|
+
try {
|
|
123
|
+
const u = new URL(url);
|
|
124
|
+
const hostType = isValidYouTubeHost(u.hostname.toLowerCase());
|
|
125
|
+
if (!hostType) return null;
|
|
126
|
+
if (hostType === "youtu.be") {
|
|
127
|
+
return extractYoutuBeId(u.pathname);
|
|
128
|
+
}
|
|
129
|
+
return extractYouTubeId(u.pathname, u.searchParams);
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var getYouTubeEmbedUrl = (videoId) => {
|
|
135
|
+
return `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=0&rel=0&modestbranding=1`;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/components/Modal/Modal.tsx
|
|
139
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
140
|
+
var SIZE_CLASSES2 = {
|
|
39
141
|
xs: "max-w-[360px]",
|
|
40
142
|
sm: "max-w-[420px]",
|
|
41
143
|
md: "max-w-[510px]",
|
|
@@ -49,11 +151,17 @@ var Modal = ({
|
|
|
49
151
|
children,
|
|
50
152
|
size = "md",
|
|
51
153
|
className = "",
|
|
52
|
-
closeOnBackdropClick = true,
|
|
53
154
|
closeOnEscape = true,
|
|
54
155
|
footer,
|
|
55
|
-
hideCloseButton = false
|
|
156
|
+
hideCloseButton = false,
|
|
157
|
+
variant = "default",
|
|
158
|
+
description,
|
|
159
|
+
image,
|
|
160
|
+
imageAlt,
|
|
161
|
+
actionLink,
|
|
162
|
+
actionLabel
|
|
56
163
|
}) => {
|
|
164
|
+
const titleId = (0, import_react.useId)();
|
|
57
165
|
(0, import_react.useEffect)(() => {
|
|
58
166
|
if (!isOpen || !closeOnEscape) return;
|
|
59
167
|
const handleEscape = (event) => {
|
|
@@ -75,18 +183,8 @@ var Modal = ({
|
|
|
75
183
|
document.body.style.overflow = originalOverflow;
|
|
76
184
|
};
|
|
77
185
|
}, [isOpen]);
|
|
78
|
-
const handleBackdropClick = (event) => {
|
|
79
|
-
if (closeOnBackdropClick && event.target === event.currentTarget) {
|
|
80
|
-
onClose();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
const handleBackdropKeyDown = (event) => {
|
|
84
|
-
if (closeOnBackdropClick && (event.key === "Enter" || event.key === " ")) {
|
|
85
|
-
onClose();
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
186
|
if (!isOpen) return null;
|
|
89
|
-
const sizeClasses =
|
|
187
|
+
const sizeClasses = SIZE_CLASSES2[size];
|
|
90
188
|
const baseClasses = "bg-secondary-50 rounded-3xl shadow-hard-shadow-2 border border-border-100 w-full mx-4";
|
|
91
189
|
const dialogResetClasses = "p-0 m-0 border-none outline-none max-h-none static";
|
|
92
190
|
const modalClasses = cn(
|
|
@@ -95,33 +193,120 @@ var Modal = ({
|
|
|
95
193
|
dialogResetClasses,
|
|
96
194
|
className
|
|
97
195
|
);
|
|
98
|
-
|
|
99
|
-
|
|
196
|
+
const normalizeUrl = (href) => /^https?:\/\//i.test(href) ? href : `https://${href}`;
|
|
197
|
+
const handleActionClick = () => {
|
|
198
|
+
if (actionLink) {
|
|
199
|
+
window.open(normalizeUrl(actionLink), "_blank", "noopener,noreferrer");
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
if (variant === "activity") {
|
|
203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
204
|
+
"dialog",
|
|
205
|
+
{
|
|
206
|
+
className: modalClasses,
|
|
207
|
+
"aria-labelledby": titleId,
|
|
208
|
+
"aria-modal": "true",
|
|
209
|
+
open: true,
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex justify-end p-6 pb-0", children: !hideCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
onClick: onClose,
|
|
215
|
+
className: "p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2",
|
|
216
|
+
"aria-label": "Fechar modal",
|
|
217
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_phosphor_react.X, { size: 18 })
|
|
218
|
+
}
|
|
219
|
+
) }),
|
|
220
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center px-6 pb-6 gap-5", children: [
|
|
221
|
+
image && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
222
|
+
"img",
|
|
223
|
+
{
|
|
224
|
+
src: image,
|
|
225
|
+
alt: imageAlt ?? "",
|
|
226
|
+
className: "w-[122px] h-[122px] object-contain"
|
|
227
|
+
}
|
|
228
|
+
) }),
|
|
229
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
230
|
+
"h2",
|
|
231
|
+
{
|
|
232
|
+
id: titleId,
|
|
233
|
+
className: "text-lg font-semibold text-text-950 text-center",
|
|
234
|
+
children: title
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm font-normal text-text-400 text-center max-w-md leading-[21px]", children: description }),
|
|
238
|
+
actionLink && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full", children: [
|
|
239
|
+
(() => {
|
|
240
|
+
const normalized = normalizeUrl(actionLink);
|
|
241
|
+
const isYT = isYouTubeUrl(normalized);
|
|
242
|
+
if (!isYT) return null;
|
|
243
|
+
const id = getYouTubeVideoId(normalized);
|
|
244
|
+
if (!id) {
|
|
245
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
246
|
+
Button_default,
|
|
247
|
+
{
|
|
248
|
+
variant: "solid",
|
|
249
|
+
action: "primary",
|
|
250
|
+
size: "large",
|
|
251
|
+
className: "w-full",
|
|
252
|
+
onClick: handleActionClick,
|
|
253
|
+
children: actionLabel || "Iniciar Atividade"
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
258
|
+
"iframe",
|
|
259
|
+
{
|
|
260
|
+
src: getYouTubeEmbedUrl(id),
|
|
261
|
+
className: "w-full aspect-video rounded-lg",
|
|
262
|
+
allowFullScreen: true,
|
|
263
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
264
|
+
title: "V\xEDdeo YouTube"
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
})(),
|
|
268
|
+
!isYouTubeUrl(normalizeUrl(actionLink)) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
269
|
+
Button_default,
|
|
270
|
+
{
|
|
271
|
+
variant: "solid",
|
|
272
|
+
action: "primary",
|
|
273
|
+
size: "large",
|
|
274
|
+
className: "w-full",
|
|
275
|
+
onClick: handleActionClick,
|
|
276
|
+
children: actionLabel || "Iniciar Atividade"
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
] })
|
|
280
|
+
] })
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
) });
|
|
284
|
+
}
|
|
285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
286
|
+
"dialog",
|
|
100
287
|
{
|
|
101
|
-
className:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "modal-title", className: "text-lg font-semibold text-text-950", children: title }),
|
|
110
|
-
!hideCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
288
|
+
className: modalClasses,
|
|
289
|
+
"aria-labelledby": titleId,
|
|
290
|
+
"aria-modal": "true",
|
|
291
|
+
open: true,
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between px-6 py-6", children: [
|
|
294
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { id: titleId, className: "text-lg font-semibold text-text-950", children: title }),
|
|
295
|
+
!hideCloseButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
111
296
|
"button",
|
|
112
297
|
{
|
|
113
298
|
onClick: onClose,
|
|
114
299
|
className: "p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2",
|
|
115
300
|
"aria-label": "Fechar modal",
|
|
116
|
-
children: /* @__PURE__ */ (0,
|
|
301
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_phosphor_react.X, { size: 18 })
|
|
117
302
|
}
|
|
118
303
|
)
|
|
119
304
|
] }),
|
|
120
|
-
/* @__PURE__ */ (0,
|
|
121
|
-
footer && /* @__PURE__ */ (0,
|
|
122
|
-
]
|
|
305
|
+
children && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "px-6 pb-6", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-text-500 font-normal text-sm leading-6", children }) }),
|
|
306
|
+
footer && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex justify-end gap-3 px-6 pb-6", children: footer })
|
|
307
|
+
]
|
|
123
308
|
}
|
|
124
|
-
);
|
|
309
|
+
) });
|
|
125
310
|
};
|
|
126
311
|
var Modal_default = Modal;
|
|
127
312
|
//# sourceMappingURL=index.js.map
|
package/dist/Modal/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Modal/Modal.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { ReactNode, MouseEvent, useEffect, KeyboardEvent } from 'react';\nimport { X } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n xs: 'max-w-[360px]',\n sm: 'max-w-[420px]',\n md: 'max-w-[510px]',\n lg: 'max-w-[640px]',\n xl: 'max-w-[970px]',\n} as const;\n\n/**\n * Modal component props interface\n */\ntype ModalProps = {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Function to close the modal */\n onClose: () => void;\n /** Modal title */\n title: string;\n /** Modal description/content */\n children: ReactNode;\n /** Size of the modal */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Additional CSS classes for the modal content */\n className?: string;\n /** Whether clicking the backdrop should close the modal */\n closeOnBackdropClick?: boolean;\n /** Whether pressing Escape should close the modal */\n closeOnEscape?: boolean;\n /** Footer content (typically buttons) */\n footer?: ReactNode;\n /** Hide the close button */\n hideCloseButton?: boolean;\n};\n\n/**\n * Modal component for Analytica Ensino platforms\n *\n * A flexible modal component with multiple size variants and customizable behavior.\n *\n * @param isOpen - Whether the modal is currently open\n * @param onClose - Callback function called when the modal should be closed\n * @param title - The title displayed at the top of the modal\n * @param children - The main content of the modal\n * @param size - The size variant (xs, sm, md, lg, xl)\n * @param className - Additional CSS classes for the modal content\n * @param closeOnBackdropClick - Whether clicking the backdrop closes the modal (default: true)\n * @param closeOnEscape - Whether pressing Escape closes the modal (default: true)\n * @param footer - Footer content, typically action buttons\n * @param hideCloseButton - Whether to hide the X close button (default: false)\n * @returns A modal overlay with content\n *\n * @example\n * ```tsx\n * <Modal\n * isOpen={isModalOpen}\n * onClose={() => setIsModalOpen(false)}\n * title=\"Invite your team\"\n * size=\"md\"\n * footer={\n * <div className=\"flex gap-3\">\n * <Button variant=\"outline\" onClick={() => setIsModalOpen(false)}>Cancel</Button>\n * <Button variant=\"solid\" onClick={handleExplore}>Explore</Button>\n * </div>\n * }\n * >\n * Elevate user interactions with our versatile modals.\n * </Modal>\n * ```\n */\nconst Modal = ({\n isOpen,\n onClose,\n title,\n children,\n size = 'md',\n className = '',\n closeOnBackdropClick = true,\n closeOnEscape = true,\n footer,\n hideCloseButton = false,\n}: ModalProps) => {\n // Handle escape key\n useEffect(() => {\n if (!isOpen || !closeOnEscape) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose();\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, closeOnEscape, onClose]);\n\n // Handle body scroll lock\n useEffect(() => {\n const originalOverflow = document.body.style.overflow;\n if (isOpen) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = originalOverflow;\n }\n\n return () => {\n document.body.style.overflow = originalOverflow;\n };\n }, [isOpen]);\n\n // Handle backdrop click\n const handleBackdropClick = (event: MouseEvent<HTMLDivElement>) => {\n if (closeOnBackdropClick && event.target === event.currentTarget) {\n onClose();\n }\n };\n\n // Handle backdrop keyboard interaction\n const handleBackdropKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (closeOnBackdropClick && (event.key === 'Enter' || event.key === ' ')) {\n onClose();\n }\n };\n\n if (!isOpen) return null;\n\n const sizeClasses = SIZE_CLASSES[size];\n const baseClasses =\n 'bg-secondary-50 rounded-3xl shadow-hard-shadow-2 border border-border-100 w-full mx-4';\n // Reset dialog default styles to prevent positioning issues\n const dialogResetClasses =\n 'p-0 m-0 border-none outline-none max-h-none static';\n const modalClasses = cn(\n baseClasses,\n sizeClasses,\n dialogResetClasses,\n className\n );\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs\"\n onClick={handleBackdropClick}\n onKeyDown={handleBackdropKeyDown}\n role=\"button\"\n tabIndex={closeOnBackdropClick ? 0 : -1}\n aria-label=\"Fechar modal clicando no fundo\"\n >\n <dialog className={modalClasses} aria-labelledby=\"modal-title\" open>\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-6\">\n <h2 id=\"modal-title\" className=\"text-lg font-semibold text-text-950\">\n {title}\n </h2>\n {!hideCloseButton && (\n <button\n onClick={onClose}\n className=\"p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2\"\n aria-label=\"Fechar modal\"\n >\n <X size={18} />\n </button>\n )}\n </div>\n\n {/* Content */}\n <div className=\"px-6 pb-6\">\n <div className=\"text-text-500 font-normal text-sm leading-6\">\n {children}\n </div>\n </div>\n\n {/* Footer */}\n {footer && (\n <div className=\"flex justify-end gap-3 px-6 pb-6\">{footer}</div>\n )}\n </dialog>\n </div>\n );\n};\n\nexport default Modal;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgE;AAChE,4BAAkB;;;ACDlB,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADuJQ;AArJR,IAAM,eAAe;AAAA,EACnB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AA+DA,IAAM,QAAQ,CAAC;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,uBAAuB;AAAA,EACvB,gBAAgB;AAAA,EAChB;AAAA,EACA,kBAAkB;AACpB,MAAkB;AAEhB,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,cAAe;AAE/B,UAAM,eAAe,CAAC,UAAoC;AACxD,UAAI,MAAM,QAAQ,UAAU;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,YAAY;AACjD,WAAO,MAAM,SAAS,oBAAoB,WAAW,YAAY;AAAA,EACnE,GAAG,CAAC,QAAQ,eAAe,OAAO,CAAC;AAGnC,8BAAU,MAAM;AACd,UAAM,mBAAmB,SAAS,KAAK,MAAM;AAC7C,QAAI,QAAQ;AACV,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC,OAAO;AACL,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAEA,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,QAAM,sBAAsB,CAAC,UAAsC;AACjE,QAAI,wBAAwB,MAAM,WAAW,MAAM,eAAe;AAChE,cAAQ;AAAA,IACV;AAAA,EACF;AAGA,QAAM,wBAAwB,CAAC,UAAyC;AACtE,QAAI,yBAAyB,MAAM,QAAQ,WAAW,MAAM,QAAQ,MAAM;AACxE,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,cAAc,aAAa,IAAI;AACrC,QAAM,cACJ;AAEF,QAAM,qBACJ;AACF,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAW;AAAA,MACX,MAAK;AAAA,MACL,UAAU,uBAAuB,IAAI;AAAA,MACrC,cAAW;AAAA,MAEX,uDAAC,YAAO,WAAW,cAAc,mBAAgB,eAAc,MAAI,MAEjE;AAAA,qDAAC,SAAI,WAAU,+CACb;AAAA,sDAAC,QAAG,IAAG,eAAc,WAAU,uCAC5B,iBACH;AAAA,UACC,CAAC,mBACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS;AAAA,cACT,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,sDAAC,2BAAE,MAAM,IAAI;AAAA;AAAA,UACf;AAAA,WAEJ;AAAA,QAGA,4CAAC,SAAI,WAAU,aACb,sDAAC,SAAI,WAAU,+CACZ,UACH,GACF;AAAA,QAGC,UACC,4CAAC,SAAI,WAAU,oCAAoC,kBAAO;AAAA,SAE9D;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Modal/Modal.tsx","../../src/utils/utils.ts","../../src/components/Button/Button.tsx","../../src/components/Modal/utils/videoUtils.ts"],"sourcesContent":["import { ReactNode, useEffect, useId } from 'react';\nimport { X } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\nimport Button from '../Button/Button';\nimport {\n isYouTubeUrl,\n getYouTubeVideoId,\n getYouTubeEmbedUrl,\n} from './utils/videoUtils';\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n xs: 'max-w-[360px]',\n sm: 'max-w-[420px]',\n md: 'max-w-[510px]',\n lg: 'max-w-[640px]',\n xl: 'max-w-[970px]',\n} as const;\n\n/**\n * Modal component props interface\n */\ntype ModalProps = {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Function to close the modal */\n onClose: () => void;\n /** Modal title */\n title: string;\n /** Modal description/content */\n children?: ReactNode;\n /** Size of the modal */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Additional CSS classes for the modal content */\n className?: string;\n /** Whether pressing Escape should close the modal */\n closeOnEscape?: boolean;\n /** Footer content (typically buttons) */\n footer?: ReactNode;\n /** Hide the close button */\n hideCloseButton?: boolean;\n /** Modal variant */\n variant?: 'default' | 'activity';\n /** Description for activity variant */\n description?: string;\n /** Image URL for activity variant */\n image?: string;\n /** Alt text for activity image (leave empty for decorative images) */\n imageAlt?: string;\n /** Action link for activity variant */\n actionLink?: string;\n /** Action button label for activity variant */\n actionLabel?: string;\n};\n\n/**\n * Modal component for Analytica Ensino platforms\n *\n * A flexible modal component with multiple size variants and customizable behavior.\n *\n * @param isOpen - Whether the modal is currently open\n * @param onClose - Callback function called when the modal should be closed\n * @param title - The title displayed at the top of the modal\n * @param children - The main content of the modal\n * @param size - The size variant (xs, sm, md, lg, xl)\n * @param className - Additional CSS classes for the modal content\n * @param closeOnEscape - Whether pressing Escape closes the modal (default: true)\n * @param footer - Footer content, typically action buttons\n * @param hideCloseButton - Whether to hide the X close button (default: false)\n * @returns A modal overlay with content\n *\n * @example\n * ```tsx\n * <Modal\n * isOpen={isModalOpen}\n * onClose={() => setIsModalOpen(false)}\n * title=\"Invite your team\"\n * size=\"md\"\n * footer={\n * <div className=\"flex gap-3\">\n * <Button variant=\"outline\" onClick={() => setIsModalOpen(false)}>Cancel</Button>\n * <Button variant=\"solid\" onClick={handleExplore}>Explore</Button>\n * </div>\n * }\n * >\n * Elevate user interactions with our versatile modals.\n * </Modal>\n * ```\n */\nconst Modal = ({\n isOpen,\n onClose,\n title,\n children,\n size = 'md',\n className = '',\n closeOnEscape = true,\n footer,\n hideCloseButton = false,\n variant = 'default',\n description,\n image,\n imageAlt,\n actionLink,\n actionLabel,\n}: ModalProps) => {\n const titleId = useId();\n\n // Handle escape key\n useEffect(() => {\n if (!isOpen || !closeOnEscape) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose();\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [isOpen, closeOnEscape, onClose]);\n\n // Handle body scroll lock\n useEffect(() => {\n const originalOverflow = document.body.style.overflow;\n if (isOpen) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = originalOverflow;\n }\n\n return () => {\n document.body.style.overflow = originalOverflow;\n };\n }, [isOpen]);\n\n if (!isOpen) return null;\n\n const sizeClasses = SIZE_CLASSES[size];\n const baseClasses =\n 'bg-secondary-50 rounded-3xl shadow-hard-shadow-2 border border-border-100 w-full mx-4';\n // Reset dialog default styles to prevent positioning issues\n const dialogResetClasses =\n 'p-0 m-0 border-none outline-none max-h-none static';\n const modalClasses = cn(\n baseClasses,\n sizeClasses,\n dialogResetClasses,\n className\n );\n\n // Normalize URLs missing protocol\n const normalizeUrl = (href: string) =>\n /^https?:\\/\\//i.test(href) ? href : `https://${href}`;\n\n // Handle action link click\n const handleActionClick = () => {\n if (actionLink) {\n window.open(normalizeUrl(actionLink), '_blank', 'noopener,noreferrer');\n }\n };\n\n // Activity variant rendering\n if (variant === 'activity') {\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default\">\n <dialog\n className={modalClasses}\n aria-labelledby={titleId}\n aria-modal=\"true\"\n open\n >\n {/* Header simples com X */}\n <div className=\"flex justify-end p-6 pb-0\">\n {!hideCloseButton && (\n <button\n onClick={onClose}\n className=\"p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2\"\n aria-label=\"Fechar modal\"\n >\n <X size={18} />\n </button>\n )}\n </div>\n\n {/* Conteúdo centralizado */}\n <div className=\"flex flex-col items-center px-6 pb-6 gap-5\">\n {/* Imagem ilustrativa */}\n {image && (\n <div className=\"flex justify-center\">\n <img\n src={image}\n alt={imageAlt ?? ''}\n className=\"w-[122px] h-[122px] object-contain\"\n />\n </div>\n )}\n\n {/* Título */}\n <h2\n id={titleId}\n className=\"text-lg font-semibold text-text-950 text-center\"\n >\n {title}\n </h2>\n\n {/* Descrição */}\n {description && (\n <p className=\"text-sm font-normal text-text-400 text-center max-w-md leading-[21px]\">\n {description}\n </p>\n )}\n\n {/* Ação: Botão ou Vídeo Embedado */}\n {actionLink && (\n <div className=\"w-full\">\n {(() => {\n const normalized = normalizeUrl(actionLink);\n const isYT = isYouTubeUrl(normalized);\n if (!isYT) return null;\n const id = getYouTubeVideoId(normalized);\n if (!id) {\n return (\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"large\"\n className=\"w-full\"\n onClick={handleActionClick}\n >\n {actionLabel || 'Iniciar Atividade'}\n </Button>\n );\n }\n return (\n <iframe\n src={getYouTubeEmbedUrl(id)}\n className=\"w-full aspect-video rounded-lg\"\n allowFullScreen\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n title=\"Vídeo YouTube\"\n />\n );\n })()}\n {!isYouTubeUrl(normalizeUrl(actionLink)) && (\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"large\"\n className=\"w-full\"\n onClick={handleActionClick}\n >\n {actionLabel || 'Iniciar Atividade'}\n </Button>\n )}\n </div>\n )}\n </div>\n </dialog>\n </div>\n );\n }\n\n // Default variant rendering\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default\">\n <dialog\n className={modalClasses}\n aria-labelledby={titleId}\n aria-modal=\"true\"\n open\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-6\">\n <h2 id={titleId} className=\"text-lg font-semibold text-text-950\">\n {title}\n </h2>\n {!hideCloseButton && (\n <button\n onClick={onClose}\n className=\"p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2\"\n aria-label=\"Fechar modal\"\n >\n <X size={18} />\n </button>\n )}\n </div>\n\n {/* Content */}\n {children && (\n <div className=\"px-6 pb-6\">\n <div className=\"text-text-500 font-normal text-sm leading-6\">\n {children}\n </div>\n </div>\n )}\n\n {/* Footer */}\n {footer && (\n <div className=\"flex justify-end gap-3 px-6 pb-6\">{footer}</div>\n )}\n </dialog>\n </div>\n );\n};\n\nexport default Modal;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n 'bg-primary-950 text-text border border-primary-950 hover:bg-primary-800 hover:border-primary-800 focus-visible:outline-none focus-visible:bg-primary-950 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-primary-700 active:border-primary-700 disabled:bg-primary-500 disabled:border-primary-500 disabled:opacity-40 disabled:cursor-not-allowed',\n positive:\n 'bg-success-500 text-text border border-success-500 hover:bg-success-600 hover:border-success-600 focus-visible:outline-none focus-visible:bg-success-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-success-700 active:border-success-700 disabled:bg-success-500 disabled:border-success-500 disabled:opacity-40 disabled:cursor-not-allowed',\n negative:\n 'bg-error-500 text-text border border-error-500 hover:bg-error-600 hover:border-error-600 focus-visible:outline-none focus-visible:bg-error-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-error-700 active:border-error-700 disabled:bg-error-500 disabled:border-error-500 disabled:opacity-40 disabled:cursor-not-allowed',\n },\n outline: {\n primary:\n 'bg-transparent text-primary-950 border border-primary-950 hover:bg-background-50 hover:text-primary-400 hover:border-primary-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 active:border-primary-700 disabled:opacity-40 disabled:cursor-not-allowed',\n positive:\n 'bg-transparent text-success-500 border border-success-300 hover:bg-background-50 hover:text-success-400 hover:border-success-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 active:border-success-700 disabled:opacity-40 disabled:cursor-not-allowed',\n negative:\n 'bg-transparent text-error-500 border border-error-300 hover:bg-background-50 hover:text-error-400 hover:border-error-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 active:border-error-700 disabled:opacity-40 disabled:cursor-not-allowed',\n },\n link: {\n primary:\n 'bg-transparent text-primary-950 hover:text-primary-400 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 disabled:opacity-40 disabled:cursor-not-allowed',\n positive:\n 'bg-transparent text-success-500 hover:text-success-400 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 disabled:opacity-40 disabled:cursor-not-allowed',\n negative:\n 'bg-transparent text-error-500 hover:text-error-400 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 disabled:opacity-40 disabled:cursor-not-allowed',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","/**\n * Video utilities for Modal component\n *\n * Utilities to handle YouTube video embedding and URL detection\n */\n\n/**\n * Check if a given URL is a YouTube URL\n *\n * @param url - The URL to check\n * @returns true if the URL is from YouTube, false otherwise\n */\nexport const isYouTubeUrl = (url: string): boolean => {\n const youtubeRegex =\n /^(https?:\\/\\/)?((www|m|music)\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)\\/.+/i;\n return youtubeRegex.test(url);\n};\n\n/**\n * Validate if hostname is a legitimate YouTube host\n *\n * @param host - The hostname to validate\n * @returns The type of YouTube host or null if invalid\n */\nconst isValidYouTubeHost = (\n host: string\n): 'youtu.be' | 'youtube' | 'nocookie' | null => {\n if (host === 'youtu.be') return 'youtu.be';\n\n const isValidYouTubeCom =\n host === 'youtube.com' ||\n (host.endsWith('.youtube.com') &&\n /^(www|m|music)\\.youtube\\.com$/.test(host));\n\n if (isValidYouTubeCom) return 'youtube';\n\n const isValidNoCookie =\n host === 'youtube-nocookie.com' ||\n (host.endsWith('.youtube-nocookie.com') &&\n /^(www|m|music)\\.youtube-nocookie\\.com$/.test(host));\n\n if (isValidNoCookie) return 'nocookie';\n\n return null;\n};\n\n/**\n * Extract video ID from youtu.be path\n *\n * @param pathname - The URL pathname\n * @returns The video ID or null\n */\nconst extractYoutuBeId = (pathname: string): string | null => {\n const firstSeg = pathname.split('/').filter(Boolean)[0];\n return firstSeg || null;\n};\n\n/**\n * Extract video ID from YouTube or nocookie domains\n *\n * @param pathname - The URL pathname\n * @param searchParams - The URL search parameters\n * @returns The video ID or null\n */\nconst extractYouTubeId = (\n pathname: string,\n searchParams: URLSearchParams\n): string | null => {\n const parts = pathname.split('/').filter(Boolean);\n const [first, second] = parts;\n\n if (first === 'embed' && second) return second;\n if (first === 'shorts' && second) return second;\n if (first === 'live' && second) return second;\n\n const v = searchParams.get('v');\n if (v) return v;\n\n return null;\n};\n\n/**\n * Extract YouTube video ID from URL\n *\n * @param url - The YouTube URL\n * @returns The video ID if found, null otherwise\n */\nexport const getYouTubeVideoId = (url: string): string | null => {\n try {\n const u = new URL(url);\n const hostType = isValidYouTubeHost(u.hostname.toLowerCase());\n\n if (!hostType) return null;\n\n if (hostType === 'youtu.be') {\n return extractYoutuBeId(u.pathname);\n }\n\n return extractYouTubeId(u.pathname, u.searchParams);\n } catch {\n return null;\n }\n};\n\n/**\n * Generate YouTube embed URL for iframe\n *\n * @param videoId - The YouTube video ID\n * @returns The embed URL for the video\n */\nexport const getYouTubeEmbedUrl = (videoId: string): string => {\n return `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=0&rel=0&modestbranding=1`;\n};\n"],"mappings":"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","names":["import_jsx_runtime","SIZE_CLASSES"]}
|