analytica-frontend-lib 1.1.7 → 1.1.8

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.
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * Whiteboard image item interface
6
+ */
7
+ interface WhiteboardImage {
8
+ id: string;
9
+ imageUrl: string;
10
+ title?: string;
11
+ }
12
+ /**
13
+ * Whiteboard component props interface
14
+ */
15
+ interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** Array of images to display in the whiteboard */
17
+ images: WhiteboardImage[];
18
+ /** Whether to show download button on images */
19
+ showDownload?: boolean;
20
+ /** Custom className for the container */
21
+ className?: string;
22
+ /** Callback when download button is clicked */
23
+ onDownload?: (image: WhiteboardImage) => void;
24
+ /** Maximum number of images to display per row on desktop */
25
+ imagesPerRow?: 2 | 3 | 4;
26
+ }
27
+ /**
28
+ * Whiteboard component for displaying classroom board images
29
+ * @param props Component properties
30
+ * @returns Whiteboard component
31
+ */
32
+ declare const Whiteboard: ({ images, showDownload, className, onDownload, imagesPerRow, ...rest }: WhiteboardProps) => react_jsx_runtime.JSX.Element;
33
+
34
+ export { type WhiteboardImage, type WhiteboardProps, Whiteboard as default };
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * Whiteboard image item interface
6
+ */
7
+ interface WhiteboardImage {
8
+ id: string;
9
+ imageUrl: string;
10
+ title?: string;
11
+ }
12
+ /**
13
+ * Whiteboard component props interface
14
+ */
15
+ interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** Array of images to display in the whiteboard */
17
+ images: WhiteboardImage[];
18
+ /** Whether to show download button on images */
19
+ showDownload?: boolean;
20
+ /** Custom className for the container */
21
+ className?: string;
22
+ /** Callback when download button is clicked */
23
+ onDownload?: (image: WhiteboardImage) => void;
24
+ /** Maximum number of images to display per row on desktop */
25
+ imagesPerRow?: 2 | 3 | 4;
26
+ }
27
+ /**
28
+ * Whiteboard component for displaying classroom board images
29
+ * @param props Component properties
30
+ * @returns Whiteboard component
31
+ */
32
+ declare const Whiteboard: ({ images, showDownload, className, onDownload, imagesPerRow, ...rest }: WhiteboardProps) => react_jsx_runtime.JSX.Element;
33
+
34
+ export { type WhiteboardImage, type WhiteboardProps, Whiteboard as default };
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/components/Whiteboard/Whiteboard.tsx
21
+ var Whiteboard_exports = {};
22
+ __export(Whiteboard_exports, {
23
+ default: () => Whiteboard_default
24
+ });
25
+ module.exports = __toCommonJS(Whiteboard_exports);
26
+ var import_react = require("react");
27
+ var import_phosphor_react = require("phosphor-react");
28
+
29
+ // src/utils/utils.ts
30
+ var import_clsx = require("clsx");
31
+ var import_tailwind_merge = require("tailwind-merge");
32
+ function cn(...inputs) {
33
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
34
+ }
35
+
36
+ // src/components/Whiteboard/Whiteboard.tsx
37
+ var import_jsx_runtime = require("react/jsx-runtime");
38
+ var IMAGE_WIDTH = 225;
39
+ var IMAGE_HEIGHT = 90;
40
+ var Whiteboard = ({
41
+ images,
42
+ showDownload = true,
43
+ className,
44
+ onDownload,
45
+ imagesPerRow = 2,
46
+ ...rest
47
+ }) => {
48
+ const [imageErrors, setImageErrors] = (0, import_react.useState)(/* @__PURE__ */ new Set());
49
+ const handleDownload = (0, import_react.useCallback)(
50
+ (image) => {
51
+ if (onDownload) {
52
+ onDownload(image);
53
+ } else {
54
+ const link = document.createElement("a");
55
+ link.href = image.imageUrl;
56
+ link.download = image.title || `whiteboard-${image.id}`;
57
+ link.target = "_blank";
58
+ link.rel = "noopener noreferrer";
59
+ document.body.appendChild(link);
60
+ link.click();
61
+ document.body.removeChild(link);
62
+ }
63
+ },
64
+ [onDownload]
65
+ );
66
+ const handleImageError = (0, import_react.useCallback)((imageId) => {
67
+ setImageErrors((prev) => new Set(prev).add(imageId));
68
+ }, []);
69
+ const gridColsClass = images?.length === 1 ? "grid-cols-1" : {
70
+ 2: "grid-cols-1 sm:grid-cols-2",
71
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
72
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
73
+ }[imagesPerRow];
74
+ if (!images || images.length === 0) {
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
76
+ "div",
77
+ {
78
+ className: cn(
79
+ "flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl",
80
+ className
81
+ ),
82
+ ...rest,
83
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-400 text-sm", children: "Nenhuma imagem dispon\xEDvel" })
84
+ }
85
+ );
86
+ }
87
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
88
+ "div",
89
+ {
90
+ className: cn(
91
+ "flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto",
92
+ className
93
+ ),
94
+ ...rest,
95
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("grid gap-4", gridColsClass), children: images.map((image) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
96
+ "div",
97
+ {
98
+ className: "relative group overflow-hidden bg-gray-100 rounded-lg",
99
+ style: {
100
+ width: `${IMAGE_WIDTH}px`
101
+ },
102
+ children: [
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
104
+ "div",
105
+ {
106
+ className: "relative",
107
+ style: {
108
+ width: `${IMAGE_WIDTH}px`,
109
+ height: `${IMAGE_HEIGHT}px`
110
+ },
111
+ children: imageErrors.has(image.id) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-500 text-sm text-center px-2", children: "Imagem indispon\xEDvel" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ "img",
114
+ {
115
+ src: image.imageUrl,
116
+ alt: image.title || `Whiteboard ${image.id}`,
117
+ className: "absolute inset-0 w-full h-full object-cover",
118
+ loading: "lazy",
119
+ onError: () => handleImageError(image.id)
120
+ }
121
+ ),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" })
123
+ ] })
124
+ }
125
+ ),
126
+ showDownload && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
127
+ "button",
128
+ {
129
+ type: "button",
130
+ onClick: () => handleDownload(image),
131
+ className: "absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6",
132
+ "aria-label": `Download ${image.title || "imagem"}`,
133
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
134
+ import_phosphor_react.DownloadSimple,
135
+ {
136
+ size: 24,
137
+ weight: "regular",
138
+ className: "text-white group-hover/button:scale-110 transition-transform duration-200"
139
+ }
140
+ )
141
+ }
142
+ )
143
+ ]
144
+ },
145
+ image.id
146
+ )) })
147
+ }
148
+ );
149
+ };
150
+ var Whiteboard_default = Whiteboard;
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Whiteboard/Whiteboard.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { HTMLAttributes, useCallback, useState } from 'react';\nimport { DownloadSimple } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\n// Design constants for critical layout dimensions\nconst IMAGE_WIDTH = 225;\nconst IMAGE_HEIGHT = 90;\n\n/**\n * Whiteboard image item interface\n */\nexport interface WhiteboardImage {\n id: string;\n imageUrl: string;\n title?: string;\n}\n\n/**\n * Whiteboard component props interface\n */\nexport interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {\n /** Array of images to display in the whiteboard */\n images: WhiteboardImage[];\n /** Whether to show download button on images */\n showDownload?: boolean;\n /** Custom className for the container */\n className?: string;\n /** Callback when download button is clicked */\n onDownload?: (image: WhiteboardImage) => void;\n /** Maximum number of images to display per row on desktop */\n imagesPerRow?: 2 | 3 | 4;\n}\n\n/**\n * Whiteboard component for displaying classroom board images\n * @param props Component properties\n * @returns Whiteboard component\n */\nconst Whiteboard = ({\n images,\n showDownload = true,\n className,\n onDownload,\n imagesPerRow = 2,\n ...rest\n}: WhiteboardProps) => {\n // State to track images that failed to load\n const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());\n\n /**\n * Handle image download\n */\n const handleDownload = useCallback(\n (image: WhiteboardImage) => {\n if (onDownload) {\n onDownload(image);\n } else {\n const link = document.createElement('a');\n link.href = image.imageUrl;\n link.download = image.title || `whiteboard-${image.id}`;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n },\n [onDownload]\n );\n\n /**\n * Handle image loading error\n */\n const handleImageError = useCallback((imageId: string) => {\n setImageErrors((prev) => new Set(prev).add(imageId));\n }, []);\n\n const gridColsClass =\n images?.length === 1\n ? 'grid-cols-1'\n : {\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',\n }[imagesPerRow];\n\n // Let CSS handle sizing responsively\n\n if (!images || images.length === 0) {\n return (\n <div\n className={cn(\n 'flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl',\n className\n )}\n {...rest}\n >\n <p className=\"text-gray-400 text-sm\">Nenhuma imagem disponível</p>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto',\n className\n )}\n {...rest}\n >\n <div className={cn('grid gap-4', gridColsClass)}>\n {images.map((image) => (\n <div\n key={image.id}\n className=\"relative group overflow-hidden bg-gray-100 rounded-lg\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n }}\n >\n <div\n className=\"relative\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n height: `${IMAGE_HEIGHT}px`,\n }}\n >\n {imageErrors.has(image.id) ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gray-200\">\n <p className=\"text-gray-500 text-sm text-center px-2\">\n Imagem indisponível\n </p>\n </div>\n ) : (\n <>\n <img\n src={image.imageUrl}\n alt={image.title || `Whiteboard ${image.id}`}\n className=\"absolute inset-0 w-full h-full object-cover\"\n loading=\"lazy\"\n onError={() => handleImageError(image.id)}\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/20 to-transparent\" />\n </>\n )}\n </div>\n {showDownload && (\n <button\n type=\"button\"\n onClick={() => handleDownload(image)}\n className=\"absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6\"\n aria-label={`Download ${image.title || 'imagem'}`}\n >\n <DownloadSimple\n size={24}\n weight=\"regular\"\n className=\"text-white group-hover/button:scale-110 transition-transform duration-200\"\n />\n </button>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default Whiteboard;\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":"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","names":[]}
@@ -0,0 +1,130 @@
1
+ // src/components/Whiteboard/Whiteboard.tsx
2
+ import { useCallback, useState } from "react";
3
+ import { DownloadSimple } from "phosphor-react";
4
+
5
+ // src/utils/utils.ts
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ // src/components/Whiteboard/Whiteboard.tsx
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ var IMAGE_WIDTH = 225;
15
+ var IMAGE_HEIGHT = 90;
16
+ var Whiteboard = ({
17
+ images,
18
+ showDownload = true,
19
+ className,
20
+ onDownload,
21
+ imagesPerRow = 2,
22
+ ...rest
23
+ }) => {
24
+ const [imageErrors, setImageErrors] = useState(/* @__PURE__ */ new Set());
25
+ const handleDownload = useCallback(
26
+ (image) => {
27
+ if (onDownload) {
28
+ onDownload(image);
29
+ } else {
30
+ const link = document.createElement("a");
31
+ link.href = image.imageUrl;
32
+ link.download = image.title || `whiteboard-${image.id}`;
33
+ link.target = "_blank";
34
+ link.rel = "noopener noreferrer";
35
+ document.body.appendChild(link);
36
+ link.click();
37
+ document.body.removeChild(link);
38
+ }
39
+ },
40
+ [onDownload]
41
+ );
42
+ const handleImageError = useCallback((imageId) => {
43
+ setImageErrors((prev) => new Set(prev).add(imageId));
44
+ }, []);
45
+ const gridColsClass = images?.length === 1 ? "grid-cols-1" : {
46
+ 2: "grid-cols-1 sm:grid-cols-2",
47
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
48
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
49
+ }[imagesPerRow];
50
+ if (!images || images.length === 0) {
51
+ return /* @__PURE__ */ jsx(
52
+ "div",
53
+ {
54
+ className: cn(
55
+ "flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl",
56
+ className
57
+ ),
58
+ ...rest,
59
+ children: /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm", children: "Nenhuma imagem dispon\xEDvel" })
60
+ }
61
+ );
62
+ }
63
+ return /* @__PURE__ */ jsx(
64
+ "div",
65
+ {
66
+ className: cn(
67
+ "flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto",
68
+ className
69
+ ),
70
+ ...rest,
71
+ children: /* @__PURE__ */ jsx("div", { className: cn("grid gap-4", gridColsClass), children: images.map((image) => /* @__PURE__ */ jsxs(
72
+ "div",
73
+ {
74
+ className: "relative group overflow-hidden bg-gray-100 rounded-lg",
75
+ style: {
76
+ width: `${IMAGE_WIDTH}px`
77
+ },
78
+ children: [
79
+ /* @__PURE__ */ jsx(
80
+ "div",
81
+ {
82
+ className: "relative",
83
+ style: {
84
+ width: `${IMAGE_WIDTH}px`,
85
+ height: `${IMAGE_HEIGHT}px`
86
+ },
87
+ children: imageErrors.has(image.id) ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-sm text-center px-2", children: "Imagem indispon\xEDvel" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
88
+ /* @__PURE__ */ jsx(
89
+ "img",
90
+ {
91
+ src: image.imageUrl,
92
+ alt: image.title || `Whiteboard ${image.id}`,
93
+ className: "absolute inset-0 w-full h-full object-cover",
94
+ loading: "lazy",
95
+ onError: () => handleImageError(image.id)
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" })
99
+ ] })
100
+ }
101
+ ),
102
+ showDownload && /* @__PURE__ */ jsx(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick: () => handleDownload(image),
107
+ className: "absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6",
108
+ "aria-label": `Download ${image.title || "imagem"}`,
109
+ children: /* @__PURE__ */ jsx(
110
+ DownloadSimple,
111
+ {
112
+ size: 24,
113
+ weight: "regular",
114
+ className: "text-white group-hover/button:scale-110 transition-transform duration-200"
115
+ }
116
+ )
117
+ }
118
+ )
119
+ ]
120
+ },
121
+ image.id
122
+ )) })
123
+ }
124
+ );
125
+ };
126
+ var Whiteboard_default = Whiteboard;
127
+ export {
128
+ Whiteboard_default as default
129
+ };
130
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Whiteboard/Whiteboard.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { HTMLAttributes, useCallback, useState } from 'react';\nimport { DownloadSimple } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\n// Design constants for critical layout dimensions\nconst IMAGE_WIDTH = 225;\nconst IMAGE_HEIGHT = 90;\n\n/**\n * Whiteboard image item interface\n */\nexport interface WhiteboardImage {\n id: string;\n imageUrl: string;\n title?: string;\n}\n\n/**\n * Whiteboard component props interface\n */\nexport interface WhiteboardProps extends HTMLAttributes<HTMLDivElement> {\n /** Array of images to display in the whiteboard */\n images: WhiteboardImage[];\n /** Whether to show download button on images */\n showDownload?: boolean;\n /** Custom className for the container */\n className?: string;\n /** Callback when download button is clicked */\n onDownload?: (image: WhiteboardImage) => void;\n /** Maximum number of images to display per row on desktop */\n imagesPerRow?: 2 | 3 | 4;\n}\n\n/**\n * Whiteboard component for displaying classroom board images\n * @param props Component properties\n * @returns Whiteboard component\n */\nconst Whiteboard = ({\n images,\n showDownload = true,\n className,\n onDownload,\n imagesPerRow = 2,\n ...rest\n}: WhiteboardProps) => {\n // State to track images that failed to load\n const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());\n\n /**\n * Handle image download\n */\n const handleDownload = useCallback(\n (image: WhiteboardImage) => {\n if (onDownload) {\n onDownload(image);\n } else {\n const link = document.createElement('a');\n link.href = image.imageUrl;\n link.download = image.title || `whiteboard-${image.id}`;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n },\n [onDownload]\n );\n\n /**\n * Handle image loading error\n */\n const handleImageError = useCallback((imageId: string) => {\n setImageErrors((prev) => new Set(prev).add(imageId));\n }, []);\n\n const gridColsClass =\n images?.length === 1\n ? 'grid-cols-1'\n : {\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',\n }[imagesPerRow];\n\n // Let CSS handle sizing responsively\n\n if (!images || images.length === 0) {\n return (\n <div\n className={cn(\n 'flex items-center justify-center p-8 bg-white border border-gray-100 rounded-xl',\n className\n )}\n {...rest}\n >\n <p className=\"text-gray-400 text-sm\">Nenhuma imagem disponível</p>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col bg-white border border-gray-100 p-4 gap-2 rounded-xl w-fit mx-auto',\n className\n )}\n {...rest}\n >\n <div className={cn('grid gap-4', gridColsClass)}>\n {images.map((image) => (\n <div\n key={image.id}\n className=\"relative group overflow-hidden bg-gray-100 rounded-lg\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n }}\n >\n <div\n className=\"relative\"\n style={{\n width: `${IMAGE_WIDTH}px`,\n height: `${IMAGE_HEIGHT}px`,\n }}\n >\n {imageErrors.has(image.id) ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gray-200\">\n <p className=\"text-gray-500 text-sm text-center px-2\">\n Imagem indisponível\n </p>\n </div>\n ) : (\n <>\n <img\n src={image.imageUrl}\n alt={image.title || `Whiteboard ${image.id}`}\n className=\"absolute inset-0 w-full h-full object-cover\"\n loading=\"lazy\"\n onError={() => handleImageError(image.id)}\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/20 to-transparent\" />\n </>\n )}\n </div>\n {showDownload && (\n <button\n type=\"button\"\n onClick={() => handleDownload(image)}\n className=\"absolute bottom-3 right-3 flex items-center justify-center bg-black/20 backdrop-blur-sm rounded hover:bg-black/30 transition-colors duration-200 group/button w-6 h-6\"\n aria-label={`Download ${image.title || 'imagem'}`}\n >\n <DownloadSimple\n size={24}\n weight=\"regular\"\n className=\"text-white group-hover/button:scale-110 transition-transform duration-200\"\n />\n </button>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default Whiteboard;\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":"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","names":[]}
package/dist/index.css CHANGED
@@ -547,6 +547,9 @@
547
547
  .bottom-0 {
548
548
  bottom: calc(var(--spacing) * 0);
549
549
  }
