@wherabouts/react-ui 0.1.0 → 0.3.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 +21 -0
- package/README.md +27 -9
- package/dist/index.cjs +246 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -45
- package/dist/index.d.ts +97 -45
- package/dist/index.js +246 -257
- package/dist/index.js.map +1 -1
- package/dist/styles.css +52 -40
- package/docs/README.md +16 -0
- package/docs/address-autocomplete.md +208 -0
- package/docs/address-field-group.md +152 -0
- package/docs/address-form-field.md +198 -0
- package/docs/forward-geocode-input.md +115 -0
- package/docs/reverse-geocode-input.md +124 -0
- package/package.json +26 -19
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# AddressFormField
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
`AddressAutocomplete` wrapped with a `<label>` and error styling — a drop-in form field. Accepts every `AddressAutocomplete` prop plus `label`, `labelClassName`, and `errorClassName`.
|
|
6
|
+
|
|
7
|
+
## When to use / when not
|
|
8
|
+
|
|
9
|
+
**Use it when:**
|
|
10
|
+
|
|
11
|
+
- You need a labelled address-search field inside a form — this is the right default
|
|
12
|
+
for checkout forms, shipping address capture, and any other form where the label and
|
|
13
|
+
error text should be co-located with the input.
|
|
14
|
+
- You want required-field marking (`*`) and accessible error text (`role="alert"`,
|
|
15
|
+
`aria-live="polite"`) without wiring them up yourself.
|
|
16
|
+
- You're already using `AddressAutocomplete` props and just want a label + error wrapper
|
|
17
|
+
around them with no extra setup.
|
|
18
|
+
|
|
19
|
+
**Don't use it when:**
|
|
20
|
+
|
|
21
|
+
- You need the raw autocomplete without any label or error chrome — use
|
|
22
|
+
`AddressAutocomplete` directly and bring your own `<label>`.
|
|
23
|
+
- You're editing an already-known, structured address (street/suburb/state/postcode) —
|
|
24
|
+
use `AddressFieldGroup` instead.
|
|
25
|
+
- You only need to resolve free text to coordinates without a suggestion dropdown — use
|
|
26
|
+
`ForwardGeocodeInput`.
|
|
27
|
+
|
|
28
|
+
## Import & minimal example
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { createWheraboutsClient } from "@wherabouts/sdk";
|
|
32
|
+
import { AddressFormField } from "@wherabouts/react-ui";
|
|
33
|
+
import "@wherabouts/react-ui/styles.css";
|
|
34
|
+
|
|
35
|
+
const client = createWheraboutsClient({ apiKey: import.meta.env.VITE_WHERABOUTS_KEY });
|
|
36
|
+
|
|
37
|
+
export function Checkout() {
|
|
38
|
+
return (
|
|
39
|
+
<AddressFormField client={client} label="Delivery address" required onSelect={setAddress} />
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Worked examples
|
|
45
|
+
|
|
46
|
+
### 1. Controlled selection state
|
|
47
|
+
|
|
48
|
+
Store the selected address from `onSelect`; surface it elsewhere in the form:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
function DeliveryAddress() {
|
|
52
|
+
const [selected, setSelected] = useState<AddressWithParsed | null>(null);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<AddressFormField
|
|
57
|
+
client={client}
|
|
58
|
+
label="Delivery address"
|
|
59
|
+
placeholder="Start typing…"
|
|
60
|
+
onSelect={(address) => setSelected(address)}
|
|
61
|
+
required
|
|
62
|
+
/>
|
|
63
|
+
{selected && (
|
|
64
|
+
<p>
|
|
65
|
+
Selected: {selected.streetAddress}, {selected.suburb} {selected.state}{" "}
|
|
66
|
+
{selected.postcode}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. TanStack Form wiring
|
|
75
|
+
|
|
76
|
+
Wire `onSelect` to a TanStack Form field's `handleChange`, and pass the field's
|
|
77
|
+
validation error to `error`:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useForm } from "@tanstack/react-form";
|
|
81
|
+
|
|
82
|
+
function CheckoutForm() {
|
|
83
|
+
const form = useForm({
|
|
84
|
+
defaultValues: { address: null as AddressWithParsed | null },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<form.Field name="address">
|
|
89
|
+
{(field) => (
|
|
90
|
+
<AddressFormField
|
|
91
|
+
client={client}
|
|
92
|
+
label="Shipping address"
|
|
93
|
+
onSelect={(address) => field.handleChange(address)}
|
|
94
|
+
error={field.state.meta.errors?.[0]}
|
|
95
|
+
required
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</form.Field>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Custom label and error styles
|
|
104
|
+
|
|
105
|
+
Override label and error appearance via `labelClassName` and `errorClassName`:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<AddressFormField
|
|
109
|
+
client={client}
|
|
110
|
+
label="Pickup location"
|
|
111
|
+
labelClassName="text-lg font-semibold text-brand-900"
|
|
112
|
+
errorClassName="font-medium"
|
|
113
|
+
error={validationError}
|
|
114
|
+
onSelect={setAddress}
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. Geolocation / proximity bias
|
|
119
|
+
|
|
120
|
+
All `AddressAutocomplete` props are forwarded — including geolocation:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<AddressFormField
|
|
124
|
+
client={client}
|
|
125
|
+
label="Delivery address"
|
|
126
|
+
enableGeolocation
|
|
127
|
+
onSelect={setAddress}
|
|
128
|
+
required
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Props
|
|
133
|
+
|
|
134
|
+
`AddressFormField` extends every prop from `AddressAutocomplete` (see
|
|
135
|
+
[AddressAutocomplete docs](./address-autocomplete.md)) and adds:
|
|
136
|
+
|
|
137
|
+
| Prop | Type | Default | Description |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `label` | `string` | — | **Required.** Text content of the `<label>` element. |
|
|
140
|
+
| `labelClassName` | `string` | — | Additional class(es) applied to the `<label>` element. |
|
|
141
|
+
| `errorClassName` | `string` | — | Additional class(es) applied to the error text `<p>` element. |
|
|
142
|
+
|
|
143
|
+
All `AddressAutocomplete` props are forwarded unchanged. Key inherited props:
|
|
144
|
+
|
|
145
|
+
| Prop | Type | Default | Description |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `client` | `WheraboutsClient` | — | **Required.** SDK client created with `createWheraboutsClient`. |
|
|
148
|
+
| `onSelect` | `(address: AddressWithParsed) => void` | — | Called when a suggestion is selected. |
|
|
149
|
+
| `onQueryChange` | `(query: string) => void` | — | Called as the input text changes. |
|
|
150
|
+
| `placeholder` | `string` | — | Input placeholder text. |
|
|
151
|
+
| `debounceMs` | `number` | `300` | Debounce in ms before querying the API. |
|
|
152
|
+
| `minCharsToSearch` | `number` | `2` | Minimum characters typed before searching. |
|
|
153
|
+
| `maxSuggestions` | `number` | `5` | Maximum number of suggestions to show. |
|
|
154
|
+
| `enableGeolocation` | `boolean` | `false` | Use the browser's geolocation to bias results by proximity. |
|
|
155
|
+
| `userLat` | `number` | — | Explicit latitude for proximity bias. |
|
|
156
|
+
| `userLng` | `number` | — | Explicit longitude for proximity bias. |
|
|
157
|
+
| `disabled` | `boolean` | — | Disable the input. |
|
|
158
|
+
| `required` | `boolean` | — | Mark the input as required (also renders `*` next to the label). |
|
|
159
|
+
| `error` | `string` | — | External error message; renders below the input with `role="alert"`. |
|
|
160
|
+
| `id` | `string` | `"wherabouts-field"` | Forwarded to the input element and linked to the label via `htmlFor`. |
|
|
161
|
+
| `className` | `string` | — | Class applied to the `AddressAutocomplete` root container. |
|
|
162
|
+
|
|
163
|
+
## Accessibility
|
|
164
|
+
|
|
165
|
+
- The component renders a `<label>` with `htmlFor` set to the same `id` as the
|
|
166
|
+
underlying `AddressAutocomplete` input (default `"wherabouts-field"`). This
|
|
167
|
+
association satisfies WCAG 2.1 SC 1.3.1 and ensures screen readers announce the
|
|
168
|
+
label when the input is focused.
|
|
169
|
+
- Pass a custom `id` when you render multiple `AddressFormField` instances on the
|
|
170
|
+
same page to keep `htmlFor`/`id` pairs unique.
|
|
171
|
+
- When `required` is `true`, a `*` character is rendered `aria-hidden="true"` (so it
|
|
172
|
+
is not read by screen readers) alongside the visible label text; the `required`
|
|
173
|
+
attribute is forwarded to the input so assistive technology announces it natively.
|
|
174
|
+
- When `error` is set, a `<p role="alert" aria-live="polite">` is rendered below the
|
|
175
|
+
input. Screen readers will announce the error text without the user having to navigate
|
|
176
|
+
to it. The `errorClassName` prop lets you style this element to match your design
|
|
177
|
+
system.
|
|
178
|
+
- All keyboard navigation and ARIA combobox semantics are inherited from
|
|
179
|
+
`AddressAutocomplete` — see the [AddressAutocomplete accessibility section](./address-autocomplete.md#accessibility).
|
|
180
|
+
|
|
181
|
+
## Recipes & edge cases
|
|
182
|
+
|
|
183
|
+
- **Multiple fields on one page:** Always supply a unique `id` (e.g. `id="billing-address"`,
|
|
184
|
+
`id="shipping-address"`) when rendering more than one `AddressFormField` on the same
|
|
185
|
+
page. Without it, both fields default to `id="wherabouts-field"`, which breaks the
|
|
186
|
+
`htmlFor` association and is invalid HTML.
|
|
187
|
+
- **Passing `error` from form validation vs. API errors:** The `error` prop accepts an
|
|
188
|
+
external string (e.g. from TanStack Form's `field.state.meta.errors`) and renders it
|
|
189
|
+
below the input. API-level errors from the autocomplete itself surface inside the
|
|
190
|
+
dropdown via `AddressAutocomplete`'s built-in error slot, not through this prop.
|
|
191
|
+
- **Debounce / min-chars tuning:** Inherits `AddressAutocomplete` defaults
|
|
192
|
+
(`debounceMs={300}`, `minCharsToSearch={2}`). For high-traffic forms, raise both to
|
|
193
|
+
reduce API call volume.
|
|
194
|
+
- **`sessionToken` for billing:** Pass `newSessionToken()` from the SDK as `sessionToken`
|
|
195
|
+
to group a whole keystroke sequence into a single billable autocomplete session.
|
|
196
|
+
- **Clearing the field:** There is no imperative `clear()` API. If you need to clear
|
|
197
|
+
the field programmatically (e.g. after form submit), unmount and remount the component
|
|
198
|
+
or use a React `key` change.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# ForwardGeocodeInput
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Resolves a free-text address string to coordinates (forward geocoding) as the `query` prop changes. Controlled: the parent owns `query` and receives geocode results via `onResult`. Renders a read-only display input showing the resolved `latitude, longitude` pair.
|
|
6
|
+
|
|
7
|
+
## When to use / when not
|
|
8
|
+
|
|
9
|
+
**Use it when:**
|
|
10
|
+
|
|
11
|
+
- You have a text query (from a search box, form field, or programmatic source) and need the corresponding coordinates without building your own geocode loop.
|
|
12
|
+
- You want a lightweight read-only coordinate display driven by an external input.
|
|
13
|
+
- You need to react to resolved coordinates (e.g. drop a map pin, store lat/lng alongside a form submission).
|
|
14
|
+
|
|
15
|
+
**Don't use it when:**
|
|
16
|
+
|
|
17
|
+
- You need the user to type and search interactively in a single input — use `AddressAutocomplete` instead (it handles debounce, suggestions, and selection in one component).
|
|
18
|
+
- You have coordinates and want the human-readable address — use `ReverseGeocodeInput` instead.
|
|
19
|
+
- You need editable lat/lng fields — wire your own inputs to `useForwardGeocode` directly.
|
|
20
|
+
|
|
21
|
+
## Import & minimal example
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { createWheraboutsClient } from "@wherabouts/sdk";
|
|
25
|
+
import { ForwardGeocodeInput } from "@wherabouts/react-ui";
|
|
26
|
+
|
|
27
|
+
const client = createWheraboutsClient({ apiKey: "..." });
|
|
28
|
+
|
|
29
|
+
function MyComponent() {
|
|
30
|
+
const [query, setQuery] = React.useState("");
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<input value={query} onChange={(e) => setQuery(e.target.value)} />
|
|
35
|
+
<ForwardGeocodeInput
|
|
36
|
+
client={client}
|
|
37
|
+
query={query}
|
|
38
|
+
onResult={(r) => console.log(r.latitude, r.longitude)}
|
|
39
|
+
/>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Worked examples
|
|
46
|
+
|
|
47
|
+
### 1. Store resolved coordinates on form submit
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const [query, setQuery] = React.useState("");
|
|
51
|
+
const [coords, setCoords] = React.useState<{
|
|
52
|
+
lat: number | null;
|
|
53
|
+
lng: number | null;
|
|
54
|
+
}>({ lat: null, lng: null });
|
|
55
|
+
|
|
56
|
+
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Enter address" />
|
|
57
|
+
<ForwardGeocodeInput
|
|
58
|
+
client={client}
|
|
59
|
+
query={query}
|
|
60
|
+
onResult={(r) => setCoords({ lat: r.latitude, lng: r.longitude })}
|
|
61
|
+
placeholder="Resolved coordinates"
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Debounce the query to reduce API requests
|
|
66
|
+
|
|
67
|
+
The component fires a geocode request on every `query` change. If `query` comes from a fast-changing input (e.g. keystrokes), debounce before passing to `query`:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Replace with your preferred debounce hook (e.g. `use-debounce`).
|
|
71
|
+
import { useDebouncedValue } from "your-debounce-hook";
|
|
72
|
+
|
|
73
|
+
const [raw, setRaw] = React.useState("");
|
|
74
|
+
const debouncedQuery = useDebouncedValue(raw, 300);
|
|
75
|
+
|
|
76
|
+
<input value={raw} onChange={(e) => setRaw(e.target.value)} />
|
|
77
|
+
<ForwardGeocodeInput client={client} query={debouncedQuery} onResult={handleResult} />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Skip geocoding when query is empty
|
|
81
|
+
|
|
82
|
+
Pass `query={null}` (or an empty string) to suppress the network request entirely:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<ForwardGeocodeInput client={client} query={query || null} onResult={handleResult} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Props
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Description |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `client` | `WheraboutsClient` | **Required** | SDK client created with `createWheraboutsClient`. |
|
|
93
|
+
| `query` | `string \| null` | **Required** | Address text to geocode. `null` or empty string skips the request. |
|
|
94
|
+
| `onResult` | `(r: { latitude: number \| null; longitude: number \| null; formattedAddress: string \| null }) => void` | — | Called whenever the resolved result changes. All fields are `null` when no result is available. |
|
|
95
|
+
| `placeholder` | `string` | `"Coordinates will appear here"` | Placeholder text for the read-only display input. |
|
|
96
|
+
| `id` | `string` | — | `id` forwarded to the input element. |
|
|
97
|
+
| `className` | `string` | — | Additional CSS class applied to the input element. |
|
|
98
|
+
| `disabled` | `boolean` | `false` | Disables the input. |
|
|
99
|
+
|
|
100
|
+
> **Controlled component:** `ForwardGeocodeInput` does not own `query`. Your component must supply and update it. The component only renders the resolved coordinate string; it does not accept user typing.
|
|
101
|
+
|
|
102
|
+
## Accessibility
|
|
103
|
+
|
|
104
|
+
- The component renders a native `<input type="text" readOnly>` — screen readers announce it as a text field with the current value.
|
|
105
|
+
- Supply a visible `<label>` or `aria-label` / `aria-labelledby` targeting the `id` prop so assistive technologies identify the field.
|
|
106
|
+
- The `disabled` prop produces the standard disabled input state, which is communicated to screen readers automatically.
|
|
107
|
+
- Because the input is read-only, keyboard users cannot accidentally modify the displayed value.
|
|
108
|
+
|
|
109
|
+
## Recipes & edge cases
|
|
110
|
+
|
|
111
|
+
- **`null` query = no request:** Pass `query={null}` to explicitly suppress geocoding (e.g. before the user has typed anything). An empty string `""` also produces no result.
|
|
112
|
+
- **Debouncing the query:** The component fires on every `query` change. Debounce upstream (e.g. 300 ms) if `query` comes from keystroke events to avoid excessive API calls.
|
|
113
|
+
- **`onResult` on every render cycle:** `onResult` is called inside a `useEffect` that runs whenever the resolved `data` changes. If you pass an inline callback, wrap it in `useCallback` to avoid firing on every parent render.
|
|
114
|
+
- **All-null result:** When the geocode fails or returns no match, `onResult` is still called with `{ latitude: null, longitude: null, formattedAddress: null }`. Guard against nulls before using the values.
|
|
115
|
+
- **Display format:** The component renders `"lat.toFixed(4), lng.toFixed(4)"` in the input. If you need raw numeric values, consume them from the `onResult` callback instead.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# ReverseGeocodeInput
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Resolves a `latitude`/`longitude` pair to the nearest address (reverse geocoding). No request is made until both coordinates are non-null.
|
|
6
|
+
|
|
7
|
+
## When to use / when not
|
|
8
|
+
|
|
9
|
+
**Use it when:**
|
|
10
|
+
|
|
11
|
+
- You have coordinates (e.g. from a GPS fix, map click, or device location) and need the human-readable address for display or storage.
|
|
12
|
+
- You want a lightweight read-only address display that reacts to changing coordinates.
|
|
13
|
+
- You need to react to the resolved address (e.g. prefill a form field, log the location label, show the nearest street address).
|
|
14
|
+
|
|
15
|
+
**Don't use it when:**
|
|
16
|
+
|
|
17
|
+
- You need the user to type and search interactively in a single input — use `AddressAutocomplete` instead (it handles debounce, suggestions, and selection in one component).
|
|
18
|
+
- You have a text query and want coordinates — use `ForwardGeocodeInput` instead.
|
|
19
|
+
- You need editable address fields — wire your own inputs to `useReverseGeocode` directly.
|
|
20
|
+
|
|
21
|
+
## Import & minimal example
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { createWheraboutsClient } from "@wherabouts/sdk";
|
|
25
|
+
import { ReverseGeocodeInput } from "@wherabouts/react-ui";
|
|
26
|
+
|
|
27
|
+
const client = createWheraboutsClient({ apiKey: "..." });
|
|
28
|
+
|
|
29
|
+
function MyComponent() {
|
|
30
|
+
const [coords, setCoords] = React.useState<{
|
|
31
|
+
lat: number | null;
|
|
32
|
+
lng: number | null;
|
|
33
|
+
}>({ lat: null, lng: null });
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ReverseGeocodeInput
|
|
37
|
+
client={client}
|
|
38
|
+
latitude={coords.lat}
|
|
39
|
+
longitude={coords.lng}
|
|
40
|
+
onResult={(r) => console.log(r.address, r.distance)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Worked examples
|
|
47
|
+
|
|
48
|
+
### 1. Show address when user clicks a map
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
const [lat, setLat] = React.useState<number | null>(null);
|
|
52
|
+
const [lng, setLng] = React.useState<number | null>(null);
|
|
53
|
+
const [label, setLabel] = React.useState<string>("");
|
|
54
|
+
|
|
55
|
+
// Imagine mapInstance.on("click", ...) sets lat/lng
|
|
56
|
+
<ReverseGeocodeInput
|
|
57
|
+
client={client}
|
|
58
|
+
latitude={lat}
|
|
59
|
+
longitude={lng}
|
|
60
|
+
onResult={(r) => setLabel(r.address ?? "Unknown location")}
|
|
61
|
+
placeholder="Click the map to resolve an address"
|
|
62
|
+
/>
|
|
63
|
+
<p>Selected: {label || "—"}</p>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Display distance alongside the resolved address
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const [distance, setDistance] = React.useState<number | null>(null);
|
|
70
|
+
|
|
71
|
+
<ReverseGeocodeInput
|
|
72
|
+
client={client}
|
|
73
|
+
latitude={-27.4698}
|
|
74
|
+
longitude={153.0251}
|
|
75
|
+
onResult={(r) => setDistance(r.distance)}
|
|
76
|
+
/>
|
|
77
|
+
{distance !== null && (
|
|
78
|
+
<p>Distance from nearest address: {distance.toFixed(1)} m</p>
|
|
79
|
+
)}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Hold the request until both coordinates are available
|
|
83
|
+
|
|
84
|
+
Pass `null` for either coordinate to suppress the network request entirely. The component renders its placeholder until a valid pair arrives:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<ReverseGeocodeInput
|
|
88
|
+
client={client}
|
|
89
|
+
latitude={gpsReady ? lat : null}
|
|
90
|
+
longitude={gpsReady ? lng : null}
|
|
91
|
+
onResult={handleResult}
|
|
92
|
+
placeholder="Waiting for GPS fix…"
|
|
93
|
+
/>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Props
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `client` | `WheraboutsClient` | **Required** | SDK client created with `createWheraboutsClient`. |
|
|
101
|
+
| `latitude` | `number \| null` | **Required** | Latitude to reverse-geocode. `null` suppresses the request. |
|
|
102
|
+
| `longitude` | `number \| null` | **Required** | Longitude to reverse-geocode. `null` suppresses the request. |
|
|
103
|
+
| `onResult` | `(r: { address: string \| null; distance: number \| null }) => void` | — | Called whenever the resolved address changes. Both fields are `null` when no result is available. |
|
|
104
|
+
| `placeholder` | `string` | `"Address will appear here"` | Placeholder text for the read-only display input. |
|
|
105
|
+
| `id` | `string` | — | `id` forwarded to the input element. |
|
|
106
|
+
| `className` | `string` | — | Additional CSS class applied to the input element. |
|
|
107
|
+
| `disabled` | `boolean` | `false` | Disables the input. |
|
|
108
|
+
|
|
109
|
+
> **Null-coordinate handling:** No geocode request is made until **both** `latitude` and `longitude` are non-null. The component renders its placeholder while either coordinate is `null`.
|
|
110
|
+
|
|
111
|
+
## Accessibility
|
|
112
|
+
|
|
113
|
+
- The component renders a native `<input type="text" readOnly>` — screen readers announce it as a text field with the current value.
|
|
114
|
+
- Supply a visible `<label>` or `aria-label` / `aria-labelledby` targeting the `id` prop so assistive technologies identify the field.
|
|
115
|
+
- The `disabled` prop produces the standard disabled input state, which is communicated to screen readers automatically.
|
|
116
|
+
- Because the input is read-only, keyboard users cannot accidentally modify the displayed value.
|
|
117
|
+
|
|
118
|
+
## Recipes & edge cases
|
|
119
|
+
|
|
120
|
+
- **Both coordinates must be non-null:** If either `latitude` or `longitude` is `null`, no request is fired and the input shows the placeholder. Use this deliberately to gate the request on GPS availability or user interaction.
|
|
121
|
+
- **`onResult` on every render cycle:** `onResult` is called inside a `useEffect` that runs whenever the resolved data changes. If you pass an inline callback, wrap it in `useCallback` to avoid firing on every parent render.
|
|
122
|
+
- **All-null result:** When reverse geocoding returns no match, `onResult` is still called with `{ address: null, distance: null }`. Guard against nulls before using the values.
|
|
123
|
+
- **`address` is `formattedAddress` from the API:** The `address` field in `onResult` is the formatted address string from the nearest result (or `null` if unavailable). It is also what the read-only input displays.
|
|
124
|
+
- **`distance` is in metres:** The `distance` field reports how far the resolved address is from the supplied coordinates, in metres.
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wherabouts/react-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Production-ready React components for Wherabouts SDK — address autocomplete, geocoding, and form fields with accessibility and customization.",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -17,38 +17,45 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
+
"docs",
|
|
20
21
|
"README.md",
|
|
21
|
-
"CHANGELOG.md"
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"LICENSE"
|
|
22
24
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "tsup && node scripts/build-css.mjs",
|
|
25
|
-
"dev": "tsup --watch",
|
|
26
|
-
"test": "vitest run",
|
|
27
|
-
"test:watch": "vitest",
|
|
28
|
-
"prepublishOnly": "pnpm build"
|
|
29
|
-
},
|
|
30
25
|
"peerDependencies": {
|
|
31
26
|
"react": ">=19.0.0",
|
|
32
27
|
"react-dom": ">=19.0.0",
|
|
33
|
-
"@wherabouts/sdk": ">=0.
|
|
28
|
+
"@wherabouts/sdk": ">=0.5.0",
|
|
34
29
|
"@wherabouts/react": ">=0.2.0"
|
|
35
30
|
},
|
|
36
31
|
"devDependencies": {
|
|
32
|
+
"@storybook/addon-a11y": "^9.0.0",
|
|
33
|
+
"@storybook/react-vite": "^9.0.0",
|
|
37
34
|
"@testing-library/jest-dom": "^6.1.5",
|
|
38
35
|
"@testing-library/react": "^15.0.0",
|
|
39
36
|
"@testing-library/user-event": "^14.5.1",
|
|
40
|
-
"@types/react": "
|
|
41
|
-
"@types/react-dom": "
|
|
42
|
-
"@wherabouts/react": "workspace:*",
|
|
43
|
-
"@wherabouts/sdk": "workspace:*",
|
|
37
|
+
"@types/react": "^19.2.10",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
44
39
|
"class-variance-authority": "^0.7.0",
|
|
45
40
|
"clsx": "^2.1.1",
|
|
46
41
|
"jsdom": "^23.0.1",
|
|
47
|
-
"react": "
|
|
48
|
-
"react-dom": "
|
|
42
|
+
"react": "^19.2.3",
|
|
43
|
+
"react-dom": "^19.2.3",
|
|
44
|
+
"storybook": "^9.0.0",
|
|
49
45
|
"tailwind-merge": "^2.3.0",
|
|
50
46
|
"tsup": "^8.0.2",
|
|
51
47
|
"typescript": "^5.4.4",
|
|
52
|
-
"
|
|
48
|
+
"vite": "^7.0.2",
|
|
49
|
+
"vitest": "^1.2.0",
|
|
50
|
+
"@wherabouts/sdk": "0.6.0",
|
|
51
|
+
"@wherabouts/react": "0.3.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup && node scripts/build-css.mjs",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"storybook": "storybook dev -p 6006 --no-open",
|
|
59
|
+
"build-storybook": "storybook build"
|
|
53
60
|
}
|
|
54
|
-
}
|
|
61
|
+
}
|