@kilamaelie/react-geo-picker 1.0.0
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/LICENSE +12 -0
- package/README.md +214 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kilama Elie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above and disclaimer notices shall be included in all copies or substantial portions of the Software.
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# react-geo-picker 🌍
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.org/package/react-geo-picker) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](https://tailwindcss.com/)
|
|
4
|
+
|
|
5
|
+
A lightweight, fully responsive, and accessible React package containing modal components and utilities for selecting **Countries**, **Cities**, and **Phone Number Calling Codes**.
|
|
6
|
+
|
|
7
|
+
Built from the ground up with **TypeScript**, **Tailwind CSS**, and **Radix UI**, `react-geo-picker` works seamlessly out-of-the-box in both **Vite (Client SPAs)** and **Next.js (App & Pages Routers, Server/Client components)**.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🚀 **Zero Configuration UI:** Clean, modern modals styled consistently with Tailwind CSS and Radix primitives.
|
|
14
|
+
- 📱 **Mobile First & Responsive:** Fluid viewports (`95vw`, optimized heights) ensuring no layout clipping on mobile devices or small screens.
|
|
15
|
+
- 🔄 **Framework Agnostic:** Compatible with modern React frameworks supporting `"use client"` directives natively.
|
|
16
|
+
- 🛠 **TypeScript First:** Fully typed out of the box with strict generics, making form integration (like `react-hook-form`) seamless.
|
|
17
|
+
- 📦 **Rich Built-In Datasets:** Pre-bundled with comprehensive international country profiles, national flags, calling codes, and nested city datasets.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
Install the package via your preferred package manager:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install react-geo-picker
|
|
27
|
+
# or
|
|
28
|
+
yarn add react-geo-picker
|
|
29
|
+
# or
|
|
30
|
+
pnpm add react-geo-picker
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Peer & Host Dependencies
|
|
34
|
+
Ensure your host application has the following installed:
|
|
35
|
+
* `react` (^18.0.0 || ^19.0.0)
|
|
36
|
+
* `react-dom` (^18.0.0 || ^19.0.0)
|
|
37
|
+
* `tailwindcss` (configured in your project)
|
|
38
|
+
* `lucide-react` (used for UI icons)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🚀 Usage Guide
|
|
43
|
+
|
|
44
|
+
### 1. Country Selection Modal
|
|
45
|
+
Allows users to pick a country name from an alphabetized, scrollable list complete with emoji flags and international codes.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useState } from 'react';
|
|
49
|
+
import { CountryModal } from 'react-geo-picker';
|
|
50
|
+
|
|
51
|
+
export default function CountrySelectDemo() {
|
|
52
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
53
|
+
const [selectedCountry, setSelectedCountry] = useState('');
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="w-full max-w-sm space-y-2">
|
|
57
|
+
<label className="text-sm font-medium text-foreground">Country</label>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={() => setIsOpen(true)}
|
|
61
|
+
className="w-full flex items-center justify-between px-4 py-2.5 rounded-xl border border-border bg-background text-sm shadow-sm hover:bg-muted/50 transition-colors"
|
|
62
|
+
>
|
|
63
|
+
<span>{selectedCountry || 'Select a country...'}</span>
|
|
64
|
+
<span className="text-xs text-muted-foreground">▼</span>
|
|
65
|
+
</button>
|
|
66
|
+
|
|
67
|
+
<CountryModal
|
|
68
|
+
showCountryModal={isOpen}
|
|
69
|
+
setShowCountryModal={setIsOpen}
|
|
70
|
+
onChange={(country) => {
|
|
71
|
+
setSelectedCountry(country);
|
|
72
|
+
console.log('Selected country:', country);
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### 2. City Selection Modal (Filtered by Country)
|
|
83
|
+
Dynamically renders the major cities associated with the selected parent country.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { useState } from 'react';
|
|
87
|
+
import { CitiesModal } from 'react-geo-picker';
|
|
88
|
+
|
|
89
|
+
export default function CitySelectDemo() {
|
|
90
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
91
|
+
const [country] = useState('Canada'); // Can be bound to your state
|
|
92
|
+
const [selectedCity, setSelectedCity] = useState('');
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="w-full max-w-sm space-y-2">
|
|
96
|
+
<label className="text-sm font-medium text-foreground">City ({country})</label>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={() => setIsOpen(true)}
|
|
100
|
+
className="w-full flex items-center justify-between px-4 py-2.5 rounded-xl border border-border bg-background text-sm shadow-sm hover:bg-muted/50 transition-colors"
|
|
101
|
+
>
|
|
102
|
+
<span>{selectedCity || `Select city in ${country}...`}</span>
|
|
103
|
+
<span className="text-xs text-muted-foreground">▼</span>
|
|
104
|
+
</button>
|
|
105
|
+
|
|
106
|
+
<CitiesModal
|
|
107
|
+
showCitiesModal={isOpen}
|
|
108
|
+
setShowCitiesModal={setIsOpen}
|
|
109
|
+
country={country}
|
|
110
|
+
onChange={(city) => {
|
|
111
|
+
setSelectedCity(city);
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### 3. Phone Number Code Modal & Input Sanitizer Utility
|
|
122
|
+
Perfect for onboarding flows, authentication inputs, and international telephone capture.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useState } from 'react';
|
|
126
|
+
import { PhoneNumberModal, formatPhoneNumber } from 'react-geo-picker';
|
|
127
|
+
|
|
128
|
+
export default function PhoneInputDemo() {
|
|
129
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
130
|
+
const [countryCode, setCountryCode] = useState('+1');
|
|
131
|
+
const [phoneNumber, setPhoneNumber] = useState('');
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="w-full max-w-sm space-y-2">
|
|
135
|
+
<label className="text-sm font-medium text-foreground">Phone Number</label>
|
|
136
|
+
<div className="flex gap-2">
|
|
137
|
+
{/* Country Code Trigger Button */}
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => setIsOpen(true)}
|
|
141
|
+
className="px-3 py-2.5 rounded-xl border border-border bg-background text-sm font-semibold shadow-sm hover:bg-muted/50 transition-colors flex items-center gap-1.5"
|
|
142
|
+
>
|
|
143
|
+
<span>{countryCode}</span>
|
|
144
|
+
<span className="text-xs text-muted-foreground">▼</span>
|
|
145
|
+
</button>
|
|
146
|
+
|
|
147
|
+
{/* Input field utilizing formatPhoneNumber utility */}
|
|
148
|
+
<input
|
|
149
|
+
type="tel"
|
|
150
|
+
value={phoneNumber}
|
|
151
|
+
onChange={(e) => setPhoneNumber(formatPhoneNumber(e.target.value))}
|
|
152
|
+
placeholder="555-0199"
|
|
153
|
+
className="flex-1 px-4 py-2.5 rounded-xl border border-border bg-background text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<PhoneNumberModal
|
|
158
|
+
showPhoneNumberModal={isOpen}
|
|
159
|
+
setShowPhoneNumberModal={setIsOpen}
|
|
160
|
+
onChange={(code) => {
|
|
161
|
+
setCountryCode(code);
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 📚 API Reference
|
|
172
|
+
|
|
173
|
+
### Component Props
|
|
174
|
+
|
|
175
|
+
All modal components share an identical and predictable state-handling signature:
|
|
176
|
+
|
|
177
|
+
| Prop Name | Type | Description |
|
|
178
|
+
| :--- | :--- | :--- |
|
|
179
|
+
| `show[ModalName]` | `boolean` | Controls the open/closed visibility state of the modal. |
|
|
180
|
+
| `setShow[ModalName]` | `React.Dispatch<React.SetStateAction<boolean>>` | State dispatcher function to update visibility. |
|
|
181
|
+
| `onChange` | `(value: string) => void` | Callback function triggered when an item is selected. Passes back the target value (`country.name`, `city`, or `country.code`). |
|
|
182
|
+
| `country` *(CitiesModal only)* | `string` | The parent country string used to filter and lookup the respective cities array. |
|
|
183
|
+
|
|
184
|
+
### Utilities
|
|
185
|
+
|
|
186
|
+
- **`formatPhoneNumber(value: string): string`**
|
|
187
|
+
- Strips all non-digit characters from string input.
|
|
188
|
+
- Automatically bounds strings to a maximum of 15 digits conforming to international standard E.164.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🛠 Local Development & Contributing
|
|
193
|
+
|
|
194
|
+
1. **Clone the repository:**
|
|
195
|
+
```bash
|
|
196
|
+
git clone https://github.com/kilamaelie/react-geo-picker.git
|
|
197
|
+
cd react-geo-picker
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
2. **Install dependencies:**
|
|
201
|
+
```bash
|
|
202
|
+
npm install
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. **Run build watch mode:**
|
|
206
|
+
```bash
|
|
207
|
+
npm run dev
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 📄 License
|
|
213
|
+
|
|
214
|
+
Distributed under the **MIT License**. See `LICENSE` for more information.
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kilamaelie/react-geo-picker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight, responsive React modal components and utilities for selecting countries, cities, and country codes.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./style.css": "./dist/index.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build:css": "tailwindcss -i ./src/styles.css -o ./dist/index.css --minify",
|
|
22
|
+
"build:js": "tsup src/index.ts --format cjs,esm --dts",
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/kilamaelie/react-geo-picker.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"react",
|
|
32
|
+
"typescript",
|
|
33
|
+
"tailwind",
|
|
34
|
+
"countries",
|
|
35
|
+
"cities",
|
|
36
|
+
"phone-code",
|
|
37
|
+
"modal",
|
|
38
|
+
"nextjs",
|
|
39
|
+
"vite"
|
|
40
|
+
],
|
|
41
|
+
"author": "Kilama Elie",
|
|
42
|
+
"license": "ISC",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/kilamaelie/react-geo-picker/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/kilamaelie/react-geo-picker#readme",
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"lucide-react": "^1.41.0",
|
|
49
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
50
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
51
|
+
"tailwindcss": "^3.0.0 || ^4.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/react": "^19.2.18",
|
|
55
|
+
"@types/react-dom": "^19.2.7",
|
|
56
|
+
"autoprefixer": "^10.5.5",
|
|
57
|
+
"postcss": "^8.5.28",
|
|
58
|
+
"react": "^19.2.8",
|
|
59
|
+
"react-dom": "^19.2.8",
|
|
60
|
+
"tailwindcss": "^4.3.3",
|
|
61
|
+
"tsup": "^8.5.1",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@radix-ui/react-dialog": "^1.1.23",
|
|
66
|
+
"@radix-ui/react-slot": "^1.3.3",
|
|
67
|
+
"class-variance-authority": "^0.7.1",
|
|
68
|
+
"clsx": "^2.1.1",
|
|
69
|
+
"radix-ui": "^1.6.7",
|
|
70
|
+
"react-hook-form": "^7.87.0",
|
|
71
|
+
"tailwind-merge": "^3.6.0"
|
|
72
|
+
}
|
|
73
|
+
}
|