kupos-ui-components-lib 9.10.1 → 9.10.2
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/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/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
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;
|
|
@@ -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;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BRAND } from "./constants";
|
|
3
|
+
|
|
4
|
+
interface SurveyFooterProps {
|
|
5
|
+
selectedScore?: number | null;
|
|
6
|
+
onSkip?: () => void;
|
|
7
|
+
onSubmit: () => void;
|
|
8
|
+
layout?: "inline" | "stacked";
|
|
9
|
+
colors?: {
|
|
10
|
+
secondaryColor?: string;
|
|
11
|
+
tertiaryColor?: string;
|
|
12
|
+
primaryColor?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SurveyFooter = ({
|
|
17
|
+
selectedScore,
|
|
18
|
+
onSkip,
|
|
19
|
+
onSubmit,
|
|
20
|
+
layout = "inline",
|
|
21
|
+
colors,
|
|
22
|
+
}: SurveyFooterProps) => {
|
|
23
|
+
if (layout === "stacked") {
|
|
24
|
+
return (
|
|
25
|
+
<div style={{ marginTop: 24 }}>
|
|
26
|
+
<button
|
|
27
|
+
onClick={onSkip}
|
|
28
|
+
style={{
|
|
29
|
+
display: "block",
|
|
30
|
+
width: "100%",
|
|
31
|
+
textAlign: "center",
|
|
32
|
+
fontSize: "13.33px",
|
|
33
|
+
color: "#6B7280",
|
|
34
|
+
background: "none",
|
|
35
|
+
border: "none",
|
|
36
|
+
cursor: "pointer",
|
|
37
|
+
padding: "0 0 14px",
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
Saltar por ahora
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
<button
|
|
44
|
+
onClick={onSubmit}
|
|
45
|
+
disabled={selectedScore == null}
|
|
46
|
+
style={{
|
|
47
|
+
display: "flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
justifyContent: "center",
|
|
50
|
+
gap: 8,
|
|
51
|
+
width: "100%",
|
|
52
|
+
padding: "16px",
|
|
53
|
+
borderRadius: 999,
|
|
54
|
+
fontSize: "13.33px",
|
|
55
|
+
fontWeight: 700,
|
|
56
|
+
letterSpacing: "0.08em",
|
|
57
|
+
textTransform: "uppercase" as const,
|
|
58
|
+
border: "none",
|
|
59
|
+
cursor: selectedScore != null ? "pointer" : "not-allowed",
|
|
60
|
+
background:
|
|
61
|
+
selectedScore != null
|
|
62
|
+
? colors?.secondaryColor
|
|
63
|
+
: "rgba(232,76,13,0.28)",
|
|
64
|
+
color: "#FFFFFF",
|
|
65
|
+
transition: "background 0.15s",
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
Enviar comentarios <span>→</span>
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
display: "flex",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
justifyContent: "space-between",
|
|
81
|
+
marginTop: 24,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<button
|
|
85
|
+
onClick={onSkip}
|
|
86
|
+
style={{
|
|
87
|
+
fontSize: "13.33px",
|
|
88
|
+
color: "#6B7280",
|
|
89
|
+
background: "none",
|
|
90
|
+
border: "none",
|
|
91
|
+
cursor: "pointer",
|
|
92
|
+
padding: 0,
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
Saltar por ahora
|
|
96
|
+
</button>
|
|
97
|
+
|
|
98
|
+
<button
|
|
99
|
+
onClick={onSubmit}
|
|
100
|
+
disabled={selectedScore == null}
|
|
101
|
+
style={{
|
|
102
|
+
display: "flex",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
gap: 8,
|
|
105
|
+
padding: "14px 28px",
|
|
106
|
+
borderRadius: 999,
|
|
107
|
+
fontSize: "13.33px",
|
|
108
|
+
fontWeight: 700,
|
|
109
|
+
letterSpacing: "0.08em",
|
|
110
|
+
textTransform: "uppercase" as const,
|
|
111
|
+
border: "none",
|
|
112
|
+
cursor: selectedScore != null ? "pointer" : "not-allowed",
|
|
113
|
+
background:
|
|
114
|
+
selectedScore != null
|
|
115
|
+
? colors?.secondaryColor
|
|
116
|
+
: "rgba(232,76,13,0.28)",
|
|
117
|
+
color: "#FFFFFF",
|
|
118
|
+
transition: "background 0.15s",
|
|
119
|
+
minWidth: 160,
|
|
120
|
+
justifyContent: "center",
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Enviar comentarios <span>→</span>
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div
|
|
128
|
+
style={{
|
|
129
|
+
display: "flex",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
justifyContent: "center",
|
|
132
|
+
gap: 6,
|
|
133
|
+
marginTop: 14,
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
color: "#9CA3AF",
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<span>🔒</span>
|
|
139
|
+
<span>Anónimo · solo se usa para mejorar el servicio</span>
|
|
140
|
+
</div>
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default SurveyFooter;
|