react-listing-engine 0.1.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/chunk-2T5PE5MB.js +2 -0
- package/dist/chunk-2VUHLHHX.cjs +2 -0
- package/dist/chunk-35KYEBAC.js +2 -0
- package/dist/chunk-5FBI2WIM.js +2 -0
- package/dist/chunk-5O36WI7H.cjs +2 -0
- package/dist/chunk-6PZHSHU3.js +2 -0
- package/dist/chunk-CHNUE46K.cjs +2 -0
- package/dist/chunk-LCQZIWBO.cjs +2 -0
- package/dist/chunk-N3OZ2HK2.cjs +2 -0
- package/dist/chunk-PFPHWJAA.js +2 -0
- package/dist/components-provider-CF0Mo0B8.d.cts +120 -0
- package/dist/components-provider-CjMxkxrP.d.ts +120 -0
- package/dist/entity-adapter.interface-BDgbSxhq.d.cts +33 -0
- package/dist/entity-adapter.interface-BDgbSxhq.d.ts +33 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +674 -0
- package/dist/index.d.ts +674 -0
- package/dist/index.js +2 -0
- package/dist/listing-app-De6OqpUC.d.ts +199 -0
- package/dist/listing-app-pKMQBuQI.d.cts +199 -0
- package/dist/listing-config-options.interface-ZJYY_ZvR.d.cts +12 -0
- package/dist/listing-config-options.interface-ZJYY_ZvR.d.ts +12 -0
- package/dist/map-provider.interface-BMnwW3ob.d.cts +37 -0
- package/dist/map-provider.interface-BxexMT3X.d.ts +37 -0
- package/dist/maps/google/index.cjs +2 -0
- package/dist/maps/google/index.d.cts +39 -0
- package/dist/maps/google/index.d.ts +39 -0
- package/dist/maps/google/index.js +2 -0
- package/dist/presets/rental/index.cjs +2 -0
- package/dist/presets/rental/index.d.cts +266 -0
- package/dist/presets/rental/index.d.ts +266 -0
- package/dist/presets/rental/index.js +2 -0
- package/dist/shadcn/index.cjs +2 -0
- package/dist/shadcn/index.d.cts +261 -0
- package/dist/shadcn/index.d.ts +261 -0
- package/dist/shadcn/index.js +2 -0
- package/dist/styled/index.cjs +2 -0
- package/dist/styled/index.d.cts +123 -0
- package/dist/styled/index.d.ts +123 -0
- package/dist/styled/index.js +2 -0
- package/dist/styles.css +972 -0
- package/dist/testing/index.cjs +2 -0
- package/dist/testing/index.d.cts +51 -0
- package/dist/testing/index.d.ts +51 -0
- package/dist/testing/index.js +2 -0
- package/dist/url-sync.controller-CsU_QxoS.d.cts +89 -0
- package/dist/url-sync.controller-DX67JivW.d.ts +89 -0
- package/package.json +143 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { c as IListingCardProps, d as IListingComponents, e as IListingEmptyProps, f as IListingFilterPanelProps, g as IListingLoadingProps, h as IListingMarkerProps, i as IListingPopupProps, j as IListingResultHeaderProps, k as IListingSearchProps, l as IListingSidebarProps, I as IListingToolbarProps, D as DatasetDefinition, F as FilterRegistry } from '../components-provider-CF0Mo0B8.cjs';
|
|
4
|
+
import { L as LatLng } from '../entity-adapter.interface-BDgbSxhq.cjs';
|
|
5
|
+
import { M as MapProvider } from '../map-provider.interface-BMnwW3ob.cjs';
|
|
6
|
+
import { I as IListingConfigOptions } from '../listing-config-options.interface-ZJYY_ZvR.cjs';
|
|
7
|
+
import { U as UrlSyncController } from '../url-sync.controller-CsU_QxoS.cjs';
|
|
8
|
+
import { ClassValue } from 'clsx';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convenience wrapper around `ListingComponentsProvider` that wires in every
|
|
12
|
+
* package-shipped `Default*` component (via `shadcnDefaultComponents`, the
|
|
13
|
+
* shared source of truth also used by `ListingApp`'s merge). Use this if you
|
|
14
|
+
* want the styled adapter out of the box; use `ListingComponentsProvider`
|
|
15
|
+
* directly (with your own components for some or all slots) otherwise -- the
|
|
16
|
+
* two compose fine since `ListingComponentsProvider` falls back per-slot.
|
|
17
|
+
* Mirrors `react-wizard-engine`'s `WizardComponentsProviderWithDefaults`.
|
|
18
|
+
*/
|
|
19
|
+
declare function ListingComponentsProviderWithDefaults({ children }: {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}): react.JSX.Element;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default styled `Card` slot. Renders an optional image, title, subtitle,
|
|
25
|
+
* price and badge from a plain view-model item, and doubles as the
|
|
26
|
+
* clickable/selectable surface (`onSelect`) -- when `onSelect` is provided
|
|
27
|
+
* the card is a `button` rather than a `div` + click handler so it is
|
|
28
|
+
* keyboard operable and announces as a toggle (`aria-pressed`) for free.
|
|
29
|
+
* When `onSelect` is absent (display-only usage) it renders as a
|
|
30
|
+
* non-interactive `<article>` with the same visual classes, minus the
|
|
31
|
+
* button/focus/aria-pressed semantics -- so a purely presentational card
|
|
32
|
+
* doesn't add a no-op tab stop.
|
|
33
|
+
*/
|
|
34
|
+
declare function DefaultCard({ item, selected, onSelect }: IListingCardProps): react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Every `/shadcn` styled default, keyed by slot -- the single source of truth
|
|
38
|
+
* both `ListingComponentsProviderWithDefaults` (spreads it verbatim) and
|
|
39
|
+
* `ListingApp` (spreads a `components` override OVER it) build on.
|
|
40
|
+
*
|
|
41
|
+
* Kept as a plain object, not JSX, so a caller can merge it with
|
|
42
|
+
* `{ ...shadcnDefaultComponents, ...overrides }` and hand the result to ONE
|
|
43
|
+
* `ListingComponentsProvider`. This matters because `ListingComponentsProvider`
|
|
44
|
+
* merges `provided ?? ITS OWN private, UNSTYLED fallbacks` per slot -- it does
|
|
45
|
+
* not read the parent context (see `src/react/components-provider.tsx`).
|
|
46
|
+
* Nesting a `ListingComponentsProvider` (with only the overrides) INSIDE a
|
|
47
|
+
* `ListingComponentsProviderWithDefaults` would therefore silently reset
|
|
48
|
+
* every un-overridden slot back to the bare fallback instead of the shadcn
|
|
49
|
+
* default -- exactly the bug this object exists to avoid.
|
|
50
|
+
*/
|
|
51
|
+
declare const shadcnDefaultComponents: IListingComponents;
|
|
52
|
+
|
|
53
|
+
/** Default styled `Empty` slot: a centered empty state with an icon, heading and hint. */
|
|
54
|
+
declare function DefaultEmpty(_props: IListingEmptyProps): react.JSX.Element;
|
|
55
|
+
|
|
56
|
+
/** Default styled `FilterPanel` slot: stacks one filter control per row inside `Sidebar`. */
|
|
57
|
+
declare function DefaultFilterPanel({ children }: IListingFilterPanelProps): react.JSX.Element;
|
|
58
|
+
|
|
59
|
+
/** Default styled `Loading` slot: a shimmering skeleton list, announced via `role="status"`. */
|
|
60
|
+
declare function DefaultLoading(_props: IListingLoadingProps): react.JSX.Element;
|
|
61
|
+
|
|
62
|
+
/** Default styled `Marker` slot: a small price pill for a map point. */
|
|
63
|
+
declare function DefaultMarker({ point }: IListingMarkerProps): react.JSX.Element;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default styled `Popup` slot: a small card with an accessible close button.
|
|
67
|
+
* `role="group"` + `aria-label` (rather than `role="dialog"`) since this is a
|
|
68
|
+
* non-modal, non-focus-trapped popup anchored to a map marker -- `dialog`
|
|
69
|
+
* without modality/focus management would misrepresent it to AT users.
|
|
70
|
+
*/
|
|
71
|
+
declare function DefaultPopup({ entity, onClose }: IListingPopupProps): react.JSX.Element;
|
|
72
|
+
|
|
73
|
+
/** Default styled `ResultHeader` slot: `"{count} results"`, or `"{count} of {total} results"` when a distinct total is known. */
|
|
74
|
+
declare function DefaultResultHeader({ count, total }: IListingResultHeaderProps): react.JSX.Element;
|
|
75
|
+
|
|
76
|
+
/** Default styled `Search` slot: a plain styled text input. */
|
|
77
|
+
declare function DefaultSearch({ value, onChange, placeholder }: IListingSearchProps): react.JSX.Element;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Default styled `Sidebar` slot: the outer chrome for the filters column in
|
|
81
|
+
* `ListingLayout`. Full width and unbordered on mobile (stacks above the
|
|
82
|
+
* results in normal document flow); becomes a fixed-width bordered rail from
|
|
83
|
+
* `md` up.
|
|
84
|
+
*/
|
|
85
|
+
declare function DefaultSidebar({ children }: IListingSidebarProps): react.JSX.Element;
|
|
86
|
+
|
|
87
|
+
/** Default styled `Toolbar` slot: a bordered bar wrapping arbitrary children. */
|
|
88
|
+
declare function DefaultToolbar({ children }: IListingToolbarProps): react.JSX.Element;
|
|
89
|
+
|
|
90
|
+
interface IListingLayoutProps {
|
|
91
|
+
/**
|
|
92
|
+
* Optional search box, wired by the CONSUMER into their own `TFilters`
|
|
93
|
+
* shape. `ListingLayout` is filters-shape-erased (same reasoning as the
|
|
94
|
+
* engine itself -- see `ListingEngineOptions`'s docstring) so it cannot
|
|
95
|
+
* derive a generic "search" filter on its own; when omitted, no search box
|
|
96
|
+
* is rendered at all. Documented, deliberate scope: wiring a named
|
|
97
|
+
* "search" filter convention end-to-end is a future enhancement.
|
|
98
|
+
*/
|
|
99
|
+
search?: {
|
|
100
|
+
value: string;
|
|
101
|
+
onChange: (value: string) => void;
|
|
102
|
+
placeholder?: string;
|
|
103
|
+
};
|
|
104
|
+
className?: string;
|
|
105
|
+
/** Extra content rendered at the end of the filter bar, alongside `ListingResultHeader` (e.g. a sort control). */
|
|
106
|
+
toolbarEnd?: ReactNode;
|
|
107
|
+
/**
|
|
108
|
+
* Whether `ListingLayout` fetches the first page itself on mount
|
|
109
|
+
* (`engine.applyFilters({})`). Defaults to `true` (batteries-included).
|
|
110
|
+
* Pass `false` to defer the initial fetch to the caller -- e.g. a
|
|
111
|
+
* search-before-results flow, or when `urlSync` hydrates filters from the
|
|
112
|
+
* URL and should drive the first fetch instead of racing it.
|
|
113
|
+
*/
|
|
114
|
+
autoFetch?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Forwarded verbatim to `<ListingMap center={mapCenter} />`. Omit to get
|
|
117
|
+
* `ListingMap`'s turnkey auto-fit default (frames the map to its own data
|
|
118
|
+
* once it loads) -- passing `mapCenter` opts OUT of auto-fit entirely, on
|
|
119
|
+
* the theory that an explicit initial view is a deliberate choice
|
|
120
|
+
* (see `ListingMap`'s doc comment's "Auto-fit" section).
|
|
121
|
+
*/
|
|
122
|
+
mapCenter?: LatLng;
|
|
123
|
+
/** Forwarded verbatim to `<ListingMap zoom={mapZoom} />`. See `mapCenter`. */
|
|
124
|
+
mapZoom?: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Full, responsive default listing experience, matching the "top filter bar
|
|
128
|
+
* + list-left/map-right split" pattern of a typical `/find` real-estate
|
|
129
|
+
* search page: a sticky horizontal filter bar (search + filter groups +
|
|
130
|
+
* result count), then below it a split -- a scrollable card grid on the left
|
|
131
|
+
* and a full-height map on the right. Composed entirely from the
|
|
132
|
+
* structure-only compound components in `~/react` plus the injected slot
|
|
133
|
+
* components (via `useListingComponents()` for `Search`, and implicitly
|
|
134
|
+
* through each compound component for `Card`/`Empty`/`Loading`/etc.) -- this
|
|
135
|
+
* file adds layout/chrome only, no new business logic.
|
|
136
|
+
*
|
|
137
|
+
* Layout choices (documented, not the only valid ones):
|
|
138
|
+
* - FILTER BAR (`data-slot="listing-layout-filter-bar"`): `sticky top-0`,
|
|
139
|
+
* `border-b`/`bg-background`, and a `flex flex-wrap items-end gap-3` row
|
|
140
|
+
* containing the injected `Search` (when `search` is passed),
|
|
141
|
+
* `<ListingFilters>` (given the same horizontal row className plus a
|
|
142
|
+
* `min-w-0` `groupClassName` so individual groups can shrink instead of
|
|
143
|
+
* forcing overflow), and a trailing `ml-auto` cluster with
|
|
144
|
+
* `<ListingResultHeader>` + `toolbarEnd`. `flex-wrap` is what makes this
|
|
145
|
+
* gracefully reflow on narrow widths -- groups drop to new lines instead
|
|
146
|
+
* of overflowing or requiring a separate mobile-only layout.
|
|
147
|
+
* - SPLIT (`data-slot="listing-layout-split"`): a single CSS grid,
|
|
148
|
+
* `md:grid-cols-[minmax(340px,42%)_1fr]` from `md` up (list column floors
|
|
149
|
+
* at 340px, caps at 42% of the split's width; map takes the rest). Below
|
|
150
|
+
* `md` there is no grid -- just two full-width panels, and exactly one is
|
|
151
|
+
* visible at a time (see the mobile toggle below).
|
|
152
|
+
* - LIST region (`data-slot="listing-layout-list"`): `overflow-y-auto`, its
|
|
153
|
+
* own scroll container so browsing the list never requires scrolling the
|
|
154
|
+
* map out of view. Holds `<ListingList>` (an auto-fill card grid) and
|
|
155
|
+
* `<ListingPagination>`.
|
|
156
|
+
* - MAP region (`data-slot="listing-layout-map"`): fills the split's full
|
|
157
|
+
* height via the grid's default stretch alignment (no explicit height
|
|
158
|
+
* needed), plus `md:sticky md:top-0` so that if a consuming app ever lets
|
|
159
|
+
* `ListingLayout` sit inside a naturally document-scrolling page (rather
|
|
160
|
+
* than the fixed-height shell this component defaults to via
|
|
161
|
+
* `h-full min-h-0`), the map still pins in place while the list scrolls
|
|
162
|
+
* past -- inert (but harmless) in the default fixed-height composition,
|
|
163
|
+
* where there is nothing above the split to scroll past in the first
|
|
164
|
+
* place. Renders `<ListingMap>` with a default centered fallback message
|
|
165
|
+
* (`MAP_FALLBACK`) for when no `MapProvider` is configured, so the pane
|
|
166
|
+
* never looks broken/blank.
|
|
167
|
+
* - MOBILE LIST/MAP TOGGLE (`data-slot="listing-layout-mobile-toggle"`,
|
|
168
|
+
* `md:hidden`): a small two-button segmented control, local `useState`
|
|
169
|
+
* (`mobileView`), defaulting to `'list'`. Both the list and map regions
|
|
170
|
+
* stay mounted at all times (never remounted on toggle -- that would
|
|
171
|
+
* re-trigger `ListingList`'s/`ListingMap`'s own mount effects for no
|
|
172
|
+
* reason); only their visibility flips via `hidden md:block` /
|
|
173
|
+
* `block md:block`, i.e. the toggle only ever matters below `md` -- at
|
|
174
|
+
* `md` and up both regions are always visible side by side and the toggle
|
|
175
|
+
* control itself is hidden.
|
|
176
|
+
* - Fetches the first page itself on mount (`engine.applyFilters({})`) by
|
|
177
|
+
* default: none of the structure-only `~/react` compound components do
|
|
178
|
+
* this (they are deliberately side-effect-free), so as the
|
|
179
|
+
* batteries-included "full experience" entry point, `ListingLayout` is the
|
|
180
|
+
* natural, single owner of that one bootstrapping side effect. Pass
|
|
181
|
+
* `autoFetch={false}` to opt out and drive the first fetch yourself (e.g.
|
|
182
|
+
* search-before-results, or when `urlSync` hydrates filters from the URL).
|
|
183
|
+
*/
|
|
184
|
+
declare function ListingLayout({ search, className, toolbarEnd, autoFetch, mapCenter, mapZoom, }: IListingLayoutProps): react.JSX.Element;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* `ListingApp.map` accepts either a ready-made `MapProvider` (any
|
|
188
|
+
* implementation -- Google, a fake, a future provider) or, as a convenience,
|
|
189
|
+
* a bare Google Maps API key. See `useResolvedMap`'s doc comment for why the
|
|
190
|
+
* key shorthand is resolved via a dynamic `import()` rather than a static one.
|
|
191
|
+
*
|
|
192
|
+
* Both shapes optionally carry `center`/`zoom` -- the initial view, forwarded
|
|
193
|
+
* through `ListingLayout`'s `mapCenter`/`mapZoom` straight into
|
|
194
|
+
* `ListingMap`'s own `center`/`zoom` props. Omit them to get `ListingMap`'s
|
|
195
|
+
* turnkey auto-fit default instead (see that component's doc comment);
|
|
196
|
+
* supplying `center` opts out of auto-fit entirely.
|
|
197
|
+
*/
|
|
198
|
+
type ListingAppMapProp = {
|
|
199
|
+
provider: MapProvider;
|
|
200
|
+
center?: LatLng;
|
|
201
|
+
zoom?: number;
|
|
202
|
+
} | {
|
|
203
|
+
apiKey: string;
|
|
204
|
+
mapId?: string;
|
|
205
|
+
center?: LatLng;
|
|
206
|
+
zoom?: number;
|
|
207
|
+
};
|
|
208
|
+
interface ListingAppProps<TFilters> {
|
|
209
|
+
/**
|
|
210
|
+
* One or more marker-layer datasets; the FIRST entry is the primary
|
|
211
|
+
* dataset (drives the results list + pagination) -- same "insertion order"
|
|
212
|
+
* rule `composeListingProviders`/`withDataset` already follow. Entity-erased
|
|
213
|
+
* (`any`, not `TEntity`) for the same reason `DatasetRegistry<unknown,
|
|
214
|
+
* TFilters>` is: one array can legitimately hold heterogeneous layers (a
|
|
215
|
+
* properties dataset and a businesses dataset have different entity types)
|
|
216
|
+
* -- see `compose-listing-providers.ts`'s `withDataset` doc comment.
|
|
217
|
+
*/
|
|
218
|
+
datasets: DatasetDefinition<any, TFilters>[];
|
|
219
|
+
/** e.g. `withRentalFilters()`, or any `(reg) => { reg.add(...); }` callback -- forwarded verbatim to `withFilters`. */
|
|
220
|
+
filters?: (reg: FilterRegistry<TFilters>) => void;
|
|
221
|
+
/** A ready `MapProvider`, or `{ apiKey, mapId? }` to build a `googleProvider` internally. Omit for no map (the styled layout shows a "Map unavailable" fallback). */
|
|
222
|
+
map?: ListingAppMapProp;
|
|
223
|
+
/** Slot overrides, merged OVER the shadcn styled defaults -- see this file's doc comment for how the merge works. */
|
|
224
|
+
components?: Partial<IListingComponents>;
|
|
225
|
+
/** A `UrlSyncController<TFilters>` (e.g. `rentalUrlSync()`), or `true`. `true` is a documented no-op: the library is filters-shape-erased and cannot generically derive a `TFilters <-> QueryParams` mapping, which is exactly why domain presets (like `rentalUrlSync`) exist to build a real controller. */
|
|
226
|
+
urlSync?: UrlSyncController<TFilters> | boolean;
|
|
227
|
+
initialFilters?: TFilters;
|
|
228
|
+
config?: Partial<IListingConfigOptions>;
|
|
229
|
+
className?: string;
|
|
230
|
+
search?: IListingLayoutProps['search'];
|
|
231
|
+
autoFetch?: boolean;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Turnkey, batteries-included entry point: pass datasets + filters + a map +
|
|
235
|
+
* component overrides + URL sync, and `ListingApp` composes
|
|
236
|
+
* `composeListingProviders(...)`, `<ListingProvider>`, the styled `/shadcn`
|
|
237
|
+
* defaults, and `<ListingLayout>` for you. Equivalent to (and internally
|
|
238
|
+
* built from) the lower-level primitives every other `/shadcn` example wires
|
|
239
|
+
* by hand -- reach for those directly when you need something `ListingApp`
|
|
240
|
+
* doesn't expose (e.g. multiple `<ListingLayout>`s sharing one engine).
|
|
241
|
+
*
|
|
242
|
+
* `TEntity` still can't be inferred from these props (`datasets` narrows it,
|
|
243
|
+
* but a multi-dataset array widens back to the union/`unknown` in practice)
|
|
244
|
+
* -- annotate the call site when entity-level typing matters, exactly like
|
|
245
|
+
* `<ListingProvider<TEntity, TFilters>>` itself.
|
|
246
|
+
*
|
|
247
|
+
* COMPONENTS MERGE: `components` overrides are applied ON TOP of the shadcn
|
|
248
|
+
* styled defaults via ONE explicit `{ ...shadcnDefaultComponents, ...components }`
|
|
249
|
+
* object, passed to a single `<ListingComponentsProvider>` -- never as a
|
|
250
|
+
* nested `<ListingComponentsProvider>` inside `<ListingComponentsProviderWithDefaults>`.
|
|
251
|
+
* `ListingComponentsProvider` merges `provided ?? ITS OWN private, unstyled
|
|
252
|
+
* fallbacks` per slot (it does not read the parent context -- see
|
|
253
|
+
* `src/react/components-provider.tsx`), so nesting would silently discard
|
|
254
|
+
* every un-overridden shadcn default instead of keeping it.
|
|
255
|
+
*/
|
|
256
|
+
declare function ListingApp<TEntity, TFilters>(props: ListingAppProps<TFilters>): react.JSX.Element | null;
|
|
257
|
+
|
|
258
|
+
/** Merges class-name fragments, letting `tailwind-merge` resolve conflicting Tailwind utilities (last one wins). Mirrors `react-wizard-engine`'s `~/utils/cn`. */
|
|
259
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
260
|
+
|
|
261
|
+
export { DefaultCard, DefaultEmpty, DefaultFilterPanel, DefaultLoading, DefaultMarker, DefaultPopup, DefaultResultHeader, DefaultSearch, DefaultSidebar, DefaultToolbar, type IListingLayoutProps, ListingApp, type ListingAppMapProp, type ListingAppProps, ListingComponentsProviderWithDefaults, ListingLayout, cn, shadcnDefaultComponents };
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { c as IListingCardProps, d as IListingComponents, e as IListingEmptyProps, f as IListingFilterPanelProps, g as IListingLoadingProps, h as IListingMarkerProps, i as IListingPopupProps, j as IListingResultHeaderProps, k as IListingSearchProps, l as IListingSidebarProps, I as IListingToolbarProps, D as DatasetDefinition, F as FilterRegistry } from '../components-provider-CjMxkxrP.js';
|
|
4
|
+
import { L as LatLng } from '../entity-adapter.interface-BDgbSxhq.js';
|
|
5
|
+
import { M as MapProvider } from '../map-provider.interface-BxexMT3X.js';
|
|
6
|
+
import { I as IListingConfigOptions } from '../listing-config-options.interface-ZJYY_ZvR.js';
|
|
7
|
+
import { U as UrlSyncController } from '../url-sync.controller-DX67JivW.js';
|
|
8
|
+
import { ClassValue } from 'clsx';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convenience wrapper around `ListingComponentsProvider` that wires in every
|
|
12
|
+
* package-shipped `Default*` component (via `shadcnDefaultComponents`, the
|
|
13
|
+
* shared source of truth also used by `ListingApp`'s merge). Use this if you
|
|
14
|
+
* want the styled adapter out of the box; use `ListingComponentsProvider`
|
|
15
|
+
* directly (with your own components for some or all slots) otherwise -- the
|
|
16
|
+
* two compose fine since `ListingComponentsProvider` falls back per-slot.
|
|
17
|
+
* Mirrors `react-wizard-engine`'s `WizardComponentsProviderWithDefaults`.
|
|
18
|
+
*/
|
|
19
|
+
declare function ListingComponentsProviderWithDefaults({ children }: {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}): react.JSX.Element;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default styled `Card` slot. Renders an optional image, title, subtitle,
|
|
25
|
+
* price and badge from a plain view-model item, and doubles as the
|
|
26
|
+
* clickable/selectable surface (`onSelect`) -- when `onSelect` is provided
|
|
27
|
+
* the card is a `button` rather than a `div` + click handler so it is
|
|
28
|
+
* keyboard operable and announces as a toggle (`aria-pressed`) for free.
|
|
29
|
+
* When `onSelect` is absent (display-only usage) it renders as a
|
|
30
|
+
* non-interactive `<article>` with the same visual classes, minus the
|
|
31
|
+
* button/focus/aria-pressed semantics -- so a purely presentational card
|
|
32
|
+
* doesn't add a no-op tab stop.
|
|
33
|
+
*/
|
|
34
|
+
declare function DefaultCard({ item, selected, onSelect }: IListingCardProps): react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Every `/shadcn` styled default, keyed by slot -- the single source of truth
|
|
38
|
+
* both `ListingComponentsProviderWithDefaults` (spreads it verbatim) and
|
|
39
|
+
* `ListingApp` (spreads a `components` override OVER it) build on.
|
|
40
|
+
*
|
|
41
|
+
* Kept as a plain object, not JSX, so a caller can merge it with
|
|
42
|
+
* `{ ...shadcnDefaultComponents, ...overrides }` and hand the result to ONE
|
|
43
|
+
* `ListingComponentsProvider`. This matters because `ListingComponentsProvider`
|
|
44
|
+
* merges `provided ?? ITS OWN private, UNSTYLED fallbacks` per slot -- it does
|
|
45
|
+
* not read the parent context (see `src/react/components-provider.tsx`).
|
|
46
|
+
* Nesting a `ListingComponentsProvider` (with only the overrides) INSIDE a
|
|
47
|
+
* `ListingComponentsProviderWithDefaults` would therefore silently reset
|
|
48
|
+
* every un-overridden slot back to the bare fallback instead of the shadcn
|
|
49
|
+
* default -- exactly the bug this object exists to avoid.
|
|
50
|
+
*/
|
|
51
|
+
declare const shadcnDefaultComponents: IListingComponents;
|
|
52
|
+
|
|
53
|
+
/** Default styled `Empty` slot: a centered empty state with an icon, heading and hint. */
|
|
54
|
+
declare function DefaultEmpty(_props: IListingEmptyProps): react.JSX.Element;
|
|
55
|
+
|
|
56
|
+
/** Default styled `FilterPanel` slot: stacks one filter control per row inside `Sidebar`. */
|
|
57
|
+
declare function DefaultFilterPanel({ children }: IListingFilterPanelProps): react.JSX.Element;
|
|
58
|
+
|
|
59
|
+
/** Default styled `Loading` slot: a shimmering skeleton list, announced via `role="status"`. */
|
|
60
|
+
declare function DefaultLoading(_props: IListingLoadingProps): react.JSX.Element;
|
|
61
|
+
|
|
62
|
+
/** Default styled `Marker` slot: a small price pill for a map point. */
|
|
63
|
+
declare function DefaultMarker({ point }: IListingMarkerProps): react.JSX.Element;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default styled `Popup` slot: a small card with an accessible close button.
|
|
67
|
+
* `role="group"` + `aria-label` (rather than `role="dialog"`) since this is a
|
|
68
|
+
* non-modal, non-focus-trapped popup anchored to a map marker -- `dialog`
|
|
69
|
+
* without modality/focus management would misrepresent it to AT users.
|
|
70
|
+
*/
|
|
71
|
+
declare function DefaultPopup({ entity, onClose }: IListingPopupProps): react.JSX.Element;
|
|
72
|
+
|
|
73
|
+
/** Default styled `ResultHeader` slot: `"{count} results"`, or `"{count} of {total} results"` when a distinct total is known. */
|
|
74
|
+
declare function DefaultResultHeader({ count, total }: IListingResultHeaderProps): react.JSX.Element;
|
|
75
|
+
|
|
76
|
+
/** Default styled `Search` slot: a plain styled text input. */
|
|
77
|
+
declare function DefaultSearch({ value, onChange, placeholder }: IListingSearchProps): react.JSX.Element;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Default styled `Sidebar` slot: the outer chrome for the filters column in
|
|
81
|
+
* `ListingLayout`. Full width and unbordered on mobile (stacks above the
|
|
82
|
+
* results in normal document flow); becomes a fixed-width bordered rail from
|
|
83
|
+
* `md` up.
|
|
84
|
+
*/
|
|
85
|
+
declare function DefaultSidebar({ children }: IListingSidebarProps): react.JSX.Element;
|
|
86
|
+
|
|
87
|
+
/** Default styled `Toolbar` slot: a bordered bar wrapping arbitrary children. */
|
|
88
|
+
declare function DefaultToolbar({ children }: IListingToolbarProps): react.JSX.Element;
|
|
89
|
+
|
|
90
|
+
interface IListingLayoutProps {
|
|
91
|
+
/**
|
|
92
|
+
* Optional search box, wired by the CONSUMER into their own `TFilters`
|
|
93
|
+
* shape. `ListingLayout` is filters-shape-erased (same reasoning as the
|
|
94
|
+
* engine itself -- see `ListingEngineOptions`'s docstring) so it cannot
|
|
95
|
+
* derive a generic "search" filter on its own; when omitted, no search box
|
|
96
|
+
* is rendered at all. Documented, deliberate scope: wiring a named
|
|
97
|
+
* "search" filter convention end-to-end is a future enhancement.
|
|
98
|
+
*/
|
|
99
|
+
search?: {
|
|
100
|
+
value: string;
|
|
101
|
+
onChange: (value: string) => void;
|
|
102
|
+
placeholder?: string;
|
|
103
|
+
};
|
|
104
|
+
className?: string;
|
|
105
|
+
/** Extra content rendered at the end of the filter bar, alongside `ListingResultHeader` (e.g. a sort control). */
|
|
106
|
+
toolbarEnd?: ReactNode;
|
|
107
|
+
/**
|
|
108
|
+
* Whether `ListingLayout` fetches the first page itself on mount
|
|
109
|
+
* (`engine.applyFilters({})`). Defaults to `true` (batteries-included).
|
|
110
|
+
* Pass `false` to defer the initial fetch to the caller -- e.g. a
|
|
111
|
+
* search-before-results flow, or when `urlSync` hydrates filters from the
|
|
112
|
+
* URL and should drive the first fetch instead of racing it.
|
|
113
|
+
*/
|
|
114
|
+
autoFetch?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Forwarded verbatim to `<ListingMap center={mapCenter} />`. Omit to get
|
|
117
|
+
* `ListingMap`'s turnkey auto-fit default (frames the map to its own data
|
|
118
|
+
* once it loads) -- passing `mapCenter` opts OUT of auto-fit entirely, on
|
|
119
|
+
* the theory that an explicit initial view is a deliberate choice
|
|
120
|
+
* (see `ListingMap`'s doc comment's "Auto-fit" section).
|
|
121
|
+
*/
|
|
122
|
+
mapCenter?: LatLng;
|
|
123
|
+
/** Forwarded verbatim to `<ListingMap zoom={mapZoom} />`. See `mapCenter`. */
|
|
124
|
+
mapZoom?: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Full, responsive default listing experience, matching the "top filter bar
|
|
128
|
+
* + list-left/map-right split" pattern of a typical `/find` real-estate
|
|
129
|
+
* search page: a sticky horizontal filter bar (search + filter groups +
|
|
130
|
+
* result count), then below it a split -- a scrollable card grid on the left
|
|
131
|
+
* and a full-height map on the right. Composed entirely from the
|
|
132
|
+
* structure-only compound components in `~/react` plus the injected slot
|
|
133
|
+
* components (via `useListingComponents()` for `Search`, and implicitly
|
|
134
|
+
* through each compound component for `Card`/`Empty`/`Loading`/etc.) -- this
|
|
135
|
+
* file adds layout/chrome only, no new business logic.
|
|
136
|
+
*
|
|
137
|
+
* Layout choices (documented, not the only valid ones):
|
|
138
|
+
* - FILTER BAR (`data-slot="listing-layout-filter-bar"`): `sticky top-0`,
|
|
139
|
+
* `border-b`/`bg-background`, and a `flex flex-wrap items-end gap-3` row
|
|
140
|
+
* containing the injected `Search` (when `search` is passed),
|
|
141
|
+
* `<ListingFilters>` (given the same horizontal row className plus a
|
|
142
|
+
* `min-w-0` `groupClassName` so individual groups can shrink instead of
|
|
143
|
+
* forcing overflow), and a trailing `ml-auto` cluster with
|
|
144
|
+
* `<ListingResultHeader>` + `toolbarEnd`. `flex-wrap` is what makes this
|
|
145
|
+
* gracefully reflow on narrow widths -- groups drop to new lines instead
|
|
146
|
+
* of overflowing or requiring a separate mobile-only layout.
|
|
147
|
+
* - SPLIT (`data-slot="listing-layout-split"`): a single CSS grid,
|
|
148
|
+
* `md:grid-cols-[minmax(340px,42%)_1fr]` from `md` up (list column floors
|
|
149
|
+
* at 340px, caps at 42% of the split's width; map takes the rest). Below
|
|
150
|
+
* `md` there is no grid -- just two full-width panels, and exactly one is
|
|
151
|
+
* visible at a time (see the mobile toggle below).
|
|
152
|
+
* - LIST region (`data-slot="listing-layout-list"`): `overflow-y-auto`, its
|
|
153
|
+
* own scroll container so browsing the list never requires scrolling the
|
|
154
|
+
* map out of view. Holds `<ListingList>` (an auto-fill card grid) and
|
|
155
|
+
* `<ListingPagination>`.
|
|
156
|
+
* - MAP region (`data-slot="listing-layout-map"`): fills the split's full
|
|
157
|
+
* height via the grid's default stretch alignment (no explicit height
|
|
158
|
+
* needed), plus `md:sticky md:top-0` so that if a consuming app ever lets
|
|
159
|
+
* `ListingLayout` sit inside a naturally document-scrolling page (rather
|
|
160
|
+
* than the fixed-height shell this component defaults to via
|
|
161
|
+
* `h-full min-h-0`), the map still pins in place while the list scrolls
|
|
162
|
+
* past -- inert (but harmless) in the default fixed-height composition,
|
|
163
|
+
* where there is nothing above the split to scroll past in the first
|
|
164
|
+
* place. Renders `<ListingMap>` with a default centered fallback message
|
|
165
|
+
* (`MAP_FALLBACK`) for when no `MapProvider` is configured, so the pane
|
|
166
|
+
* never looks broken/blank.
|
|
167
|
+
* - MOBILE LIST/MAP TOGGLE (`data-slot="listing-layout-mobile-toggle"`,
|
|
168
|
+
* `md:hidden`): a small two-button segmented control, local `useState`
|
|
169
|
+
* (`mobileView`), defaulting to `'list'`. Both the list and map regions
|
|
170
|
+
* stay mounted at all times (never remounted on toggle -- that would
|
|
171
|
+
* re-trigger `ListingList`'s/`ListingMap`'s own mount effects for no
|
|
172
|
+
* reason); only their visibility flips via `hidden md:block` /
|
|
173
|
+
* `block md:block`, i.e. the toggle only ever matters below `md` -- at
|
|
174
|
+
* `md` and up both regions are always visible side by side and the toggle
|
|
175
|
+
* control itself is hidden.
|
|
176
|
+
* - Fetches the first page itself on mount (`engine.applyFilters({})`) by
|
|
177
|
+
* default: none of the structure-only `~/react` compound components do
|
|
178
|
+
* this (they are deliberately side-effect-free), so as the
|
|
179
|
+
* batteries-included "full experience" entry point, `ListingLayout` is the
|
|
180
|
+
* natural, single owner of that one bootstrapping side effect. Pass
|
|
181
|
+
* `autoFetch={false}` to opt out and drive the first fetch yourself (e.g.
|
|
182
|
+
* search-before-results, or when `urlSync` hydrates filters from the URL).
|
|
183
|
+
*/
|
|
184
|
+
declare function ListingLayout({ search, className, toolbarEnd, autoFetch, mapCenter, mapZoom, }: IListingLayoutProps): react.JSX.Element;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* `ListingApp.map` accepts either a ready-made `MapProvider` (any
|
|
188
|
+
* implementation -- Google, a fake, a future provider) or, as a convenience,
|
|
189
|
+
* a bare Google Maps API key. See `useResolvedMap`'s doc comment for why the
|
|
190
|
+
* key shorthand is resolved via a dynamic `import()` rather than a static one.
|
|
191
|
+
*
|
|
192
|
+
* Both shapes optionally carry `center`/`zoom` -- the initial view, forwarded
|
|
193
|
+
* through `ListingLayout`'s `mapCenter`/`mapZoom` straight into
|
|
194
|
+
* `ListingMap`'s own `center`/`zoom` props. Omit them to get `ListingMap`'s
|
|
195
|
+
* turnkey auto-fit default instead (see that component's doc comment);
|
|
196
|
+
* supplying `center` opts out of auto-fit entirely.
|
|
197
|
+
*/
|
|
198
|
+
type ListingAppMapProp = {
|
|
199
|
+
provider: MapProvider;
|
|
200
|
+
center?: LatLng;
|
|
201
|
+
zoom?: number;
|
|
202
|
+
} | {
|
|
203
|
+
apiKey: string;
|
|
204
|
+
mapId?: string;
|
|
205
|
+
center?: LatLng;
|
|
206
|
+
zoom?: number;
|
|
207
|
+
};
|
|
208
|
+
interface ListingAppProps<TFilters> {
|
|
209
|
+
/**
|
|
210
|
+
* One or more marker-layer datasets; the FIRST entry is the primary
|
|
211
|
+
* dataset (drives the results list + pagination) -- same "insertion order"
|
|
212
|
+
* rule `composeListingProviders`/`withDataset` already follow. Entity-erased
|
|
213
|
+
* (`any`, not `TEntity`) for the same reason `DatasetRegistry<unknown,
|
|
214
|
+
* TFilters>` is: one array can legitimately hold heterogeneous layers (a
|
|
215
|
+
* properties dataset and a businesses dataset have different entity types)
|
|
216
|
+
* -- see `compose-listing-providers.ts`'s `withDataset` doc comment.
|
|
217
|
+
*/
|
|
218
|
+
datasets: DatasetDefinition<any, TFilters>[];
|
|
219
|
+
/** e.g. `withRentalFilters()`, or any `(reg) => { reg.add(...); }` callback -- forwarded verbatim to `withFilters`. */
|
|
220
|
+
filters?: (reg: FilterRegistry<TFilters>) => void;
|
|
221
|
+
/** A ready `MapProvider`, or `{ apiKey, mapId? }` to build a `googleProvider` internally. Omit for no map (the styled layout shows a "Map unavailable" fallback). */
|
|
222
|
+
map?: ListingAppMapProp;
|
|
223
|
+
/** Slot overrides, merged OVER the shadcn styled defaults -- see this file's doc comment for how the merge works. */
|
|
224
|
+
components?: Partial<IListingComponents>;
|
|
225
|
+
/** A `UrlSyncController<TFilters>` (e.g. `rentalUrlSync()`), or `true`. `true` is a documented no-op: the library is filters-shape-erased and cannot generically derive a `TFilters <-> QueryParams` mapping, which is exactly why domain presets (like `rentalUrlSync`) exist to build a real controller. */
|
|
226
|
+
urlSync?: UrlSyncController<TFilters> | boolean;
|
|
227
|
+
initialFilters?: TFilters;
|
|
228
|
+
config?: Partial<IListingConfigOptions>;
|
|
229
|
+
className?: string;
|
|
230
|
+
search?: IListingLayoutProps['search'];
|
|
231
|
+
autoFetch?: boolean;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Turnkey, batteries-included entry point: pass datasets + filters + a map +
|
|
235
|
+
* component overrides + URL sync, and `ListingApp` composes
|
|
236
|
+
* `composeListingProviders(...)`, `<ListingProvider>`, the styled `/shadcn`
|
|
237
|
+
* defaults, and `<ListingLayout>` for you. Equivalent to (and internally
|
|
238
|
+
* built from) the lower-level primitives every other `/shadcn` example wires
|
|
239
|
+
* by hand -- reach for those directly when you need something `ListingApp`
|
|
240
|
+
* doesn't expose (e.g. multiple `<ListingLayout>`s sharing one engine).
|
|
241
|
+
*
|
|
242
|
+
* `TEntity` still can't be inferred from these props (`datasets` narrows it,
|
|
243
|
+
* but a multi-dataset array widens back to the union/`unknown` in practice)
|
|
244
|
+
* -- annotate the call site when entity-level typing matters, exactly like
|
|
245
|
+
* `<ListingProvider<TEntity, TFilters>>` itself.
|
|
246
|
+
*
|
|
247
|
+
* COMPONENTS MERGE: `components` overrides are applied ON TOP of the shadcn
|
|
248
|
+
* styled defaults via ONE explicit `{ ...shadcnDefaultComponents, ...components }`
|
|
249
|
+
* object, passed to a single `<ListingComponentsProvider>` -- never as a
|
|
250
|
+
* nested `<ListingComponentsProvider>` inside `<ListingComponentsProviderWithDefaults>`.
|
|
251
|
+
* `ListingComponentsProvider` merges `provided ?? ITS OWN private, unstyled
|
|
252
|
+
* fallbacks` per slot (it does not read the parent context -- see
|
|
253
|
+
* `src/react/components-provider.tsx`), so nesting would silently discard
|
|
254
|
+
* every un-overridden shadcn default instead of keeping it.
|
|
255
|
+
*/
|
|
256
|
+
declare function ListingApp<TEntity, TFilters>(props: ListingAppProps<TFilters>): react.JSX.Element | null;
|
|
257
|
+
|
|
258
|
+
/** Merges class-name fragments, letting `tailwind-merge` resolve conflicting Tailwind utilities (last one wins). Mirrors `react-wizard-engine`'s `~/utils/cn`. */
|
|
259
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
260
|
+
|
|
261
|
+
export { DefaultCard, DefaultEmpty, DefaultFilterPanel, DefaultLoading, DefaultMarker, DefaultPopup, DefaultResultHeader, DefaultSearch, DefaultSidebar, DefaultToolbar, type IListingLayoutProps, ListingApp, type ListingAppMapProp, type ListingAppProps, ListingComponentsProviderWithDefaults, ListingLayout, cn, shadcnDefaultComponents };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{A as X,B as Y,h as U,i as H,j as _,k as O,l as W,m as z,n as B,p as v,q as $,s as Z,v as G,x as q,y as J,z as Q}from"../chunk-PFPHWJAA.js";import{a as s}from"../chunk-6PZHSHU3.js";import{a as K}from"../chunk-35KYEBAC.js";import{Fragment as se,jsx as m,jsxs as N}from"react/jsx-runtime";function oe(e){return typeof e!="number"?e:new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}function w({item:e,selected:i,onSelect:t}){let r=e??{},l=s("flex w-full flex-col overflow-hidden rounded-lg border bg-card text-left text-card-foreground shadow-sm","motion-safe:transition-shadow",t&&"hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",i&&"ring-2 ring-ring"),a=N(se,{children:[r.imageUrl&&m("img",{src:r.imageUrl,alt:r.title??"",className:"aspect-video w-full rounded-t-lg object-cover"}),N("div",{className:"flex flex-1 flex-col gap-1 p-3",children:[N("div",{className:"flex items-start justify-between gap-2",children:[r.title&&m("span",{className:"font-medium",children:r.title}),r.badge&&m("span",{className:"shrink-0 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground",children:r.badge})]}),r.subtitle&&m("span",{className:"text-sm text-muted-foreground",children:r.subtitle}),r.price!=null&&m("span",{className:"mt-1 font-semibold tabular-nums",children:oe(r.price)})]})]});return t?m("button",{type:"button",onClick:t,"aria-pressed":i??!1,className:l,children:a}):m("article",{className:l,children:a})}import{jsx as L,jsxs as j}from"react/jsx-runtime";function C(e){return j("div",{role:"status",className:"flex flex-col items-center justify-center gap-2 px-4 py-12 text-center text-muted-foreground",children:[j("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round",className:"h-8 w-8","aria-hidden":"true",children:[L("circle",{cx:"11",cy:"11",r:"7"}),L("path",{d:"m21 21-4.3-4.3"})]}),L("p",{className:"text-sm font-medium text-foreground",children:"No results"}),L("p",{className:"text-xs text-muted-foreground",children:"Try adjusting your filters or search terms."})]})}import{jsx as ae}from"react/jsx-runtime";function I({children:e}){return ae("div",{className:"flex flex-col gap-3",children:e})}import{jsx as x,jsxs as ne}from"react/jsx-runtime";function D(e){return x("div",{className:"flex flex-col gap-3 p-3",role:"status","aria-busy":"true","aria-label":"Loading results",children:Array.from({length:3}).map((i,t)=>ne("div",{className:"flex flex-col gap-2 motion-safe:animate-pulse",children:[x("div",{className:"aspect-video w-full rounded-md bg-muted"}),x("div",{className:"h-4 w-2/3 rounded-md bg-muted"}),x("div",{className:"h-3 w-1/3 rounded-md bg-muted"})]},t))})}import{jsx as le}from"react/jsx-runtime";function M({point:e}){let i=e.entity??{};return le("span",{className:"rounded-full border bg-background px-2 py-0.5 text-xs font-medium tabular-nums shadow-sm",children:i.price??""})}import{jsx as c,jsxs as k}from"react/jsx-runtime";function F({entity:e,onClose:i}){let t=e??{};return k("div",{className:"relative w-64 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md",role:"group","aria-label":"Location details",children:[c("button",{type:"button",onClick:i,"aria-label":"Close",className:"absolute right-2 top-2 rounded-md p-1 text-muted-foreground motion-safe:transition-colors hover:bg-foreground/[0.06] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",children:k("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",className:"h-4 w-4","aria-hidden":"true",children:[c("path",{d:"M18 6 6 18"}),c("path",{d:"m6 6 12 12"})]})}),k("div",{className:"pr-6",children:[t.title&&c("div",{className:"font-medium",children:t.title}),t.subtitle&&c("div",{className:"text-sm text-muted-foreground",children:t.subtitle}),t.price!=null&&c("div",{className:"mt-1 font-semibold tabular-nums",children:t.price})]})]})}import{jsx as pe}from"react/jsx-runtime";function S({count:e,total:i}){let t=i!=null&&i!==e?`${e} of ${i} results`:`${e} results`;return pe("div",{className:"text-sm tabular-nums text-muted-foreground",children:t})}import{jsx as de}from"react/jsx-runtime";function T({value:e,onChange:i,placeholder:t}){return de("input",{type:"search",value:e,placeholder:t,onChange:r=>i(r.target.value),"aria-label":t??"Search",className:s("h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground","focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring")})}import{jsx as me}from"react/jsx-runtime";function R({children:e}){return me("aside",{className:"flex w-full flex-col gap-4 p-4 md:border-r md:border-border",children:e})}import{jsx as ue}from"react/jsx-runtime";function A({children:e}){return ue("div",{className:"flex items-center gap-2 border-b p-2",children:e})}var g={Card:w,Marker:M,Popup:F,Sidebar:R,FilterPanel:I,Search:T,Empty:C,Loading:D,ResultHeader:S,Toolbar:A};import{jsx as ce}from"react/jsx-runtime";function fe({children:e}){return ce(v,{...g,children:e})}import{useEffect as Le,useState as xe}from"react";import{useEffect as ge,useState as be}from"react";import{jsx as o,jsxs as b}from"react/jsx-runtime";var ve=o("p",{className:"px-4 text-center text-sm text-muted-foreground",children:"Map unavailable - provide a Google Maps API key"});function E({search:e,className:i,toolbarEnd:t,autoFetch:r=!0,mapCenter:l,mapZoom:a}){let p=Z(),{Search:u}=$(),[f,y]=be("list");ge(()=>{r!==!1&&p.applyFilters({})},[p,r]);let h=f==="map"?"hidden md:block":"block",P=f==="list"?"hidden md:block":"block";return b("div",{className:s("flex h-full min-h-0 w-full flex-col bg-background text-foreground",i),"data-slot":"listing-layout",children:[b("div",{className:"sticky top-0 z-10 flex flex-wrap items-end gap-3 border-b border-border bg-background p-3","data-slot":"listing-layout-filter-bar",children:[e&&o("div",{className:"w-full min-w-0 sm:w-auto sm:max-w-xs sm:flex-1",children:o(u,{value:e.value,onChange:e.onChange,placeholder:e.placeholder})}),o(G,{className:"flex flex-wrap items-end gap-3",groupClassName:"min-w-0"}),b("div",{className:"ml-auto flex items-center gap-3",children:[o(X,{}),t]})]}),o("div",{className:"flex items-center justify-center gap-1 border-b border-border bg-background p-2 md:hidden","data-slot":"listing-layout-mobile-toggle",children:o("div",{role:"group","aria-label":"View",className:"inline-flex rounded-md border border-border p-0.5",children:[{view:"list",label:"List"},{view:"map",label:"Map"}].map(({view:d,label:n})=>o("button",{type:"button","aria-pressed":f===d,onClick:()=>y(d),className:s("rounded-[5px] px-3 py-1 text-sm font-medium motion-safe:transition-colors",f===d?"bg-primary text-primary-foreground":"text-muted-foreground hover:bg-foreground/[0.06] hover:text-foreground"),children:n},d))})}),b("div",{className:"grid min-h-0 flex-1 md:grid-cols-[minmax(340px,42%)_1fr]","data-slot":"listing-layout-split",children:[b("div",{className:s("min-h-0 overflow-y-auto p-3",h),"data-slot":"listing-layout-list",children:[o(q,{className:"grid content-start gap-3 [grid-template-columns:repeat(auto-fill,minmax(200px,1fr))]"}),o("div",{className:"mt-4 flex justify-center",children:o(Q,{})})]}),o("div",{className:s("min-h-0 md:sticky md:top-0",P),"data-slot":"listing-layout-map",children:o(J,{center:l,zoom:a,fallback:ve})})]})]})}import{jsx as V}from"react/jsx-runtime";function ee(e){return"provider"in e}function ye(e){let i=e!=null&&!ee(e),t=i?e.apiKey:void 0,r=i?e.mapId:void 0,[l,a]=xe(()=>!e||ee(e)?{ready:!0,provider:e?.provider}:{ready:!1});return Le(()=>{if(!t)return;let p=!1;return import("../maps/google/index.js").then(({googleProvider:u})=>{p||a({ready:!0,provider:u({apiKey:t,mapId:r})})}),()=>{p=!0}},[t,r]),l}function he(e){let{datasets:i,filters:t,map:r,components:l,urlSync:a,initialFilters:p,config:u,className:f,search:y,autoFetch:h}=e,{ready:P,provider:d}=ye(r);if(!P)return null;let n=[];for(let ie of i)n.push(O(ie));t&&n.push(W(t)),d&&n.push(_(d)),a instanceof K&&n.push(z(a)),p&&n.push(B(p)),u&&n.push(H(u));let te=U(...n),re={...g,...l};return V(Y,{...te,children:V(v,{...re,children:V(E,{className:f,search:y,autoFetch:h,mapCenter:r?.center,mapZoom:r?.zoom})})})}export{w as DefaultCard,C as DefaultEmpty,I as DefaultFilterPanel,D as DefaultLoading,M as DefaultMarker,F as DefaultPopup,S as DefaultResultHeader,T as DefaultSearch,R as DefaultSidebar,A as DefaultToolbar,he as ListingApp,fe as ListingComponentsProviderWithDefaults,E as ListingLayout,s as cn,g as shadcnDefaultComponents};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
|
+
var _chunkN3OZ2HK2cjs = require('../chunk-N3OZ2HK2.cjs');var _chunk5O36WI7Hcjs = require('../chunk-5O36WI7H.cjs');var _jsxruntime = require('react/jsx-runtime');function L({children:o}){return _jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.p,{..._chunkN3OZ2HK2cjs.l,children:o})}exports.BottomNav = _chunkN3OZ2HK2cjs.m; exports.BottomSheet = _chunkN3OZ2HK2cjs.n; exports.ListingApp = _chunkN3OZ2HK2cjs.p; exports.StyledCard = _chunkN3OZ2HK2cjs.b; exports.StyledComponentsProviderWithDefaults = L; exports.StyledEmpty = _chunkN3OZ2HK2cjs.c; exports.StyledFilterPanel = _chunkN3OZ2HK2cjs.d; exports.StyledListingLayout = _chunkN3OZ2HK2cjs.o; exports.StyledLoading = _chunkN3OZ2HK2cjs.e; exports.StyledMarker = _chunkN3OZ2HK2cjs.f; exports.StyledPopup = _chunkN3OZ2HK2cjs.g; exports.StyledResultHeader = _chunkN3OZ2HK2cjs.h; exports.StyledSearch = _chunkN3OZ2HK2cjs.i; exports.StyledSidebar = _chunkN3OZ2HK2cjs.j; exports.StyledToolbar = _chunkN3OZ2HK2cjs.k; exports.styledDefaultComponents = _chunkN3OZ2HK2cjs.l;
|