kupos-ui-components-lib 9.10.1 → 9.10.3
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/KuposUIComponent.d.ts +14 -1
- package/dist/KuposUIComponent.js +3 -0
- package/dist/components/Survey/SurveyMobile.js +17 -16
- package/dist/index.d.ts +5 -1
- package/dist/index.js +9 -1
- package/dist/styles.css +32 -0
- package/dist/ui/FeatureServiceUI/FeatureServiceUi.js +10 -9
- package/package.json +1 -1
- package/src/KuposUIComponent.tsx +22 -1
- package/src/assets/images/anims/service_list/succes_anim.json +1 -0
- package/src/components/Survey/ResponsiveSurvey.tsx +14 -0
- package/src/components/Survey/SurveyDesktop.tsx +121 -0
- package/src/components/Survey/SurveyMobile.tsx +125 -0
- package/src/components/Survey/index.ts +5 -0
- package/src/components/Survey/types.ts +22 -0
- package/src/index.ts +23 -0
- package/src/ui/BottomSheet/BottomSheet.tsx +131 -0
- package/src/ui/BottomSheet/index.ts +2 -0
- package/src/ui/FeatureServiceUI/FeatureServiceUi.tsx +10 -8
- package/src/ui/Modal/Modal.tsx +92 -0
- package/src/ui/Modal/ModalHeader.tsx +58 -0
- package/src/ui/Modal/index.ts +4 -0
- package/src/ui/Survey/FeedbackBanner.tsx +36 -0
- package/src/ui/Survey/FeedbackTextarea.tsx +84 -0
- package/src/ui/Survey/HeartIcon.tsx +18 -0
- package/src/ui/Survey/ScoreButtons.tsx +91 -0
- package/src/ui/Survey/SurveyFooter.tsx +145 -0
- package/src/ui/Survey/SurveyHeader.tsx +72 -0
- package/src/ui/Survey/ThankYouCard.tsx +100 -0
- package/src/ui/Survey/constants.ts +59 -0
- package/src/ui/Survey/index.ts +9 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SurveyProps {
|
|
2
|
+
variant?: "mobile" | "desktop";
|
|
3
|
+
isOpen?: boolean;
|
|
4
|
+
selectedScore?: number | null;
|
|
5
|
+
onScoreChange?: (score: number) => void;
|
|
6
|
+
feedback?: string;
|
|
7
|
+
onFeedbackChange?: (text: string) => void;
|
|
8
|
+
onClose?: () => void;
|
|
9
|
+
onSkip?: () => void;
|
|
10
|
+
onSubmit?: (score: number, feedback: string) => void;
|
|
11
|
+
isSubmitted?: boolean;
|
|
12
|
+
isLoading?: boolean;
|
|
13
|
+
colors?: {
|
|
14
|
+
secondaryColor?: string;
|
|
15
|
+
tertiaryColor?: string;
|
|
16
|
+
primaryColor?: string;
|
|
17
|
+
};
|
|
18
|
+
icons?: {
|
|
19
|
+
surveyIcon?: string;
|
|
20
|
+
closeIcon?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,16 @@ import {
|
|
|
25
25
|
ResponsiveFilterBar,
|
|
26
26
|
} from "./components/FilterBar";
|
|
27
27
|
|
|
28
|
+
// Import Survey components
|
|
29
|
+
import {
|
|
30
|
+
SurveyDesktop,
|
|
31
|
+
SurveyMobile,
|
|
32
|
+
ResponsiveSurvey,
|
|
33
|
+
} from "./components/Survey";
|
|
34
|
+
|
|
35
|
+
// Import Modal components
|
|
36
|
+
import { Modal, ModalHeader } from "./ui/Modal";
|
|
37
|
+
|
|
28
38
|
export {
|
|
29
39
|
ServiceItemDesktop,
|
|
30
40
|
ServiceItemMobile,
|
|
@@ -46,6 +56,16 @@ export {
|
|
|
46
56
|
FilterBarDesktop,
|
|
47
57
|
FilterBarMobile,
|
|
48
58
|
ResponsiveFilterBar,
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
//Survey components
|
|
62
|
+
SurveyDesktop,
|
|
63
|
+
SurveyMobile,
|
|
64
|
+
ResponsiveSurvey,
|
|
65
|
+
|
|
66
|
+
// Modal components
|
|
67
|
+
Modal,
|
|
68
|
+
ModalHeader,
|
|
49
69
|
};
|
|
50
70
|
|
|
51
71
|
// Also export types
|
|
@@ -54,3 +74,6 @@ export type { MobileServiceItemProps } from "./components/ServiceItem/mobileType
|
|
|
54
74
|
export type { PaymentSideBarProps } from "./components/PaymentSideBar/types";
|
|
55
75
|
export type { ServiceListProps } from "./components/ServiceList/types";
|
|
56
76
|
export type { FilterBarProps } from "./components/FilterBar/tyoes";
|
|
77
|
+
export type { SurveyProps } from "./components/Survey/types";
|
|
78
|
+
export type { ModalProps, ModalVariant, ModalSize, ModalHeaderProps } from "./ui/Modal";
|
|
79
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
|
|
4
|
+
export interface BottomSheetProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose?: () => void;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
showHandle?: boolean;
|
|
9
|
+
showBackdrop?: boolean;
|
|
10
|
+
backdropColor?: string;
|
|
11
|
+
blurBackground?: boolean;
|
|
12
|
+
blurAmount?: string;
|
|
13
|
+
closeOnBackdrop?: boolean;
|
|
14
|
+
padding?: string | number;
|
|
15
|
+
maxHeight?: string;
|
|
16
|
+
borderRadius?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const STYLE_ID = "__kupos_bottom_sheet_style__";
|
|
20
|
+
|
|
21
|
+
const injectStyle = () => {
|
|
22
|
+
if (typeof document === "undefined") return;
|
|
23
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
24
|
+
const style = document.createElement("style");
|
|
25
|
+
style.id = STYLE_ID;
|
|
26
|
+
style.textContent = `
|
|
27
|
+
@keyframes __ks_slideUp {
|
|
28
|
+
from { transform: translateY(100%); }
|
|
29
|
+
to { transform: translateY(0); }
|
|
30
|
+
}
|
|
31
|
+
@keyframes __ks_fadeIn {
|
|
32
|
+
from { opacity: 0; }
|
|
33
|
+
to { opacity: 1; }
|
|
34
|
+
}
|
|
35
|
+
.__ks_sheet {
|
|
36
|
+
animation: __ks_slideUp 0.38s cubic-bezier(0.32, 0.72, 0, 1) both;
|
|
37
|
+
}
|
|
38
|
+
.__ks_backdrop {
|
|
39
|
+
animation: __ks_fadeIn 0.3s ease both;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
document.head.appendChild(style);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const BottomSheet = ({
|
|
46
|
+
isOpen,
|
|
47
|
+
onClose,
|
|
48
|
+
children,
|
|
49
|
+
showHandle = true,
|
|
50
|
+
showBackdrop = false,
|
|
51
|
+
backdropColor = "rgba(0,0,0,0.45)",
|
|
52
|
+
blurBackground = false,
|
|
53
|
+
blurAmount = "6px",
|
|
54
|
+
closeOnBackdrop = true,
|
|
55
|
+
padding = "20px 20px 32px",
|
|
56
|
+
maxHeight = "92vh",
|
|
57
|
+
borderRadius = "24px 24px 0 0",
|
|
58
|
+
}: BottomSheetProps) => {
|
|
59
|
+
if (!isOpen) return null;
|
|
60
|
+
|
|
61
|
+
injectStyle();
|
|
62
|
+
|
|
63
|
+
const sheet = (
|
|
64
|
+
<>
|
|
65
|
+
{(showBackdrop || blurBackground) && (
|
|
66
|
+
<div
|
|
67
|
+
className="__ks_backdrop"
|
|
68
|
+
onClick={closeOnBackdrop ? onClose : undefined}
|
|
69
|
+
style={{
|
|
70
|
+
position: "fixed",
|
|
71
|
+
top: 0,
|
|
72
|
+
left: 0,
|
|
73
|
+
right: 0,
|
|
74
|
+
bottom: 0,
|
|
75
|
+
zIndex: 9998,
|
|
76
|
+
backgroundColor: showBackdrop ? backdropColor : "rgba(0,0,0,0.4)",
|
|
77
|
+
backdropFilter: blurBackground ? `blur(${blurAmount})` : undefined,
|
|
78
|
+
WebkitBackdropFilter: blurBackground
|
|
79
|
+
? `blur(${blurAmount})`
|
|
80
|
+
: undefined,
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
className="__ks_sheet"
|
|
87
|
+
style={{
|
|
88
|
+
position: "fixed",
|
|
89
|
+
left: 0,
|
|
90
|
+
right: 0,
|
|
91
|
+
bottom: 0,
|
|
92
|
+
zIndex: 9999,
|
|
93
|
+
background: "#FFFFFF",
|
|
94
|
+
maxHeight,
|
|
95
|
+
borderRadius,
|
|
96
|
+
overflowY: "auto",
|
|
97
|
+
boxSizing: "border-box" as const,
|
|
98
|
+
boxShadow: "0 -8px 40px rgba(0,0,0,0.15)",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{showHandle && (
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
display: "flex",
|
|
105
|
+
justifyContent: "center",
|
|
106
|
+
padding: "12px 0 4px",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
width: 40,
|
|
112
|
+
height: 4,
|
|
113
|
+
borderRadius: 999,
|
|
114
|
+
backgroundColor: "#E5E7EB",
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
<div style={{ padding }}>{children}</div>
|
|
121
|
+
</div>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (typeof document !== "undefined") {
|
|
126
|
+
return ReactDOM.createPortal(sheet, document.body) as React.ReactElement;
|
|
127
|
+
}
|
|
128
|
+
return sheet;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default BottomSheet;
|
|
@@ -250,7 +250,7 @@ const FeatureServiceUi = ({
|
|
|
250
250
|
</div>
|
|
251
251
|
</div>
|
|
252
252
|
|
|
253
|
-
<div className="flex flex-col gap-[
|
|
253
|
+
<div className="flex flex-col gap-[10px]">
|
|
254
254
|
<div className="text-[12px] bold-text">
|
|
255
255
|
{travelDate
|
|
256
256
|
? new Date(travelDate).toLocaleDateString("es-CL", {
|
|
@@ -363,9 +363,8 @@ const FeatureServiceUi = ({
|
|
|
363
363
|
{/* MIDDLE: competing operators + viewers */}
|
|
364
364
|
<div className="min-w-0 px-[22px] flex flex-col items-center justify-between gap-[16px] py-[2px] border-r border-[#363c48] border-l border-[#363c48]">
|
|
365
365
|
<div className="text-center">
|
|
366
|
-
<div className="bold-text text-[
|
|
367
|
-
{operatorsCompetingCount} operadores compitiendo
|
|
368
|
-
<br /> por tu compra
|
|
366
|
+
<div className="bold-text text-[13px]">
|
|
367
|
+
{operatorsCompetingCount} operadores compitiendo por tu compra
|
|
369
368
|
</div>
|
|
370
369
|
</div>
|
|
371
370
|
|
|
@@ -377,7 +376,7 @@ const FeatureServiceUi = ({
|
|
|
377
376
|
style={{
|
|
378
377
|
// height: "80px",
|
|
379
378
|
border: "1px solid #363c48",
|
|
380
|
-
backgroundColor: "#
|
|
379
|
+
backgroundColor: "#fff",
|
|
381
380
|
padding: "14px 10px",
|
|
382
381
|
}}
|
|
383
382
|
>
|
|
@@ -393,13 +392,13 @@ const FeatureServiceUi = ({
|
|
|
393
392
|
isSoldOut ? "grayscale" : ""
|
|
394
393
|
}`}
|
|
395
394
|
/>
|
|
396
|
-
<span className="text-[11px] truncate max-w-full text-center">
|
|
395
|
+
<span className="text-[11px] truncate max-w-full text-center text-[#464647]">
|
|
397
396
|
{op.name}
|
|
398
397
|
</span>
|
|
399
398
|
<div className="bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
|
|
400
399
|
<span>{op?.time}</span>
|
|
401
400
|
</div>
|
|
402
|
-
<span className="text-[10px] mt-[6px]">
|
|
401
|
+
<span className="text-[10px] mt-[6px] text-[#464647]">
|
|
403
402
|
{op?.seatsAvailable}
|
|
404
403
|
</span>
|
|
405
404
|
</div>
|
|
@@ -407,10 +406,13 @@ const FeatureServiceUi = ({
|
|
|
407
406
|
</div>
|
|
408
407
|
|
|
409
408
|
<div
|
|
410
|
-
className="flex w-full items-center justify-center gap-[6px] text-[12px]"
|
|
409
|
+
className="flex w-full items-center justify-center gap-[6px] text-[12px] rounded-full"
|
|
411
410
|
style={{
|
|
412
411
|
padding: "8px 14px",
|
|
413
412
|
marginBottom: "6px",
|
|
413
|
+
border: "1px solid #363c48",
|
|
414
|
+
backgroundColor: "#1a202e",
|
|
415
|
+
// padding: "14px 10px",
|
|
414
416
|
}}
|
|
415
417
|
>
|
|
416
418
|
<LottiePlayer
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
|
|
4
|
+
export type ModalVariant = "center" | "bottom-sheet";
|
|
5
|
+
export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
|
|
6
|
+
|
|
7
|
+
export interface ModalProps {
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
variant?: ModalVariant;
|
|
12
|
+
size?: ModalSize;
|
|
13
|
+
closeOnBackdrop?: boolean;
|
|
14
|
+
padding?: string | number;
|
|
15
|
+
borderRadius?: string | number;
|
|
16
|
+
backdropColor?: string;
|
|
17
|
+
maxWidth?: number | string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SIZE_MAP: Record<ModalSize, number | string> = {
|
|
21
|
+
sm: 400,
|
|
22
|
+
md: 520,
|
|
23
|
+
lg: 640,
|
|
24
|
+
xl: 800,
|
|
25
|
+
full: "100%",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const Modal = ({
|
|
29
|
+
isOpen,
|
|
30
|
+
onClose,
|
|
31
|
+
children,
|
|
32
|
+
variant = "center",
|
|
33
|
+
size = "md",
|
|
34
|
+
closeOnBackdrop = true,
|
|
35
|
+
padding = "28px 32px 24px",
|
|
36
|
+
borderRadius,
|
|
37
|
+
backdropColor = "rgba(0,0,0,0.45)",
|
|
38
|
+
maxWidth,
|
|
39
|
+
}: ModalProps) => {
|
|
40
|
+
if (!isOpen) return null;
|
|
41
|
+
|
|
42
|
+
const resolvedMaxWidth = maxWidth ?? SIZE_MAP[size];
|
|
43
|
+
const resolvedRadius =
|
|
44
|
+
borderRadius ?? (variant === "bottom-sheet" ? "24px 24px 0 0" : 24);
|
|
45
|
+
|
|
46
|
+
const overlayStyle: React.CSSProperties = {
|
|
47
|
+
position: "fixed",
|
|
48
|
+
top: 0,
|
|
49
|
+
left: 0,
|
|
50
|
+
right: 0,
|
|
51
|
+
bottom: 0,
|
|
52
|
+
zIndex: 9999,
|
|
53
|
+
display: "flex",
|
|
54
|
+
alignItems: variant === "bottom-sheet" ? "flex-end" : "center",
|
|
55
|
+
justifyContent: "center",
|
|
56
|
+
padding: variant === "bottom-sheet" ? 0 : 16,
|
|
57
|
+
backgroundColor: backdropColor,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const cardStyle: React.CSSProperties = {
|
|
61
|
+
background: "#FFFFFF",
|
|
62
|
+
width: "100%",
|
|
63
|
+
maxWidth: resolvedMaxWidth,
|
|
64
|
+
maxHeight: variant === "bottom-sheet" ? "92vh" : undefined,
|
|
65
|
+
borderRadius: resolvedRadius,
|
|
66
|
+
padding,
|
|
67
|
+
position: "relative",
|
|
68
|
+
boxSizing: "border-box",
|
|
69
|
+
overflowY: variant === "bottom-sheet" ? "auto" : undefined,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const modal = (
|
|
73
|
+
<div
|
|
74
|
+
style={overlayStyle}
|
|
75
|
+
onClick={closeOnBackdrop ? onClose : undefined}
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
style={cardStyle}
|
|
79
|
+
onClick={(e) => e.stopPropagation()}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (typeof document !== "undefined") {
|
|
87
|
+
return ReactDOM.createPortal(modal, document.body) as React.ReactElement;
|
|
88
|
+
}
|
|
89
|
+
return modal;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Modal;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface ModalHeaderProps {
|
|
4
|
+
title?: React.ReactNode;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ModalHeader = ({ title, onClose, children }: ModalHeaderProps) => (
|
|
10
|
+
<div
|
|
11
|
+
style={{
|
|
12
|
+
display: "flex",
|
|
13
|
+
alignItems: "center",
|
|
14
|
+
justifyContent: "space-between",
|
|
15
|
+
marginBottom: 20,
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
<div style={{ flex: 1 }}>
|
|
19
|
+
{title && (
|
|
20
|
+
<span
|
|
21
|
+
style={{
|
|
22
|
+
fontSize: "1rem",
|
|
23
|
+
fontWeight: 700,
|
|
24
|
+
color: "#111827",
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{title}
|
|
28
|
+
</span>
|
|
29
|
+
)}
|
|
30
|
+
{children}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{onClose && (
|
|
34
|
+
<button
|
|
35
|
+
onClick={onClose}
|
|
36
|
+
style={{
|
|
37
|
+
width: 36,
|
|
38
|
+
height: 36,
|
|
39
|
+
borderRadius: "50%",
|
|
40
|
+
background: "#F3F4F6",
|
|
41
|
+
border: "none",
|
|
42
|
+
cursor: "pointer",
|
|
43
|
+
fontSize: 14,
|
|
44
|
+
color: "#6B7280",
|
|
45
|
+
display: "flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
flexShrink: 0,
|
|
49
|
+
marginLeft: 12,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
✕
|
|
53
|
+
</button>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export default ModalHeader;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FeedbackConfig } from "./constants";
|
|
3
|
+
|
|
4
|
+
interface FeedbackBannerProps {
|
|
5
|
+
config: FeedbackConfig | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const FeedbackBanner = ({ config }: FeedbackBannerProps) => {
|
|
9
|
+
if (!config) return null;
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
style={{
|
|
13
|
+
display: "flex",
|
|
14
|
+
alignItems: "center",
|
|
15
|
+
gap: 12,
|
|
16
|
+
borderRadius: 20,
|
|
17
|
+
padding: "12px 16px",
|
|
18
|
+
marginTop: 16,
|
|
19
|
+
backgroundColor: config.bannerBg,
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<span style={{ fontSize: 22 }}>{config.emoji}</span>
|
|
23
|
+
<span
|
|
24
|
+
style={{
|
|
25
|
+
fontWeight: 600,
|
|
26
|
+
fontSize: "13.33px",
|
|
27
|
+
color: config.bannerText,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{config.message}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default FeedbackBanner;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FeedbackConfig, MAX_CHARS } from "./constants";
|
|
3
|
+
|
|
4
|
+
interface FeedbackTextareaProps {
|
|
5
|
+
config: FeedbackConfig | null;
|
|
6
|
+
feedback: string;
|
|
7
|
+
onFeedbackChange?: (text: string) => void;
|
|
8
|
+
colors?: {
|
|
9
|
+
primaryColor?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FeedbackTextarea = ({
|
|
14
|
+
config,
|
|
15
|
+
feedback,
|
|
16
|
+
onFeedbackChange,
|
|
17
|
+
colors,
|
|
18
|
+
}: FeedbackTextareaProps) => {
|
|
19
|
+
if (!config) return null;
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ position: "relative", marginTop: 42 }}>
|
|
22
|
+
<span
|
|
23
|
+
style={{
|
|
24
|
+
position: "absolute",
|
|
25
|
+
left: 16,
|
|
26
|
+
top: -10,
|
|
27
|
+
background: "#FFFFFF",
|
|
28
|
+
padding: "0 8px",
|
|
29
|
+
fontSize: "13.33px",
|
|
30
|
+
fontWeight: 600,
|
|
31
|
+
color: colors?.primaryColor || "#374151",
|
|
32
|
+
zIndex: 1,
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{config.question}
|
|
36
|
+
</span>
|
|
37
|
+
|
|
38
|
+
<div
|
|
39
|
+
style={{
|
|
40
|
+
border: "1.5px solid #E5E7EB",
|
|
41
|
+
borderRadius: 16,
|
|
42
|
+
overflow: "hidden",
|
|
43
|
+
position: "relative",
|
|
44
|
+
background: "transparent",
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<textarea
|
|
48
|
+
value={feedback}
|
|
49
|
+
onChange={(e) => {
|
|
50
|
+
if (e.target.value.length <= MAX_CHARS)
|
|
51
|
+
onFeedbackChange?.(e.target.value);
|
|
52
|
+
}}
|
|
53
|
+
placeholder="Déjanos tus comentarios (opcional)"
|
|
54
|
+
rows={4}
|
|
55
|
+
style={{
|
|
56
|
+
width: "100%",
|
|
57
|
+
padding: "16px 16px 28px",
|
|
58
|
+
background: "transparent",
|
|
59
|
+
color: "#374151",
|
|
60
|
+
fontSize: "13.33px",
|
|
61
|
+
resize: "none",
|
|
62
|
+
outline: "none",
|
|
63
|
+
border: "none",
|
|
64
|
+
boxSizing: "border-box" as const,
|
|
65
|
+
fontFamily: "inherit",
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
<div
|
|
69
|
+
style={{
|
|
70
|
+
position: "absolute",
|
|
71
|
+
bottom: 8,
|
|
72
|
+
right: 16,
|
|
73
|
+
fontSize: 12,
|
|
74
|
+
color: "#9CA3AF",
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{feedback.length}/{MAX_CHARS}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default FeedbackTextarea;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const HeartIcon = () => (
|
|
4
|
+
<svg
|
|
5
|
+
width="28"
|
|
6
|
+
height="28"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="#22c55e"
|
|
10
|
+
strokeWidth="1.8"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default HeartIcon;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { getZoneColor, getZoneShadow } from "./constants";
|
|
3
|
+
|
|
4
|
+
interface ScoreButtonsProps {
|
|
5
|
+
selectedScore?: number | null;
|
|
6
|
+
onScoreChange?: (score: number) => void;
|
|
7
|
+
buttonHeight?: number;
|
|
8
|
+
fontSize?: number;
|
|
9
|
+
gap?: number;
|
|
10
|
+
colors?: {
|
|
11
|
+
secondaryColor?: string;
|
|
12
|
+
primaryColor?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ScoreButtons = ({
|
|
17
|
+
selectedScore,
|
|
18
|
+
onScoreChange,
|
|
19
|
+
buttonHeight = 54,
|
|
20
|
+
fontSize = 15,
|
|
21
|
+
gap = 8,
|
|
22
|
+
colors,
|
|
23
|
+
}: ScoreButtonsProps) => (
|
|
24
|
+
<div style={{ marginBottom: 4 }}>
|
|
25
|
+
<div
|
|
26
|
+
style={{
|
|
27
|
+
display: "grid",
|
|
28
|
+
gridTemplateColumns: "repeat(10, 1fr)",
|
|
29
|
+
gap,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{Array.from({ length: 10 }, (_, i) => i + 1).map((num) => {
|
|
33
|
+
const isSelected = selectedScore === num;
|
|
34
|
+
const zoneColor = getZoneColor(num);
|
|
35
|
+
const activeColor = colors?.secondaryColor || zoneColor;
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
key={num}
|
|
39
|
+
onClick={() => onScoreChange?.(num)}
|
|
40
|
+
onMouseEnter={(e) => {
|
|
41
|
+
if (!isSelected) {
|
|
42
|
+
e.currentTarget.style.borderColor = activeColor;
|
|
43
|
+
e.currentTarget.style.color = activeColor;
|
|
44
|
+
}
|
|
45
|
+
}}
|
|
46
|
+
onMouseLeave={(e) => {
|
|
47
|
+
if (!isSelected) {
|
|
48
|
+
e.currentTarget.style.borderColor = "#E5E7EB";
|
|
49
|
+
e.currentTarget.style.color = "#111827";
|
|
50
|
+
}
|
|
51
|
+
}}
|
|
52
|
+
style={{
|
|
53
|
+
height: buttonHeight,
|
|
54
|
+
borderRadius: 10,
|
|
55
|
+
fontSize,
|
|
56
|
+
border: isSelected ? "none" : "1px solid #E5E7EB",
|
|
57
|
+
background: isSelected ? activeColor : "#FFFFFF",
|
|
58
|
+
color: isSelected ? "#FFFFFF" : "#111827",
|
|
59
|
+
cursor: "pointer",
|
|
60
|
+
transition: "border-color 0.15s, color 0.15s, background 0.15s",
|
|
61
|
+
boxShadow: isSelected
|
|
62
|
+
? colors?.secondaryColor
|
|
63
|
+
? `0 2px 8px ${colors.secondaryColor}58`
|
|
64
|
+
: getZoneShadow(num)
|
|
65
|
+
: "none",
|
|
66
|
+
display: "flex",
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
justifyContent: "center",
|
|
69
|
+
boxSizing: "border-box" as const,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{num}
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div
|
|
79
|
+
style={{
|
|
80
|
+
display: "flex",
|
|
81
|
+
justifyContent: "space-between",
|
|
82
|
+
marginTop: 8,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<span style={{ fontSize: 12, color: "#9CA3AF" }}>Poco probable</span>
|
|
86
|
+
<span style={{ fontSize: 12, color: "#9CA3AF" }}>Muy probable</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export default ScoreButtons;
|