550
+ .bottom-3 {
551
+ bottom: calc(var(--spacing) * 3);
552
+ }
550
553
  .bottom-4 {
551
554
  bottom: calc(var(--spacing) * 4);
552
555
  }
@@ -1681,6 +1684,9 @@
1681
1684
  .border-exam-4 {
1682
1685
  border-color: var(--color-exam-4);
1683
1686
  }
1687
+ .border-gray-100 {
1688
+ border-color: var(--color-gray-100);
1689
+ }
1684
1690
  .border-gray-200 {
1685
1691
  border-color: var(--color-gray-200);
1686
1692
  }
@@ -2047,6 +2053,12 @@
2047
2053
  .bg-background-muted {
2048
2054
  background-color: var(--color-background-muted);
2049
2055
  }
2056
+ .bg-black\/20 {
2057
+ background-color: color-mix(in srgb, #000 20%, transparent);
2058
+ @supports (color: color-mix(in lab, red, red)) {
2059
+ background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
2060
+ }
2061
+ }
2050
2062
  .bg-black\/30 {
2051
2063
  background-color: color-mix(in srgb, #000 30%, transparent);
2052
2064
  @supports (color: color-mix(in lab, red, red)) {
@@ -2173,6 +2185,9 @@
2173
2185
  .bg-gray-100 {
2174
2186
  background-color: var(--color-gray-100);
2175
2187
  }
2188
+ .bg-gray-200 {
2189
+ background-color: var(--color-gray-200);
2190
+ }
2176
2191
  .bg-gray-400 {
2177
2192
  background-color: var(--color-gray-400);
2178
2193
  }
@@ -2563,6 +2578,13 @@
2563
2578
  --tw-gradient-position: to top in oklab;
2564
2579
  background-image: linear-gradient(var(--tw-gradient-stops));
2565
2580
  }
2581
+ .from-black\/20 {
2582
+ --tw-gradient-from: color-mix(in srgb, #000 20%, transparent);
2583
+ @supports (color: color-mix(in lab, red, red)) {
2584
+ --tw-gradient-from: color-mix(in oklab, var(--color-black) 20%, transparent);
2585
+ }
2586
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
2587
+ }
2566
2588
  .from-black\/70 {
2567
2589
  --tw-gradient-from: color-mix(in srgb, #000 70%, transparent);
2568
2590
  @supports (color: color-mix(in lab, red, red)) {
@@ -3730,6 +3752,16 @@
3730
3752
  }
3731
3753
  }
3732
3754
  }
3755
+ .group-hover\/button\:scale-110 {
3756
+ &:is(:where(.group\/button):hover *) {
3757
+ @media (hover: hover) {
3758
+ --tw-scale-x: 110%;
3759
+ --tw-scale-y: 110%;
3760
+ --tw-scale-z: 110%;
3761
+ scale: var(--tw-scale-x) var(--tw-scale-y);
3762
+ }
3763
+ }
3764
+ }
3733
3765
  .placeholder\:text-text-600 {
3734
3766
  &::-moz-placeholder {
3735
3767
  color: var(--color-text-600);
@@ -4616,6 +4648,16 @@
4616
4648
  }
4617
4649
  }
4618
4650
  }
4651
+ .hover\:bg-black\/30 {
4652
+ &:hover {
4653
+ @media (hover: hover) {
4654
+ background-color: color-mix(in srgb, #000 30%, transparent);
4655
+ @supports (color: color-mix(in lab, red, red)) {
4656
+ background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
4657
+ }
4658
+ }
4659
+ }
4660
+ }
4619
4661
  .hover\:bg-blue-600 {
4620
4662
  &:hover {
4621
4663
  @media (hover: hover) {
@@ -8176,6 +8218,11 @@
8176
8218
  max-width: 100px;
8177
8219
  }
8178
8220
  }
8221
+ .sm\:grid-cols-2 {
8222
+ @media (width >= 40rem) {
8223
+ grid-template-columns: repeat(2, minmax(0, 1fr));
8224
+ }
8225
+ }
8179
8226
  .sm\:flex-row {
8180
8227
  @media (width >= 40rem) {
8181
8228
  flex-direction: row;