@crestbase/web-shared-ui 1.0.0 → 1.0.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/README.md +119 -6
- package/dist/components/button/CircleBtn.d.ts +9 -0
- package/dist/components/button/CircleBtn.js +5 -0
- package/dist/components/button/ShadowButton.d.ts +8 -0
- package/dist/components/button/ShadowButton.js +5 -0
- package/dist/components/button/index.d.ts +12 -3
- package/dist/components/button/index.js +17 -3
- package/dist/components/core/Accordion.d.ts +11 -0
- package/dist/components/core/Accordion.js +7 -0
- package/dist/components/core/AddressInput.d.ts +34 -0
- package/dist/components/core/AddressInput.js +88 -0
- package/dist/components/core/BottomSheet.d.ts +10 -0
- package/dist/components/core/BottomSheet.js +15 -0
- package/dist/components/core/Calendar.d.ts +9 -0
- package/dist/components/core/Calendar.js +98 -0
- package/dist/components/core/CalendarExtended.d.ts +27 -0
- package/dist/components/core/CalendarExtended.js +147 -0
- package/dist/components/core/Checkbox.d.ts +13 -0
- package/dist/components/core/Checkbox.js +10 -0
- package/dist/components/core/CustomDropdown.d.ts +19 -0
- package/dist/components/core/CustomDropdown.js +41 -0
- package/dist/components/core/Modal.d.ts +12 -0
- package/dist/components/core/Modal.js +12 -0
- package/dist/components/core/NumberInput.d.ts +12 -0
- package/dist/components/core/NumberInput.js +34 -0
- package/dist/components/core/NumberInputWithCommas.d.ts +10 -0
- package/dist/components/core/NumberInputWithCommas.js +19 -0
- package/dist/components/core/OptionsDropdown.d.ts +17 -0
- package/dist/components/core/OptionsDropdown.js +44 -0
- package/dist/components/core/OtpInput.d.ts +9 -0
- package/dist/components/core/OtpInput.js +54 -0
- package/dist/components/core/PreviewArea.d.ts +18 -0
- package/dist/components/core/PreviewArea.js +31 -0
- package/dist/components/core/SelectionList.d.ts +16 -0
- package/dist/components/core/SelectionList.js +9 -0
- package/dist/components/core/SquareCheckbox.d.ts +10 -0
- package/dist/components/core/SquareCheckbox.js +5 -0
- package/dist/components/core/TabNavigation.d.ts +19 -0
- package/dist/components/core/TabNavigation.js +11 -0
- package/dist/components/core/Textarea.d.ts +10 -0
- package/dist/components/core/Textarea.js +8 -0
- package/dist/components/core/Toggle.d.ts +10 -0
- package/dist/components/core/Toggle.js +11 -0
- package/dist/components/core/Upload.d.ts +28 -0
- package/dist/components/core/Upload.js +76 -0
- package/dist/components/core/UploadArea.d.ts +14 -0
- package/dist/components/core/UploadArea.js +13 -0
- package/dist/components/core/input.d.ts +11 -0
- package/dist/components/core/input.js +24 -0
- package/dist/components/index.d.ts +41 -1
- package/dist/components/index.js +25 -1
- package/dist/components/navigation/Logo.d.ts +7 -0
- package/dist/components/navigation/Logo.js +7 -0
- package/dist/components/notification/GlobalNotificationProvider.d.ts +4 -0
- package/dist/components/notification/GlobalNotificationProvider.js +6 -0
- package/dist/components/notification/NotificationPopup.d.ts +1 -0
- package/dist/components/notification/NotificationPopup.js +73 -0
- package/dist/components/notification/notificationStore.d.ts +15 -0
- package/dist/components/notification/notificationStore.js +12 -0
- package/dist/components/svg/Crestbase.d.ts +6 -0
- package/dist/components/svg/Crestbase.js +5 -0
- package/dist/components/svg/CrestbaseColored.d.ts +6 -0
- package/dist/components/svg/CrestbaseColored.js +5 -0
- package/dist/components/svg/UploadIcon.d.ts +5 -0
- package/dist/components/svg/UploadIcon.js +4 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/useFileUpload.d.ts +35 -0
- package/dist/hooks/useFileUpload.js +219 -0
- package/dist/hooks/useFileUploadWithNotifications.d.ts +36 -0
- package/dist/hooks/useFileUploadWithNotifications.js +39 -0
- package/dist/hooks/useResponsive.d.ts +38 -0
- package/dist/hooks/useResponsive.js +97 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/styles.css +2 -0
- package/dist/types/property.d.ts +17 -0
- package/dist/types/property.js +2 -0
- package/dist/utils/fileValidation.d.ts +21 -0
- package/dist/utils/fileValidation.js +82 -0
- package/dist/utils/googleMapsLoader.d.ts +40 -0
- package/dist/utils/googleMapsLoader.js +108 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +13 -0
- package/package.json +22 -12
- package/dist/components/button/index.d.ts.map +0 -1
- package/dist/components/button/index.js.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState, useRef } from "react";
|
|
4
|
+
import Calendar from "./Calendar";
|
|
5
|
+
const monthNames = [
|
|
6
|
+
"January",
|
|
7
|
+
"February",
|
|
8
|
+
"March",
|
|
9
|
+
"April",
|
|
10
|
+
"May",
|
|
11
|
+
"June",
|
|
12
|
+
"July",
|
|
13
|
+
"August",
|
|
14
|
+
"September",
|
|
15
|
+
"October",
|
|
16
|
+
"November",
|
|
17
|
+
"December",
|
|
18
|
+
];
|
|
19
|
+
export default function CalendarExtended({ selectionGranularity = "day", allowRange = false, startYear, endYear, selectedMonths = [], onMonthSelectionChange, onMonthRangeChange, minSelectableMonth, maxSelectableMonth, disableFutureMonths, containerHeight = 560, enableYearTransition: _enableYearTransition = true, selectedDates = [], onDateSelect, bookedDates, maxSelections, }) {
|
|
20
|
+
// Derive year bounds
|
|
21
|
+
const currentYear = new Date().getFullYear();
|
|
22
|
+
const DEFAULT_START_YEAR = 1900;
|
|
23
|
+
const DEFAULT_END_YEAR = disableFutureMonths ? currentYear : currentYear + 50;
|
|
24
|
+
const fromYear = startYear ?? minSelectableMonth?.year ?? DEFAULT_START_YEAR;
|
|
25
|
+
const toYear = endYear ?? maxSelectableMonth?.year ?? DEFAULT_END_YEAR;
|
|
26
|
+
// Internal range state if consumer enables range
|
|
27
|
+
const [rangeStart, setRangeStart] = useState(null);
|
|
28
|
+
const [rangeEnd, setRangeEnd] = useState(null);
|
|
29
|
+
// Accessibility: grid id for aria-controls
|
|
30
|
+
const gridIdRef = useRef(`calendar-extended-grid-${Math.random().toString(36).slice(2)}`);
|
|
31
|
+
const allMonths = useMemo(() => {
|
|
32
|
+
const list = [];
|
|
33
|
+
for (let y = fromYear; y <= toYear; y++) {
|
|
34
|
+
for (let m = 0; m < 12; m++) {
|
|
35
|
+
list.push({ year: y, month: m });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return list;
|
|
39
|
+
}, [fromYear, toYear]);
|
|
40
|
+
const yearsList = useMemo(() => {
|
|
41
|
+
return Array.from({ length: toYear - fromYear + 1 }, (_, i) => fromYear + i);
|
|
42
|
+
}, [fromYear, toYear]);
|
|
43
|
+
const isSelected = (y, m) => selectedMonths.some((sel) => sel.year === y && sel.month === m);
|
|
44
|
+
const isInRange = (y, m) => {
|
|
45
|
+
if (!rangeStart || !rangeEnd)
|
|
46
|
+
return false;
|
|
47
|
+
const startKey = rangeStart.year * 12 + rangeStart.month;
|
|
48
|
+
const endKey = rangeEnd.year * 12 + rangeEnd.month;
|
|
49
|
+
const currentKey = y * 12 + m;
|
|
50
|
+
const [minKey, maxKey] = startKey <= endKey ? [startKey, endKey] : [endKey, startKey];
|
|
51
|
+
return currentKey >= minKey && currentKey <= maxKey;
|
|
52
|
+
};
|
|
53
|
+
const isMonthDisabled = (y, m) => {
|
|
54
|
+
const key = y * 12 + m;
|
|
55
|
+
const minKey = minSelectableMonth
|
|
56
|
+
? minSelectableMonth.year * 12 + minSelectableMonth.month
|
|
57
|
+
: null;
|
|
58
|
+
const maxKey = maxSelectableMonth
|
|
59
|
+
? maxSelectableMonth.year * 12 + maxSelectableMonth.month
|
|
60
|
+
: null;
|
|
61
|
+
const today = new Date();
|
|
62
|
+
const currentKey = today.getFullYear() * 12 + today.getMonth();
|
|
63
|
+
const futureDisabled = disableFutureMonths ? key > currentKey : false;
|
|
64
|
+
return ((minKey !== null && key < minKey) ||
|
|
65
|
+
(maxKey !== null && key > maxKey) ||
|
|
66
|
+
futureDisabled);
|
|
67
|
+
};
|
|
68
|
+
const handleMonthClick = (y, m) => {
|
|
69
|
+
const clicked = { year: y, month: m };
|
|
70
|
+
// Respect disabled constraints
|
|
71
|
+
if (isMonthDisabled(y, m)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (allowRange) {
|
|
75
|
+
if (!rangeStart) {
|
|
76
|
+
setRangeStart(clicked);
|
|
77
|
+
setRangeEnd(null);
|
|
78
|
+
onMonthRangeChange?.({ start: clicked, end: clicked });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// If start exists but end not set, set end
|
|
82
|
+
if (!rangeEnd) {
|
|
83
|
+
setRangeEnd(clicked);
|
|
84
|
+
onMonthRangeChange?.({ start: rangeStart, end: clicked });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Reset range on third click
|
|
88
|
+
setRangeStart(clicked);
|
|
89
|
+
setRangeEnd(null);
|
|
90
|
+
onMonthRangeChange?.({ start: clicked, end: clicked });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Single-month selection mode
|
|
94
|
+
const exists = isSelected(y, m);
|
|
95
|
+
let next = selectedMonths;
|
|
96
|
+
if (exists) {
|
|
97
|
+
next = selectedMonths.filter((sel) => !(sel.year === y && sel.month === m));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
next = [...selectedMonths, clicked];
|
|
101
|
+
}
|
|
102
|
+
onMonthSelectionChange?.(next);
|
|
103
|
+
};
|
|
104
|
+
// Show all years within computed bounds, even if all months are disabled
|
|
105
|
+
const visibleYears = yearsList;
|
|
106
|
+
const renderYearCard = (y) => {
|
|
107
|
+
const yearLabelId = `year-label-${y}-${gridIdRef.current}`;
|
|
108
|
+
return (_jsxs("div", { className: "p-4 transition-all duration-300 ease-out", "aria-labelledby": yearLabelId, children: [_jsx("div", { id: yearLabelId, className: "text-xs font-semibold mb-3", children: y }), _jsx("div", { role: "grid", "aria-label": `Months of ${y}`, className: "grid grid-cols-3 sm:grid-cols-4 gap-2", children: monthNames.map((name, m) => {
|
|
109
|
+
const selected = isSelected(y, m);
|
|
110
|
+
const inRange = isInRange(y, m);
|
|
111
|
+
const disabled = isMonthDisabled(y, m);
|
|
112
|
+
const isStart = !!rangeStart && rangeStart.year === y && rangeStart.month === m;
|
|
113
|
+
const isEnd = !!rangeEnd && rangeEnd.year === y && rangeEnd.month === m;
|
|
114
|
+
const isCurrent = (() => {
|
|
115
|
+
const d = new Date();
|
|
116
|
+
return d.getFullYear() === y && d.getMonth() === m;
|
|
117
|
+
})();
|
|
118
|
+
const isSinglePreRange = allowRange && !!rangeStart && !rangeEnd && isStart;
|
|
119
|
+
const baseClasses = "text-[10px] p-2 cursor-pointer rounded-lg transition-all duration-200 ease-out focus:outline-none focus-visible:ring-2 focus-visible:ring-primaryblue/60";
|
|
120
|
+
const stateClasses = disabled
|
|
121
|
+
? "opacity-50 cursor-not-allowed bg-gray-50 text-gray-400"
|
|
122
|
+
: isStart && isEnd
|
|
123
|
+
? "bg-primaryblue/20 border-primaryblue text-white"
|
|
124
|
+
: isStart
|
|
125
|
+
? "bg-primaryblue/20 text-primaryblue ring-2 ring-primaryblue"
|
|
126
|
+
: isEnd
|
|
127
|
+
? "bg-primaryblue/20 text-primaryblue ring-2 ring-primaryblue/70"
|
|
128
|
+
: inRange
|
|
129
|
+
? "bg-primaryblue/10 text-primaryblue"
|
|
130
|
+
: isSinglePreRange || selected
|
|
131
|
+
? "bg-primaryblue/5 text-primaryblue"
|
|
132
|
+
: "bg-gray-100 hover:bg-gray-50";
|
|
133
|
+
const ariaSelected = selected || inRange || isStart || isEnd || isSinglePreRange;
|
|
134
|
+
return (_jsx("button", { type: "button", role: "gridcell", "aria-pressed": ariaSelected, "aria-disabled": disabled, "aria-selected": ariaSelected, "aria-current": isCurrent ? "date" : undefined, disabled: disabled, tabIndex: disabled ? -1 : 0, onClick: () => handleMonthClick(y, m), className: `${baseClasses} ${stateClasses}`, "aria-label": `${name} ${y}`, children: name }, `${y}-${m}`));
|
|
135
|
+
}) })] }, y));
|
|
136
|
+
};
|
|
137
|
+
if (selectionGranularity === "month") {
|
|
138
|
+
const containerStyle = {
|
|
139
|
+
maxHeight: containerHeight,
|
|
140
|
+
overflowY: "auto",
|
|
141
|
+
};
|
|
142
|
+
// Scroll mode only: show all years in a single-column, scrollable grid
|
|
143
|
+
return (_jsx("div", { id: gridIdRef.current, className: `grid grid-cols-1 md:grid-cols-1 xl:grid-cols-1`, style: containerStyle, role: "grid", "aria-label": "Years grid", children: visibleYears.map((y) => renderYearCard(y)) }));
|
|
144
|
+
}
|
|
145
|
+
// Default: day selection using existing Calendar (backward-compatible)
|
|
146
|
+
return (_jsx(Calendar, { selectedDates: selectedDates, onDateSelect: onDateSelect ?? (() => { }), bookedDates: bookedDates, maxSelections: maxSelections }));
|
|
147
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface CheckboxProps {
|
|
3
|
+
checked: boolean;
|
|
4
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
5
|
+
label?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const _default: (props: CheckboxProps) => React.JSX.Element;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const Checkbox = ({ checked, onChange, label, name, required = false, disabled = false, error, className = "", }) => {
|
|
4
|
+
return (_jsxs("div", { className: `w-full text-sm ${className}`, children: [label && (_jsx("label", { htmlFor: name, className: "block mb-2 text-xs text-gray-500", children: label })), _jsx("div", { className: "relative", children: _jsxs("div", { className: "w-full p-0 px-3 h-13.5 sm:h-auto sm:p-3 border border-gray-200 rounded-xl bg-gray-50 focus-within:border-gray-300 focus-within:bg-white flex items-center justify-between", children: [_jsx("span", { className: "text-base sm:text-xs text-gray-400", children: checked ? "Yes" : "No" }), _jsxs("label", { className: "relative cursor-pointer", children: [_jsx("input", { id: name, name: name, type: "checkbox", required: required, disabled: disabled, checked: checked, onChange: onChange, className: "sr-only", autoComplete: "off" }), _jsx("div", { className: [
|
|
5
|
+
"w-6 h-6 rounded-lg border flex items-center justify-center transition-colors",
|
|
6
|
+
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
7
|
+
checked ? "bg-black border-black" : "bg-white border-gray-300",
|
|
8
|
+
].join(" "), children: checked && (_jsx("svg", { className: "w-3.5 h-3.5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 6L9 17l-5-5" }) })) })] })] }) }), error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: error })] }));
|
|
9
|
+
};
|
|
10
|
+
export default Checkbox;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface DropdownOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CustomDropdownProps {
|
|
7
|
+
label?: string;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
options: DropdownOption[];
|
|
10
|
+
value?: string;
|
|
11
|
+
onChange: (value: string) => void;
|
|
12
|
+
error?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
labelClass?: string;
|
|
15
|
+
minimal?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const _default: (props: CustomDropdownProps) => React.JSX.Element;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { ChevronDownIcon, X } from "lucide-react";
|
|
5
|
+
const CustomDropdown = ({ label, placeholder = "Select an option", options, value, onChange, error, className = "", labelClass = "", minimal, disabled = false, }) => {
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const dropdownRef = useRef(null);
|
|
8
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const handleClickOutside = (event) => {
|
|
11
|
+
if (dropdownRef.current &&
|
|
12
|
+
!dropdownRef.current.contains(event.target)) {
|
|
13
|
+
setIsOpen(false);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
17
|
+
return () => {
|
|
18
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
21
|
+
const handleOptionClick = (optionValue) => {
|
|
22
|
+
if (disabled)
|
|
23
|
+
return;
|
|
24
|
+
onChange(optionValue);
|
|
25
|
+
setIsOpen(false);
|
|
26
|
+
};
|
|
27
|
+
const handleClear = (e) => {
|
|
28
|
+
if (disabled)
|
|
29
|
+
return;
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
onChange("");
|
|
32
|
+
setIsOpen(false);
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs("div", { className: `w-full ${minimal ? "text-[11px]" : "text-base sm:text-xs"} ${className}`, ref: dropdownRef, children: [label && (_jsx("label", { className: `${labelClass} block mb-2 text-[10px] text-gray-500`, children: label })), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `${minimal
|
|
35
|
+
? "flex gap-1 items-center"
|
|
36
|
+
: "w-full p-0 px-4 sm:p-4 rounded-xl bg-gray-50 border outline-none text-left flex items-center justify-between h-13.5 sm:h-auto"} ${error ? "border-red-500" : "border-gray-200"} ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`, children: [_jsx("span", { className: `${selectedOption ? "text-gray-900" : "text-gray-400"}`, children: selectedOption ? selectedOption.label : placeholder }), !disabled && (_jsx(ChevronDownIcon, { className: `w-4 h-4 text-gray-800 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}` }))] }), isOpen && !disabled && (_jsx("div", { className: "absolute z-50 min-w-full mt-1 bg-white border border-gray-200 rounded-xl shadow-lg max-h-60 overflow-y-auto", children: options.map((option) => (_jsxs("div", { className: `flex items-center justify-between hover:bg-gray-50 transition-colors ${value === option.value
|
|
37
|
+
? "bg-blue-50 text-blue-600"
|
|
38
|
+
: "text-gray-900"} first:rounded-t-xl last:rounded-b-xl`, children: [_jsx("button", { type: "button", onClick: () => handleOptionClick(option.value), className: "flex-1 text-left text-nowrap px-4 py-3", children: option.label }), value === option.value && (_jsx("button", { type: "button", onClick: handleClear, className: "p-0.5 hover:bg-gray-100 rounded-full transition-colors ml-2", children: _jsx(X, { className: "w-3 h-3 text-gray-400 hover:text-gray-600" }) }))] }, option.value))) }))] }), error && _jsx("p", { className: "mt-1 text-red-500", children: error })] }));
|
|
39
|
+
};
|
|
40
|
+
CustomDropdown.displayName = "CustomDropdown";
|
|
41
|
+
export default CustomDropdown;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface ModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
allowBackdropClose?: boolean;
|
|
7
|
+
blurClass?: string;
|
|
8
|
+
containerClassName?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const _default: (props: ModalProps) => React.JSX.Element;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const Modal = ({ isOpen, onClose, children, allowBackdropClose = true, blurClass = "backdrop-blur-xs", containerClassName = "z-50 mx-auto w-full flex justify-center items-start text-black", className = "py-12", }) => {
|
|
4
|
+
if (!isOpen)
|
|
5
|
+
return null;
|
|
6
|
+
return (_jsxs("div", { className: `fixed inset-0 z-[80] flex items-start justify-center ${className}`, children: [_jsx("button", { className: `absolute inset-0 bg-black/70 ${blurClass}`, onClick: allowBackdropClose ? onClose : undefined }), _jsx("div", { role: "dialog", className: containerClassName, onClick: (e) => {
|
|
7
|
+
if (e.target === e.currentTarget && allowBackdropClose) {
|
|
8
|
+
onClose();
|
|
9
|
+
}
|
|
10
|
+
}, children: children })] }));
|
|
11
|
+
};
|
|
12
|
+
export default Modal;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "type"> {
|
|
3
|
+
label?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
onIncrement?: () => void;
|
|
7
|
+
onDecrement?: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: (props: NumberInputProps & {
|
|
10
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
11
|
+
}) => React.JSX.Element;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
5
|
+
const NumberInput = forwardRef(({ label, name, placeholder, required = false, error, disabled = false, autoFocus = false, className = "", value, onChange, onIncrement, onDecrement, ...props }, ref) => {
|
|
6
|
+
const handleIncrement = () => {
|
|
7
|
+
if (onIncrement) {
|
|
8
|
+
onIncrement();
|
|
9
|
+
}
|
|
10
|
+
else if (onChange) {
|
|
11
|
+
const currentValue = Number(value) || 0;
|
|
12
|
+
const event = {
|
|
13
|
+
target: { value: String(currentValue + 1) },
|
|
14
|
+
};
|
|
15
|
+
onChange(event);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const handleDecrement = () => {
|
|
19
|
+
if (onDecrement) {
|
|
20
|
+
onDecrement();
|
|
21
|
+
}
|
|
22
|
+
else if (onChange) {
|
|
23
|
+
const currentValue = Number(value) || 0;
|
|
24
|
+
const newValue = Math.max(0, currentValue - 1);
|
|
25
|
+
const event = {
|
|
26
|
+
target: { value: String(newValue) },
|
|
27
|
+
};
|
|
28
|
+
onChange(event);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs("div", { className: `w-full text-sm ${className}`, children: [label && (_jsx("label", { htmlFor: name, className: "block mb-2 text-xs text-gray-500", children: label })), _jsxs("div", { className: "relative", children: [_jsx("input", { ref: ref, id: name, name: name, type: "number", placeholder: placeholder, required: required, disabled: disabled, autoFocus: autoFocus, value: value, onChange: onChange, autoComplete: "off", className: `w-full text-base sm:text-xs h-13.5 sm:h-auto p-0 px-4 sm:p-4 border border-gray-200 rounded-xl bg-gray-50 focus:border-gray-300 focus:bg-white outline-none pr-12 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${error ? "border-red-500" : "border-gray-200"} text-black`, ...props }), _jsxs("div", { className: "absolute right-3 top-1/2 transform -translate-y-1/2 flex flex-col", children: [_jsx("button", { type: "button", onClick: handleIncrement, disabled: disabled, className: "text-gray-400 hover:text-gray-600 disabled:opacity-50 disabled:cursor-not-allowed p-0.5", children: _jsx(ChevronUpIcon, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", onClick: handleDecrement, disabled: disabled, className: "text-gray-400 hover:text-gray-600 disabled:opacity-50 disabled:cursor-not-allowed p-0.5", children: _jsx(ChevronDownIcon, { className: "w-3 h-3" }) })] })] }), error && _jsx("p", { className: "mt-1 text-xs text-red-500", children: error })] }));
|
|
32
|
+
});
|
|
33
|
+
NumberInput.displayName = "NumberInput";
|
|
34
|
+
export default NumberInput;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { InputProps } from "./input";
|
|
3
|
+
export interface NumberInputWithCommasProps extends Omit<InputProps, "type" | "onChange"> {
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
value?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: (props: NumberInputWithCommasProps & {
|
|
8
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
9
|
+
}) => React.JSX.Element;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useState } from "react";
|
|
4
|
+
import { formatNumberWithCommas, getCleanNumber } from "../../utils";
|
|
5
|
+
const NumberInputWithCommas = forwardRef(({ label, name, placeholder, required = false, error, disabled = false, autoFocus = false, className = "", hideLabel = false, value = "", onChange, ...props }, ref) => {
|
|
6
|
+
const [displayValue, setDisplayValue] = useState(formatNumberWithCommas(value));
|
|
7
|
+
const handleInputChange = (e) => {
|
|
8
|
+
const inputValue = e.target.value;
|
|
9
|
+
const formatted = formatNumberWithCommas(inputValue);
|
|
10
|
+
setDisplayValue(formatted);
|
|
11
|
+
onChange(getCleanNumber(formatted));
|
|
12
|
+
};
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setDisplayValue(formatNumberWithCommas(value));
|
|
15
|
+
}, [value]);
|
|
16
|
+
return (_jsxs("div", { className: `w-full text-sm ${className}`, children: [label && !hideLabel && (_jsx("label", { htmlFor: name, className: "block mb-2 text-xs text-gray-500", children: label })), _jsx("div", { className: "relative", children: _jsxs("div", { className: "relative flex items-center", children: [_jsx("span", { className: "absolute left-5 mt-0.5/2 sm:left-4 top-1/2 transform -translate-y-1/2 text-xs text-gray-600 pointer-events-none", children: "\u20A6" }), _jsx("input", { ref: ref, id: name, name: name, type: "text", inputMode: "numeric", placeholder: placeholder, required: required, disabled: disabled, autoFocus: autoFocus, value: displayValue, onChange: handleInputChange, autoComplete: "off", className: `w-full text-base sm:text-xs h-13.5 sm:-h-auto p-0 px-4 sm:p-4 pl-8 sm:pl-7 border border-gray-200 rounded-xl bg-gray-50 focus:border-gray-300 focus:bg-white outline-none placeholder-gray-400 ${error ? "border-red-500" : "border-gray-200"} text-black`, ...props })] }) }), error && _jsx("p", { className: "mt-1 text-xs text-red-500", children: error })] }));
|
|
17
|
+
});
|
|
18
|
+
NumberInputWithCommas.displayName = "NumberInputWithCommas";
|
|
19
|
+
export default NumberInputWithCommas;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
export interface OptionItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
onClick: () => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface OptionsDropdownProps {
|
|
9
|
+
options: OptionItem[];
|
|
10
|
+
trigger?: ReactNode;
|
|
11
|
+
listClassName?: string;
|
|
12
|
+
optionClassName?: string;
|
|
13
|
+
containerClassName?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare const _default: (props: OptionsDropdownProps) => React.JSX.Element;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { MoreVertical } from "lucide-react";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
const OptionsDropdown = ({ options, trigger, listClassName = "", optionClassName = "", containerClassName = "", disabled = false, }) => {
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const dropdownRef = useRef(null);
|
|
8
|
+
// Close dropdown when clicking outside
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const handleClickOutside = (event) => {
|
|
11
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
12
|
+
setIsOpen(false);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
if (isOpen) {
|
|
16
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
17
|
+
}
|
|
18
|
+
return () => {
|
|
19
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
20
|
+
};
|
|
21
|
+
}, [isOpen]);
|
|
22
|
+
const handleToggle = () => {
|
|
23
|
+
if (!disabled) {
|
|
24
|
+
setIsOpen(!isOpen);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const handleOptionClick = (option) => {
|
|
28
|
+
if (!option.disabled) {
|
|
29
|
+
option.onClick();
|
|
30
|
+
setIsOpen(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const defaultTrigger = (_jsx("button", { className: "p-2 hover:bg-gray-100 rounded-full transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer", onClick: handleToggle, disabled: disabled, type: "button", children: _jsx(MoreVertical, { className: "size-5" }) }));
|
|
34
|
+
return (_jsxs("div", { className: `relative inline-flex items-center ${containerClassName}`, ref: dropdownRef, children: [_jsx("div", { onClick: (e) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
handleToggle();
|
|
38
|
+
}, className: "cursor-pointer", children: trigger || defaultTrigger }), isOpen && (_jsx("div", { className: `${listClassName} absolute right-0 top-full mt-2 bg-white border border-gray-100 rounded-xl divide-y divide-gray-100 shadow-lg z-50 min-w-48 overflow-hidden`, children: options.map((option) => (_jsx("button", { onClick: (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
e.stopPropagation();
|
|
41
|
+
handleOptionClick(option);
|
|
42
|
+
}, disabled: option.disabled, className: `w-full text-left px-3 py-2.5 cursor-pointer text-xs text-gray-500 hover:text-black hover:bg-gray-100 transition-colors duration-200 ${optionClassName}`, children: option.label }, option.id))) }))] }));
|
|
43
|
+
};
|
|
44
|
+
export default OptionsDropdown;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface OtpInputProps {
|
|
3
|
+
length?: number;
|
|
4
|
+
onOtpChange: (otp: string) => void;
|
|
5
|
+
label?: string;
|
|
6
|
+
size?: "sm" | "md" | "lg";
|
|
7
|
+
}
|
|
8
|
+
declare const _default: (props: OtpInputProps) => React.JSX.Element;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
const OtpInput = ({ length = 6, onOtpChange, label = "Enter OTP", size = "md" }) => {
|
|
5
|
+
const [otp, setOtp] = useState(new Array(length).fill(""));
|
|
6
|
+
const inputRefs = useRef(new Array(length).fill(null));
|
|
7
|
+
const getSizeClasses = (s) => {
|
|
8
|
+
switch (s) {
|
|
9
|
+
case "sm":
|
|
10
|
+
return "size-10 md:size-12 sm:text-lg";
|
|
11
|
+
case "md":
|
|
12
|
+
return "size-14 md:size-16 sm:text-xl";
|
|
13
|
+
case "lg":
|
|
14
|
+
return "size-16 md:size-20 sm:text-2xl";
|
|
15
|
+
default:
|
|
16
|
+
return "size-14 md:size-16 sm:text-xl";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const handleOtpChange = (index, value) => {
|
|
20
|
+
if (value.length <= 1 && /^\d*$/.test(value)) {
|
|
21
|
+
const newOtp = [...otp];
|
|
22
|
+
newOtp[index] = value;
|
|
23
|
+
setOtp(newOtp);
|
|
24
|
+
onOtpChange(newOtp.join(""));
|
|
25
|
+
if (value && index < length - 1) {
|
|
26
|
+
inputRefs.current[index + 1]?.focus();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const handleKeyDown = (index, e) => {
|
|
31
|
+
if (e.key === "Backspace" && !otp[index] && index > 0) {
|
|
32
|
+
inputRefs.current[index - 1]?.focus();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const handlePaste = (e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
const pastedData = e.clipboardData.getData("text");
|
|
38
|
+
const pastedOtp = pastedData.replace(/\D/g, "").slice(0, length);
|
|
39
|
+
if (pastedOtp.length > 0) {
|
|
40
|
+
const newOtp = new Array(length).fill("");
|
|
41
|
+
for (let i = 0; i < pastedOtp.length && i < length; i++) {
|
|
42
|
+
newOtp[i] = pastedOtp[i];
|
|
43
|
+
}
|
|
44
|
+
setOtp(newOtp);
|
|
45
|
+
onOtpChange(newOtp.join(""));
|
|
46
|
+
const nextEmptyIndex = Math.min(pastedOtp.length, length - 1);
|
|
47
|
+
inputRefs.current[nextEmptyIndex]?.focus();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs("div", { children: [_jsx("label", { className: "block mb-1 font-medium text-xs text-gray-500", children: label }), _jsx("div", { className: "flex gap-2 md:gap-4 mb-4", children: otp.map((digit, index) => (_jsx("input", { ref: (el) => {
|
|
51
|
+
inputRefs.current[index] = el;
|
|
52
|
+
}, id: `otp-${index}`, type: "text", inputMode: "numeric", pattern: "[0-9]*", value: digit, onChange: (e) => handleOtpChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onPaste: handlePaste, className: `${getSizeClasses(size)} text-center font-semibold border border-gray-300 rounded-xl focus:border-primaryblue focus:outline-none bg-gray-50 text-base text-black`, maxLength: 1, autoComplete: "off" }, index))) })] }));
|
|
53
|
+
};
|
|
54
|
+
export default OtpInput;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface ExistingFile {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
_id: string;
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
interface PreviewAreaProps {
|
|
9
|
+
allPreviewUrls: string[];
|
|
10
|
+
currentFiles: File[];
|
|
11
|
+
currentExistingFiles: ExistingFile[];
|
|
12
|
+
currentPreviewUrls: string[];
|
|
13
|
+
uploadedFiles: string[];
|
|
14
|
+
onRemoveFile: (index: number) => void;
|
|
15
|
+
isVideoFile: (file: ExistingFile | File) => boolean;
|
|
16
|
+
}
|
|
17
|
+
declare const PreviewArea: React.FC<PreviewAreaProps>;
|
|
18
|
+
export default PreviewArea;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const PreviewArea = ({ allPreviewUrls, currentFiles, currentExistingFiles, currentPreviewUrls, uploadedFiles, onRemoveFile, isVideoFile, }) => {
|
|
4
|
+
if (allPreviewUrls.length === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const renderSinglePreview = () => {
|
|
7
|
+
const isNewFile = currentFiles.length > 0;
|
|
8
|
+
const file = isNewFile ? currentFiles[0] : currentExistingFiles[0];
|
|
9
|
+
const isVideo = isVideoFile(file);
|
|
10
|
+
const url = isNewFile ? currentPreviewUrls[0] : allPreviewUrls[0];
|
|
11
|
+
return (_jsxs("div", { className: "w-full h-full relative group", children: [isVideo ? (_jsx("video", { src: url, className: "w-full h-full object-cover", controls: true, muted: true })) : (_jsx("img", { src: url, alt: isNewFile ? "Preview" : "File preview", className: "w-full h-full object-cover" })), _jsx("button", { type: "button", onClick: (e) => {
|
|
12
|
+
e.stopPropagation();
|
|
13
|
+
onRemoveFile(0);
|
|
14
|
+
}, className: "absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold transition-colors opacity-0 hover:opacity-100 group-hover:opacity-100", children: "\u00D7" }), isVideo && (_jsx("div", { className: "absolute top-2 left-2 text-white text-xs bg-black bg-opacity-50 px-2 py-1 rounded", children: "Video" }))] }));
|
|
15
|
+
};
|
|
16
|
+
const renderGridPreview = () => {
|
|
17
|
+
return (_jsx("div", { className: "p-4 h-full", children: _jsx("div", { className: "grid grid-cols-2 gap-3 h-full overflow-y-auto", children: allPreviewUrls.map((url, index) => {
|
|
18
|
+
const isNewFile = currentFiles.length > 0;
|
|
19
|
+
const file = isNewFile ? currentFiles[index] : currentExistingFiles[index];
|
|
20
|
+
const isVideo = isVideoFile(file);
|
|
21
|
+
const fileName = isNewFile ? file?.name || "" : file.name;
|
|
22
|
+
const isUploaded = isNewFile && uploadedFiles?.includes(fileName);
|
|
23
|
+
return (_jsxs("div", { className: "relative group", children: [isVideo ? (_jsx("video", { src: url, className: "w-full h-20 object-cover rounded-lg border border-gray-200", controls: true, muted: true })) : (_jsx("img", { src: url, alt: `Preview ${index + 1}`, className: "w-full h-20 object-cover rounded-lg border border-gray-200" })), isUploaded && (_jsx("div", { className: "absolute top-2 right-2 bg-green-500 text-white text-xs px-2 py-1 rounded", children: "Uploaded" })), _jsx("button", { type: "button", onClick: (e) => {
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
onRemoveFile(index);
|
|
26
|
+
}, className: "absolute top-1 right-1 bg-red-500 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs font-bold transition-colors opacity-0 group-hover:opacity-100", children: "\u00D7" }), _jsx("div", { className: "absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-all rounded-lg flex items-center justify-center", children: isVideo && (_jsx("div", { className: "text-white text-xs bg-black bg-opacity-50 px-2 py-1 rounded", children: "Video" })) })] }, index));
|
|
27
|
+
}) }) }));
|
|
28
|
+
};
|
|
29
|
+
return (_jsx("div", { className: "flex-1", children: _jsx("div", { className: "h-25 sm:h-[220px] min-h-[160px] border border-gray-200 rounded-xl bg-white overflow-hidden", children: allPreviewUrls.length === 1 ? renderSinglePreview() : renderGridPreview() }) }));
|
|
30
|
+
};
|
|
31
|
+
export default PreviewArea;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface SelectionOption {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface SelectionListProps {
|
|
9
|
+
label?: string;
|
|
10
|
+
options: SelectionOption[];
|
|
11
|
+
selectedValue: string;
|
|
12
|
+
onSelectionChange: (value: string) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const _default: (props: SelectionListProps) => React.JSX.Element;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const RadioIcon = ({ selected }) => (_jsx("span", { className: `ml-4 size-4 flex rounded-full border-1 p-[2.5px] ${selected ? "border-blue-500" : "border-gray-300 bg-gray-50"}`, children: selected ? (_jsx("span", { className: "block size-full rounded-full bg-blue-500" })) : null }));
|
|
4
|
+
const SelectionList = ({ label, options, selectedValue, onSelectionChange, className = "", }) => {
|
|
5
|
+
return (_jsx("div", { className: `space-y-6 ${className}`, children: _jsxs("div", { className: "", children: [label && (_jsx("div", { className: "p-2 pt-0 rounded-full text-xs text-gray-500", children: _jsx("div", { children: label }) })), _jsx("div", { className: "space-y-3", children: options.map((option) => (_jsxs("label", { className: `relative flex items-center p-4 border rounded-xl cursor-pointer transition-colors ${selectedValue === option.id
|
|
6
|
+
? "border-blue-500 bg-blue-50"
|
|
7
|
+
: "border-gray-200 bg-gray-50 hover:bg-gray-100"}`, onClick: () => onSelectionChange(option.id), children: [_jsx("input", { type: "radio", value: option.id, checked: selectedValue === option.id, onChange: () => onSelectionChange(option.id), className: "absolute opacity-0 w-full h-full cursor-pointer" }), _jsxs("div", { className: "flex items-center gap-3 flex-1", children: [option.icon && (_jsx("span", { className: "inline-block p-2 bg-blue-100 rounded-full", children: option.icon })), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "font-semibold text-sm text-gray-900", children: option.title }), option.subtitle && (_jsx("p", { className: "text-[11px] text-gray-500", children: option.subtitle }))] }), _jsx(RadioIcon, { selected: selectedValue === option.id })] })] }, option.id))) })] }) }));
|
|
8
|
+
};
|
|
9
|
+
export default SelectionList;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface SquareCheckboxProps {
|
|
3
|
+
label: string;
|
|
4
|
+
checked: boolean;
|
|
5
|
+
onChange: (checked: boolean) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: (props: SquareCheckboxProps) => React.JSX.Element;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const SquareCheckbox = ({ label, checked, onChange, disabled = false, className, }) => {
|
|
3
|
+
return (_jsx("div", { className: `inline-flex items-center gap-2 select-none ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} ${className ?? ""}`, children: _jsxs("label", { className: "relative inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: checked, disabled: disabled, onChange: (e) => onChange(e.target.checked), className: "sr-only", autoComplete: "off" }), _jsx("div", { className: `w-10 h-10 rounded-lg border flex items-center justify-center transition-colors ${checked ? "bg-black border-black" : "bg-gray-50 border-gray-300"}`, children: checked && (_jsx("svg", { className: "w-4.5 h-4.5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 6L9 17l-5-5" }) })) }), _jsx("span", { className: "text-xs text-gray-700", children: label })] }) }));
|
|
4
|
+
};
|
|
5
|
+
export default SquareCheckbox;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface Tab {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
interface TabNavigationProps {
|
|
7
|
+
tabs: Tab[];
|
|
8
|
+
activeTab: string;
|
|
9
|
+
onTabChange: (tabId: string) => void;
|
|
10
|
+
selectedFiles: Record<string, File[]>;
|
|
11
|
+
existingFilesByTab: Record<string, {
|
|
12
|
+
name: string;
|
|
13
|
+
url: string;
|
|
14
|
+
_id: string;
|
|
15
|
+
id: string;
|
|
16
|
+
}[]>;
|
|
17
|
+
}
|
|
18
|
+
declare const TabNavigation: React.FC<TabNavigationProps>;
|
|
19
|
+
export default TabNavigation;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const TabNavigation = ({ tabs, activeTab, onTabChange, selectedFiles, existingFilesByTab, }) => {
|
|
4
|
+
return (_jsx("div", { className: "flex gap-4 mb-4 overflow-x-auto scrollbar-hide pb-2", children: tabs.map((tab) => (_jsxs("button", { type: "button", onClick: () => onTabChange(tab.id), className: `px-3 py-1 text-[11px] rounded-lg transition-colors cursor-pointer flex items-center flex-shrink-0 whitespace-nowrap ${activeTab === tab.id
|
|
5
|
+
? "bg-blue-600 text-white"
|
|
6
|
+
: "bg-blue-50 text-blue-600 hover:bg-blue-200"} ${(selectedFiles[tab.id]?.length > 0 || existingFilesByTab[tab.id]?.length > 0) &&
|
|
7
|
+
activeTab === tab.id
|
|
8
|
+
? "ring-2 ring-blue-300"
|
|
9
|
+
: ""}`, children: [tab.label, (selectedFiles[tab.id]?.length > 0 || existingFilesByTab[tab.id]?.length > 0) && (_jsx("span", { className: `ml-2 text-xs ${activeTab === tab.id ? "bg-white" : "bg-primaryblue"} size-1.5 rounded-full inline-block` }))] }, tab.id))) }));
|
|
10
|
+
};
|
|
11
|
+
export default TabNavigation;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "className"> {
|
|
3
|
+
label?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: (props: TextareaProps & {
|
|
8
|
+
ref?: React.Ref<HTMLTextAreaElement>;
|
|
9
|
+
}) => React.JSX.Element;
|
|
10
|
+
export default _default;
|