@crestbase/web-shared-ui 1.0.1 → 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 +117 -4
- 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
package/README.md
CHANGED
|
@@ -1,13 +1,126 @@
|
|
|
1
|
+
````markdown
|
|
1
2
|
# @crestbase/web-shared-ui
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
A shared UI component library for the Crestbase web app and admin dashboard. Built with React, TypeScript, and Next.js in mind, this package provides reusable, accessible components like buttons, modals, headers, and more. It's designed for consistency across projects while keeping bundles light (peer deps for React).
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Installation
|
|
6
7
|
|
|
8
|
+
Add the package via Yarn (recommended) or NPM:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
7
11
|
yarn add @crestbase/web-shared-ui
|
|
12
|
+
# or
|
|
13
|
+
npm install @crestbase/web-shared-ui
|
|
14
|
+
```
|
|
15
|
+
````
|
|
16
|
+
|
|
17
|
+
### Peer Dependencies
|
|
18
|
+
|
|
19
|
+
Ensure React 18+ is installed (handled by Next.js):
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": "^18.0.0",
|
|
25
|
+
"react-dom": "^18.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
8
29
|
|
|
9
30
|
## Usage
|
|
10
31
|
|
|
11
|
-
|
|
32
|
+
Import components directly:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { Button, Modal } from "@crestbase/web-shared-ui";
|
|
36
|
+
|
|
37
|
+
export default function MyPage() {
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<Button
|
|
41
|
+
title="Click Me"
|
|
42
|
+
variant="primary"
|
|
43
|
+
onPress={() => console.log("Clicked!")}
|
|
44
|
+
/>
|
|
45
|
+
<Modal visible={true} title="Hello" onClose={() => {}}>
|
|
46
|
+
Content here
|
|
47
|
+
</Modal>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Available Components
|
|
54
|
+
|
|
55
|
+
- `Button` - Customizable buttons with variants.
|
|
56
|
+
- `Modal` - Accessible modals with animations.
|
|
57
|
+
- `Header` - Navigation headers.
|
|
58
|
+
- [Add more as you build—export via `src/index.ts`]
|
|
59
|
+
|
|
60
|
+
See [Storybook docs](https://your-storybook-url) for interactive examples (deploy separately if needed).
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
### Local Setup
|
|
65
|
+
|
|
66
|
+
1. Clone: `git clone https://github.com/mycrestbase/web-shared-ui.git && cd web-shared-ui`.
|
|
67
|
+
2. Install: `yarn install`.
|
|
68
|
+
3. Dev build: `yarn dev` (watches TS changes).
|
|
69
|
+
|
|
70
|
+
### Adding Components
|
|
71
|
+
|
|
72
|
+
1. Create in `src/components/NewComponent/NewComponent.tsx`.
|
|
73
|
+
2. Export in `src/components/NewComponent/index.ts`: `export { default } from './NewComponent';`.
|
|
74
|
+
3. Barrel export in `src/components/index.ts`: `export { default as NewComponent } from './NewComponent';`.
|
|
75
|
+
4. Main export in `src/index.ts`: `export * from './components';`.
|
|
76
|
+
5. Build: `yarn build` (generates `dist/`).
|
|
77
|
+
|
|
78
|
+
### Versioning & Publishing
|
|
79
|
+
|
|
80
|
+
We use semantic versioning (SemVer): `MAJOR.MINOR.PATCH`.
|
|
81
|
+
|
|
82
|
+
#### Bumping Versions
|
|
83
|
+
|
|
84
|
+
- **Patch** (bug fixes): `yarn version patch` → e.g., 1.0.0 → 1.0.1.
|
|
85
|
+
- **Minor** (new features): `yarn version minor` → 1.0.1 → 1.1.0.
|
|
86
|
+
- **Major** (breaking changes): `yarn version major` → 1.1.0 → 2.0.0.
|
|
87
|
+
|
|
88
|
+
This auto-updates `package.json` and commits.
|
|
89
|
+
|
|
90
|
+
#### Tagging & Publishing
|
|
91
|
+
|
|
92
|
+
1. Bump: `yarn version patch`.
|
|
93
|
+
2. Tag: `git tag v1.0.1` (matches version, with "v").
|
|
94
|
+
3. Push: `git push origin main && git push origin v1.0.1`.
|
|
95
|
+
- Triggers GitHub Actions: Builds, verifies, publishes to NPM (public), creates release.
|
|
96
|
+
|
|
97
|
+
#### Auto-Publish Workflow
|
|
98
|
+
|
|
99
|
+
- Located in `.github/workflows/publish.yml`.
|
|
100
|
+
- Runs on tag push: Builds → Verifies version → Publishes → GitHub release.
|
|
101
|
+
- View runs: Repo > Actions tab.
|
|
102
|
+
|
|
103
|
+
In consumer apps: `yarn upgrade @crestbase/web-shared-ui` to pull updates.
|
|
104
|
+
|
|
105
|
+
## Styles Setup
|
|
106
|
+
|
|
107
|
+
Add one import in your app entry (for Next.js, `app/layout.tsx`):
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import "@crestbase/web-shared-ui/dist/styles.css";
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This imports precompiled, scoped Tailwind-based styles so visuals render consistently without extra Tailwind configuration in consuming apps.
|
|
114
|
+
|
|
115
|
+
## Contributing
|
|
116
|
+
|
|
117
|
+
- Fork, branch (e.g., `feat/new-button`), PR to `main`.
|
|
118
|
+
- Build: `yarn build` (check `dist/`).
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT - see [LICENSE](LICENSE) for details.
|
|
123
|
+
|
|
124
|
+
---
|
|
12
125
|
|
|
13
|
-
|
|
126
|
+
Built with ❤️ for Crestbase. Questions? [Open an issue](https://github.com/mycrestbase/web-shared-ui/issues).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const CircleBtn = ({ title, icon, onBtnPress, isDisabled = false }) => {
|
|
3
|
+
return (_jsxs("button", { onClick: onBtnPress, disabled: isDisabled || !onBtnPress, "aria-disabled": isDisabled || !onBtnPress, className: "outline-none bg-white shadow-md h-6 w-6 flex items-center justify-center gap-x-2 rounded-full overflow-hidden cursor-pointer", children: [icon && icon, title && _jsx("p", { className: "font-normal text-sm text-[#011B33]", children: title })] }));
|
|
4
|
+
};
|
|
5
|
+
export default CircleBtn;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const ShadowButton = ({ children, className = "", onClick }) => {
|
|
3
|
+
return (_jsxs("button", { className: `border border-black px-3 bg-[#F1A5C2] text-blue-500 font-semibold py-1 rounded-lg relative z-30 cursor-pointer ${className}`, onClick: onClick, children: [children, _jsx("div", { className: `absolute w-full h-full rounded-lg bg-white -top-0.5 left-0.5 border border-black flex justify-center items-center ${className}`, children: children })] }));
|
|
4
|
+
};
|
|
5
|
+
export default ShadowButton;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface ButtonProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
type?: "submit" | "button";
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
className?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: (props: ButtonProps & {
|
|
10
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
11
|
+
}) => React.JSX.Element;
|
|
12
|
+
export default _default;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
const Button = () => {
|
|
3
|
-
|
|
3
|
+
const Button = ({ children, type = "submit", onClick, className = "", disabled = false, }) => {
|
|
4
|
+
const finalClasses = [
|
|
5
|
+
// Base styles (from former cb-btn)
|
|
6
|
+
"w-full p-3.5 px-6 h-13.5 sm:h-auto border border-gray-200 rounded-xl",
|
|
7
|
+
"text-white text-[13px] outline-none",
|
|
8
|
+
"bg-primaryblue hover:bg-primaryblue/70 focus:border-primaryblue",
|
|
9
|
+
// Interaction states
|
|
10
|
+
disabled ? "bg-gray-400 cursor-not-allowed" : "cursor-pointer",
|
|
11
|
+
// Consumer-provided classes
|
|
12
|
+
className,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(" ");
|
|
16
|
+
return (_jsx("button", { className: finalClasses, type: type, onClick: onClick, disabled: disabled, children: _jsx("span", { children: children }) }));
|
|
4
17
|
};
|
|
18
|
+
// Cast to a function component type to satisfy Next 15 + React 19 JSX typing
|
|
19
|
+
// while preserving the underlying React.FC behavior at runtime.
|
|
5
20
|
export default Button;
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface AccordionProps {
|
|
3
|
+
title: React.ReactNode;
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onToggle: () => void;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const Accordion: React.FC<AccordionProps>;
|
|
10
|
+
declare const _default: (props: AccordionProps) => React.JSX.Element;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { PlusIcon } from "lucide-react";
|
|
4
|
+
export const Accordion = ({ title, isOpen, onToggle, children, className = "" }) => {
|
|
5
|
+
return (_jsxs("div", { className: `border border-gray-200 rounded-2xl overflow-hidden text-sm sm:text-base ${className}`, children: [_jsxs("button", { onClick: onToggle, className: `w-full flex gap-2 items-center justify-between sm:px-4 p-4 sm:py-6 text-left ${isOpen ? "bg-gray-50" : ""} hover:bg-gray-50 cursor-pointer transition-colors`, "aria-expanded": isOpen, children: [_jsx("h3", { className: "text-black font-medium flex-1", children: title }), _jsx(PlusIcon, { className: `w-4.5 h-4.5 text-black transition-transform ${isOpen ? "rotate-45" : ""}` })] }), isOpen && _jsx("div", { className: "p-4", children: children })] }));
|
|
6
|
+
};
|
|
7
|
+
export default Accordion;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
interface AutocompleteOptions {
|
|
2
|
+
types?: string[];
|
|
3
|
+
componentRestrictions?: {
|
|
4
|
+
country: string | string[];
|
|
5
|
+
};
|
|
6
|
+
bounds?: {
|
|
7
|
+
north: number;
|
|
8
|
+
south: number;
|
|
9
|
+
east: number;
|
|
10
|
+
west: number;
|
|
11
|
+
};
|
|
12
|
+
fields?: string[];
|
|
13
|
+
strictBounds?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface AddressInputProps {
|
|
16
|
+
name: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
value?: string;
|
|
20
|
+
onChange: (value: string) => void;
|
|
21
|
+
error?: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
hideLabel?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
onLocationSelect?: (location: {
|
|
26
|
+
address: string;
|
|
27
|
+
latitude: number;
|
|
28
|
+
longitude: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
autocompleteOptions?: AutocompleteOptions;
|
|
31
|
+
dropdownClassName?: string;
|
|
32
|
+
}
|
|
33
|
+
declare function AddressInput({ name, label, placeholder, value, onChange, error, className, hideLabel, disabled, onLocationSelect, autocompleteOptions, dropdownClassName, }: Readonly<AddressInputProps>): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export default AddressInput;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { useGoogleMaps } from "../../utils/googleMapsLoader";
|
|
5
|
+
const parseAddressData = (addressData) => {
|
|
6
|
+
if (!addressData)
|
|
7
|
+
return { address: "" };
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(addressData);
|
|
10
|
+
if (typeof parsed === "object" && parsed.address) {
|
|
11
|
+
return {
|
|
12
|
+
address: parsed.address,
|
|
13
|
+
latitude: parsed.latitude,
|
|
14
|
+
longitude: parsed.longitude,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
return { address: addressData };
|
|
20
|
+
};
|
|
21
|
+
function AddressInput({ name, label, placeholder = "Enter property address", value = "", onChange, error, className = "", hideLabel = false, disabled = false, onLocationSelect, autocompleteOptions, dropdownClassName, }) {
|
|
22
|
+
const inputRef = useRef(null);
|
|
23
|
+
const autocompleteRef = useRef(null);
|
|
24
|
+
const focusHandlerRef = useRef(null);
|
|
25
|
+
const { isLoaded } = useGoogleMaps();
|
|
26
|
+
const handlePlaceChanged = useCallback(() => {
|
|
27
|
+
const place = autocompleteRef.current?.getPlace();
|
|
28
|
+
if (place?.geometry?.location) {
|
|
29
|
+
const location = {
|
|
30
|
+
address: place.formatted_address || place.name || "",
|
|
31
|
+
latitude: place.geometry.location.lat(),
|
|
32
|
+
longitude: place.geometry.location.lng(),
|
|
33
|
+
};
|
|
34
|
+
onLocationSelect?.(location);
|
|
35
|
+
}
|
|
36
|
+
}, [onLocationSelect]);
|
|
37
|
+
const handleInputChange = useCallback((event) => {
|
|
38
|
+
onChange(event.target.value);
|
|
39
|
+
}, [onChange]);
|
|
40
|
+
const addCustomStyling = useCallback(() => {
|
|
41
|
+
if (!dropdownClassName)
|
|
42
|
+
return;
|
|
43
|
+
const dropdowns = document.querySelectorAll(".pac-container");
|
|
44
|
+
for (const dropdown of dropdowns) {
|
|
45
|
+
if (!dropdown.classList.contains(dropdownClassName)) {
|
|
46
|
+
dropdown.classList.add(dropdownClassName);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, [dropdownClassName]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!isLoaded || !inputRef.current || autocompleteRef.current)
|
|
52
|
+
return;
|
|
53
|
+
const options = {
|
|
54
|
+
types: ["address"],
|
|
55
|
+
componentRestrictions: { country: "ng" },
|
|
56
|
+
fields: ["formatted_address", "geometry", "name"],
|
|
57
|
+
...autocompleteOptions,
|
|
58
|
+
};
|
|
59
|
+
if (window.google?.maps?.places?.Autocomplete) {
|
|
60
|
+
autocompleteRef.current = new window.google.maps.places.Autocomplete(inputRef.current, options);
|
|
61
|
+
autocompleteRef.current.addListener("place_changed", handlePlaceChanged);
|
|
62
|
+
}
|
|
63
|
+
if (dropdownClassName && inputRef.current) {
|
|
64
|
+
const timeoutId = setTimeout(addCustomStyling, 100);
|
|
65
|
+
focusHandlerRef.current = () => {
|
|
66
|
+
setTimeout(addCustomStyling, 100);
|
|
67
|
+
};
|
|
68
|
+
inputRef.current.addEventListener("focus", focusHandlerRef.current);
|
|
69
|
+
return () => {
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
if (inputRef.current && focusHandlerRef.current) {
|
|
72
|
+
inputRef.current.removeEventListener("focus", focusHandlerRef.current);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}, [
|
|
77
|
+
isLoaded,
|
|
78
|
+
autocompleteOptions,
|
|
79
|
+
dropdownClassName,
|
|
80
|
+
handlePlaceChanged,
|
|
81
|
+
addCustomStyling,
|
|
82
|
+
]);
|
|
83
|
+
const errorMessage = error;
|
|
84
|
+
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 })), _jsxs("div", { className: "relative", children: [_jsx("input", { ref: (e) => {
|
|
85
|
+
inputRef.current = e;
|
|
86
|
+
}, id: name, type: "text", placeholder: placeholder, value: parseAddressData(value || "").address, onChange: handleInputChange, name: name, disabled: disabled, className: `w-full text-base sm:text-xs h-13.5 sm:h-auto p-0 px-3 sm:px-4 sm:p-4 border border-gray-200 rounded-xl bg-gray-50 focus:border-gray-300 focus:bg-white outline-none placeholder-gray-300 ${errorMessage ? "border-red-500" : "border-gray-200"} ${isLoaded ? "" : "pr-12"}`, autoComplete: "off" }), !isLoaded && (_jsx("div", { className: "absolute right-3 top-1/2 transform -translate-y-1/2", children: _jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400" }) }))] }), errorMessage && (_jsx("p", { className: "mt-1 text-xs text-red-500", children: errorMessage })), !isLoaded && (_jsx("p", { className: "mt-1 text-xs text-gray-400", children: "Loading address suggestions..." }))] }));
|
|
87
|
+
}
|
|
88
|
+
export default AddressInput;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface BottomSheetProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
renderTopBg?: () => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: (props: BottomSheetProps) => React.JSX.Element;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
const BottomSheet = ({ isOpen, onClose, children, className = "", renderTopBg }) => {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
document.body.style.overflow = isOpen ? "hidden" : "unset";
|
|
7
|
+
return () => {
|
|
8
|
+
document.body.style.overflow = "unset";
|
|
9
|
+
};
|
|
10
|
+
}, [isOpen]);
|
|
11
|
+
if (!isOpen)
|
|
12
|
+
return null;
|
|
13
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex flex-col items-center justify-end", children: [_jsx("button", { className: "absolute inset-0 bg-black/40 bg-opacity-50 transition-opacity", onClick: onClose }), renderTopBg?.(), _jsx("div", { className: `relative z-12 w-full max-w-md bg-white rounded-t-3xl shadow-xl transform transition-transform duration-300 ease-out animate-in slide-in-from-bottom max-h-[80vh] overflow-hidden overflow-y-auto text-black ${className}`, children: children })] }));
|
|
14
|
+
};
|
|
15
|
+
export default BottomSheet;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface CalendarProps {
|
|
3
|
+
selectedDates: Date[];
|
|
4
|
+
onDateSelect: (date: Date) => void;
|
|
5
|
+
bookedDates?: Date[];
|
|
6
|
+
maxSelections?: number;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: (props: CalendarProps) => React.JSX.Element;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
const Calendar = ({ selectedDates, onDateSelect, bookedDates = [], maxSelections, }) => {
|
|
5
|
+
const [currentMonth, setCurrentMonth] = useState(new Date());
|
|
6
|
+
const today = new Date();
|
|
7
|
+
const year = currentMonth.getFullYear();
|
|
8
|
+
const month = currentMonth.getMonth();
|
|
9
|
+
// Get first day of month and number of days
|
|
10
|
+
const firstDay = new Date(year, month, 1);
|
|
11
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
12
|
+
const daysInMonth = lastDay.getDate();
|
|
13
|
+
const startingDayOfWeek = firstDay.getDay();
|
|
14
|
+
// Month names
|
|
15
|
+
const monthNames = [
|
|
16
|
+
"January",
|
|
17
|
+
"February",
|
|
18
|
+
"March",
|
|
19
|
+
"April",
|
|
20
|
+
"May",
|
|
21
|
+
"June",
|
|
22
|
+
"July",
|
|
23
|
+
"August",
|
|
24
|
+
"September",
|
|
25
|
+
"October",
|
|
26
|
+
"November",
|
|
27
|
+
"December",
|
|
28
|
+
];
|
|
29
|
+
const handleDateClick = (day) => {
|
|
30
|
+
const clickedDate = new Date(year, month, day);
|
|
31
|
+
// Don't allow past dates or booked dates
|
|
32
|
+
if (clickedDate < today || isDateBooked(day))
|
|
33
|
+
return;
|
|
34
|
+
// Check if we've reached max selections and this date isn't already selected
|
|
35
|
+
if (maxSelections &&
|
|
36
|
+
selectedDates.length >= maxSelections &&
|
|
37
|
+
!isDateSelected(day)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
onDateSelect(clickedDate);
|
|
41
|
+
};
|
|
42
|
+
const handlePrevMonth = () => {
|
|
43
|
+
setCurrentMonth(new Date(year, month - 1, 1));
|
|
44
|
+
};
|
|
45
|
+
const handleNextMonth = () => {
|
|
46
|
+
setCurrentMonth(new Date(year, month + 1, 1));
|
|
47
|
+
};
|
|
48
|
+
const isDateSelected = (day) => {
|
|
49
|
+
const date = new Date(year, month, day);
|
|
50
|
+
return selectedDates.some((selectedDate) => selectedDate.getTime() === date.getTime());
|
|
51
|
+
};
|
|
52
|
+
const isDateBooked = (day) => {
|
|
53
|
+
const date = new Date(year, month, day);
|
|
54
|
+
return bookedDates.some((bookedDate) => {
|
|
55
|
+
const bookedDateOnly = new Date(bookedDate.getFullYear(), bookedDate.getMonth(), bookedDate.getDate());
|
|
56
|
+
const currentDateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
57
|
+
return bookedDateOnly.getTime() === currentDateOnly.getTime();
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const isDateDisabled = (day) => {
|
|
61
|
+
const date = new Date(year, month, day);
|
|
62
|
+
return date < today || isDateBooked(day);
|
|
63
|
+
};
|
|
64
|
+
// Generate calendar days
|
|
65
|
+
const calendarDays = [];
|
|
66
|
+
// Add empty cells for days before the first day of the month
|
|
67
|
+
for (let i = 0; i < startingDayOfWeek; i++) {
|
|
68
|
+
calendarDays.push(_jsx("div", { className: "h-10" }, `empty-${i}`));
|
|
69
|
+
}
|
|
70
|
+
// Add days of the month
|
|
71
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
72
|
+
const isDisabled = isDateDisabled(day);
|
|
73
|
+
const isSelected = isDateSelected(day);
|
|
74
|
+
const isBooked = isDateBooked(day);
|
|
75
|
+
const isPast = new Date(year, month, day) < today;
|
|
76
|
+
let buttonClass = "h-8 w-8 rounded-full flex items-center justify-center text-[11px] transition-all duration-200 ";
|
|
77
|
+
if (isDisabled) {
|
|
78
|
+
if (isBooked) {
|
|
79
|
+
buttonClass +=
|
|
80
|
+
"bg-gray-100 text-gray-400 cursor-not-allowed border border-gray-200";
|
|
81
|
+
}
|
|
82
|
+
else if (isPast) {
|
|
83
|
+
buttonClass += "text-gray-300 cursor-not-allowed";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (isSelected) {
|
|
87
|
+
buttonClass +=
|
|
88
|
+
"bg-blue-500 text-white shadow-md transform scale-105 cursor-pointer";
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
buttonClass +=
|
|
92
|
+
"text-gray-700 hover:bg-blue-50 hover:text-blue-600 cursor-pointer";
|
|
93
|
+
}
|
|
94
|
+
calendarDays.push(_jsx("button", { onClick: () => handleDateClick(day), disabled: isDisabled, className: buttonClass, title: isBooked ? "This date is already booked" : isPast ? "Past date" : "", children: day }, day));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs("div", { className: "calendar ", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("button", { onClick: handlePrevMonth, className: "p-2 rounded-full hover:bg-gray-100 transition-colors cursor-pointer", children: _jsx("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }), _jsxs("h3", { className: "text-sm font-medium text-gray-800", children: [monthNames[month], " ", year] }), _jsx("button", { onClick: handleNextMonth, className: "p-2 rounded-full hover:bg-gray-100 transition-colors cursor-pointer", children: _jsx("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) }) })] }), _jsx("div", { className: "grid grid-cols-7 gap-1 mb-2", children: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (_jsx("div", { className: "h-8 flex items-center justify-center text-xs font-medium text-gray-500", children: day }, day))) }), _jsx("div", { className: "grid grid-cols-7 gap-1", children: calendarDays }), _jsx("div", { className: "mt-3", children: _jsxs("div", { className: "flex justify-center gap-4 text-xs", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-blue-500 rounded-full" }), _jsx("span", { className: "text-gray-600", children: "Selected" })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-100 border border-gray-200 rounded-full" }), _jsx("span", { className: "text-gray-600", children: "Booked" })] })] }) })] }));
|
|
97
|
+
};
|
|
98
|
+
export default Calendar;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type MonthSelection = {
|
|
2
|
+
year: number;
|
|
3
|
+
month: number;
|
|
4
|
+
};
|
|
5
|
+
export interface CalendarExtendedProps {
|
|
6
|
+
selectionGranularity?: "day" | "month";
|
|
7
|
+
allowRange?: boolean;
|
|
8
|
+
startYear?: number;
|
|
9
|
+
endYear?: number;
|
|
10
|
+
selectedMonths?: MonthSelection[];
|
|
11
|
+
onMonthSelectionChange?: (months: MonthSelection[]) => void;
|
|
12
|
+
onMonthRangeChange?: (range: {
|
|
13
|
+
start: MonthSelection;
|
|
14
|
+
end: MonthSelection;
|
|
15
|
+
}) => void;
|
|
16
|
+
minSelectableMonth?: MonthSelection;
|
|
17
|
+
maxSelectableMonth?: MonthSelection;
|
|
18
|
+
/** Convenience flag: when true, disallow selecting months beyond the current month */
|
|
19
|
+
disableFutureMonths?: boolean;
|
|
20
|
+
containerHeight?: number;
|
|
21
|
+
enableYearTransition?: boolean;
|
|
22
|
+
selectedDates?: Date[];
|
|
23
|
+
onDateSelect?: (date: Date) => void;
|
|
24
|
+
bookedDates?: Date[];
|
|
25
|
+
maxSelections?: number;
|
|
26
|
+
}
|
|
27
|
+
export default function CalendarExtended({ selectionGranularity, allowRange, startYear, endYear, selectedMonths, onMonthSelectionChange, onMonthRangeChange, minSelectableMonth, maxSelectableMonth, disableFutureMonths, containerHeight, enableYearTransition: _enableYearTransition, selectedDates, onDateSelect, bookedDates, maxSelections, }: Readonly<CalendarExtendedProps>): import("react/jsx-runtime").JSX.Element;
|