@bearmenu/ui 0.6.0 → 0.7.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/dist/chunk-A5ONAJCE.js +102 -0
- package/dist/chunk-AV777R4G.js +43 -0
- package/dist/{chunk-WKMLZDUT.js → chunk-FRPPIXMY.js} +1 -1
- package/dist/{chunk-PSR7N36A.js → chunk-HCM6LKM5.js} +1 -1
- package/dist/{chunk-YJ6OBKZJ.js → chunk-TL4YU2O5.js} +10 -3
- package/dist/chunk-UX6SWAKH.js +320 -0
- package/dist/{chunk-7PPCH42H.js → chunk-VQNDZVI7.js} +1 -1
- package/dist/chunk-WSRWFA6K.js +26 -0
- package/dist/chunk-YLNYXPIH.js +61 -0
- package/dist/components/accordion.js +1 -1
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +3 -2
- package/dist/components/badge.js +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/card.d.ts +2 -2
- package/dist/components/card.js +127 -4
- package/dist/components/combobox.js +2 -2
- package/dist/components/command.d.ts +15 -15
- package/dist/components/command.js +2 -2
- package/dist/components/currency-input.js +1 -1
- package/dist/components/date-input.d.ts +1 -1
- package/dist/components/date-input.js +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/filter-category-row.d.ts +13 -0
- package/dist/components/filter-category-row.js +44 -0
- package/dist/components/input-group.d.ts +2 -0
- package/dist/components/input-group.js +10 -5
- package/dist/components/input.d.ts +1 -0
- package/dist/components/input.js +1 -1
- package/dist/components/item.d.ts +2 -2
- package/dist/components/multi-select-combobox.js +3 -3
- package/dist/components/phone-frame.js +1 -1
- package/dist/components/place-about-tab.d.ts +2 -2
- package/dist/components/place-about-tab.js +4 -3
- package/dist/components/place-details-mobile.js +5 -3
- package/dist/components/place-schedule-week.d.ts +19 -0
- package/dist/components/place-schedule-week.js +4 -0
- package/dist/components/place-types.d.ts +18 -9
- package/dist/components/scroll-fade.d.ts +15 -0
- package/dist/components/scroll-fade.js +121 -0
- package/dist/components/searchable-select.js +2 -2
- package/dist/components/separator.js +3 -26
- package/dist/components/sheet.js +1 -1
- package/dist/components/sliding-panels.d.ts +1 -0
- package/dist/components/sliding-panels.js +22 -6
- package/dist/components/sort-option-list.d.ts +16 -0
- package/dist/components/sort-option-list.js +46 -0
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tabs.js +98 -17
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/tag.js +3 -61
- package/dist/components/text.d.ts +3 -3
- package/dist/components/toast.js +1 -1
- package/dist/components/toaster.js +1 -1
- package/package.json +23 -112
- package/src/components/accordion.tsx +1 -1
- package/src/components/badge.tsx +9 -2
- package/src/components/card.tsx +22 -18
- package/src/components/dialog.tsx +1 -1
- package/src/components/filter-category-row.tsx +52 -0
- package/src/components/input-group.tsx +9 -4
- package/src/components/input.tsx +8 -3
- package/src/components/phone-frame.tsx +1 -1
- package/src/components/place-about-tab.stories.tsx +24 -13
- package/src/components/place-about-tab.test.tsx +92 -38
- package/src/components/place-about-tab.tsx +310 -344
- package/src/components/place-schedule-week.tsx +128 -0
- package/src/components/place-types.ts +27 -8
- package/src/components/scroll-fade.stories.tsx +98 -0
- package/src/components/scroll-fade.tsx +132 -0
- package/src/components/sheet.tsx +1 -1
- package/src/components/sliding-panels.tsx +33 -6
- package/src/components/sort-option-list.tsx +57 -0
- package/src/components/tabs.tsx +76 -11
- package/src/components/toast.tsx +1 -1
- package/src/globals.css +77 -19
- package/src/lib/motion.ts +137 -0
- package/dist/chunk-BAZFVSSG.js +0 -279
- package/dist/chunk-RIDXFY6M.js +0 -38
- package/dist/chunk-XK2ZDQVE.js +0 -122
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
Phone,
|
|
6
|
-
Globe,
|
|
7
|
-
Clock,
|
|
8
|
-
UtensilsCrossed,
|
|
9
|
-
CreditCard,
|
|
10
|
-
Shield,
|
|
11
|
-
Car,
|
|
12
|
-
Building2,
|
|
13
|
-
Info,
|
|
14
|
-
} from "lucide-react";
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { Phone, Globe, Check } from "lucide-react";
|
|
15
5
|
import { cn } from "../lib/utils";
|
|
16
6
|
|
|
17
7
|
// Lucide v1+ removed brand icons; inline SVGs preserve the original visual.
|
|
@@ -48,9 +38,11 @@ function Facebook(props: React.SVGProps<SVGSVGElement>) {
|
|
|
48
38
|
</svg>
|
|
49
39
|
);
|
|
50
40
|
}
|
|
51
|
-
|
|
52
|
-
import {
|
|
41
|
+
|
|
42
|
+
import { Tag } from "./tag";
|
|
53
43
|
import { Text } from "./text";
|
|
44
|
+
import { Separator } from "./separator";
|
|
45
|
+
import { PlaceScheduleWeek } from "./place-schedule-week";
|
|
54
46
|
import type {
|
|
55
47
|
PlaceAboutTabLabels,
|
|
56
48
|
PlaceLinkKind,
|
|
@@ -60,23 +52,81 @@ import type {
|
|
|
60
52
|
export interface PlaceAboutTabProps {
|
|
61
53
|
place: PlacePreview;
|
|
62
54
|
labels: PlaceAboutTabLabels;
|
|
63
|
-
/** Slot for an embedded map
|
|
55
|
+
/** Slot for an embedded map. Rendered first, full-bleed — the caller controls the breakout / gradient treatment. */
|
|
64
56
|
mapSlot?: React.ReactNode;
|
|
65
57
|
/** Slot for an embedded busyness chart. Rendered only when both this prop and `place.busyness` are present. */
|
|
66
58
|
busynessSlot?: React.ReactNode;
|
|
67
|
-
/** Fires when the user clicks a contact link (phone/website/social).
|
|
59
|
+
/** Fires when the user clicks a contact link (phone/website/social). */
|
|
68
60
|
onLinkClick?: (kind: PlaceLinkKind, url: string) => void;
|
|
69
61
|
className?: string;
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
function formatHour(h: number): string {
|
|
73
|
-
return `${h.toString().padStart(2, "0")}:00`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
64
|
function humanizeEnum(value: string): string {
|
|
77
65
|
return value.replace(/_/g, " ");
|
|
78
66
|
}
|
|
79
67
|
|
|
68
|
+
function SectionHeading({ children }: { children: React.ReactNode }) {
|
|
69
|
+
return (
|
|
70
|
+
<Text as="h3" variant="body-sm" weight="semibold" className="text-foreground">
|
|
71
|
+
{children}
|
|
72
|
+
</Text>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Divider() {
|
|
77
|
+
return <Separator className="mx-4 w-auto" />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ChecklistGrid({ items }: { items: string[] }) {
|
|
81
|
+
return (
|
|
82
|
+
<div className="grid grid-cols-2 gap-x-3 gap-y-1.5">
|
|
83
|
+
{items.map((item, i) => (
|
|
84
|
+
<div key={i} className="flex items-start gap-2">
|
|
85
|
+
<Check
|
|
86
|
+
className="w-3.5 h-3.5 mt-0.5 shrink-0 text-muted-foreground"
|
|
87
|
+
aria-hidden
|
|
88
|
+
/>
|
|
89
|
+
<Text as="span" variant="body-sm" color="muted">
|
|
90
|
+
{item}
|
|
91
|
+
</Text>
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function ChipRow({ items }: { items: string[] }) {
|
|
99
|
+
return (
|
|
100
|
+
<div className="flex flex-wrap gap-2">
|
|
101
|
+
{items.map((item, i) => (
|
|
102
|
+
<Tag key={i} variant="default">
|
|
103
|
+
{item}
|
|
104
|
+
</Tag>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function StatusTag({
|
|
111
|
+
status,
|
|
112
|
+
label,
|
|
113
|
+
}: {
|
|
114
|
+
status: "operational" | "closed_temporarily" | "closed_permanently";
|
|
115
|
+
label: string;
|
|
116
|
+
}) {
|
|
117
|
+
const variant: "success" | "warning" | "destructive" =
|
|
118
|
+
status === "operational"
|
|
119
|
+
? "success"
|
|
120
|
+
: status === "closed_temporarily"
|
|
121
|
+
? "warning"
|
|
122
|
+
: "destructive";
|
|
123
|
+
return (
|
|
124
|
+
<Tag variant={variant} className="capitalize">
|
|
125
|
+
{label}
|
|
126
|
+
</Tag>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
80
130
|
export function PlaceAboutTab({
|
|
81
131
|
place,
|
|
82
132
|
labels,
|
|
@@ -85,12 +135,6 @@ export function PlaceAboutTab({
|
|
|
85
135
|
onLinkClick,
|
|
86
136
|
className,
|
|
87
137
|
}: PlaceAboutTabProps) {
|
|
88
|
-
const todayHours =
|
|
89
|
-
place.hours && place.hours.length > 0
|
|
90
|
-
? place.hours.map((slot) => `${formatHour(slot.opens_at)} - ${formatHour(slot.closes_at)}`)
|
|
91
|
-
: null;
|
|
92
|
-
|
|
93
|
-
const formattedAddress = place.location?.address;
|
|
94
138
|
const handleLinkClick = (kind: PlaceLinkKind, url?: string) => {
|
|
95
139
|
if (onLinkClick && url) onLinkClick(kind, url);
|
|
96
140
|
};
|
|
@@ -104,371 +148,293 @@ export function PlaceAboutTab({
|
|
|
104
148
|
const getParkingLabel = (key: string): string =>
|
|
105
149
|
labels.parkingLabels[key] || humanizeEnum(key);
|
|
106
150
|
const getTypeLabel = (key: string): string => labels.typeLabels[key] || humanizeEnum(key);
|
|
107
|
-
const getStatusLabel = (key: string): string =>
|
|
151
|
+
const getStatusLabel = (key: string): string =>
|
|
152
|
+
labels.statusLabels[key] || humanizeEnum(key);
|
|
108
153
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
154
|
+
const hasDescription = Boolean(
|
|
155
|
+
place.generative_summary || place.review_summary || place.description
|
|
156
|
+
);
|
|
157
|
+
const hasDescriptionChips = Boolean(place.type || place.business_status);
|
|
158
|
+
const hasContact = Boolean(
|
|
159
|
+
place.location?.address ||
|
|
160
|
+
place.phone ||
|
|
161
|
+
place.website_url ||
|
|
162
|
+
place.socials?.instagram_url ||
|
|
163
|
+
place.socials?.facebook_url
|
|
164
|
+
);
|
|
165
|
+
const hasSchedule = Boolean(place.schedule);
|
|
166
|
+
const hasBusyness = Boolean(busynessSlot && place.busyness && place.busyness.length > 0);
|
|
167
|
+
const hasCuisines = (place.cuisines?.length ?? 0) > 0;
|
|
168
|
+
const hasAmenities = (place.amenities?.length ?? 0) > 0;
|
|
169
|
+
const hasPayment = (place.payment_options?.length ?? 0) > 0;
|
|
170
|
+
const hasAccessibility = (place.accessibility_features?.length ?? 0) > 0;
|
|
171
|
+
const hasParking = (place.parking_options?.length ?? 0) > 0;
|
|
172
|
+
const hasLandmarks = (place.landmarks?.length ?? 0) > 0;
|
|
112
173
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
174
|
+
const neighborhood =
|
|
175
|
+
place.areas && place.areas.length > 0
|
|
176
|
+
? place.areas.map((a) => a.name).join(" · ")
|
|
177
|
+
: null;
|
|
178
|
+
|
|
179
|
+
const sections: Array<{ key: string; node: React.ReactNode }> = [];
|
|
180
|
+
|
|
181
|
+
if (hasDescription || hasDescriptionChips) {
|
|
182
|
+
sections.push({
|
|
183
|
+
key: "description",
|
|
184
|
+
node: (
|
|
185
|
+
<section className="px-4 space-y-2">
|
|
186
|
+
<SectionHeading>{labels.description}</SectionHeading>
|
|
187
|
+
{hasDescription && (
|
|
188
|
+
<div className="space-y-2">
|
|
189
|
+
{place.generative_summary && (
|
|
190
|
+
<Text as="p" variant="body-sm" leading="snug" color="muted">
|
|
128
191
|
{place.generative_summary}
|
|
129
192
|
</Text>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<Text as="p" variant="body-sm" color="muted" className="mb-2">
|
|
135
|
-
{labels.description}
|
|
193
|
+
)}
|
|
194
|
+
{place.review_summary && (
|
|
195
|
+
<Text as="p" variant="body-sm" leading="snug" color="muted">
|
|
196
|
+
{place.review_summary}
|
|
136
197
|
</Text>
|
|
137
|
-
|
|
198
|
+
)}
|
|
199
|
+
{place.description && (
|
|
200
|
+
<Text as="p" variant="body-sm" leading="snug" color="muted">
|
|
138
201
|
{place.description}
|
|
139
202
|
</Text>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
{hasDescriptionChips && (
|
|
207
|
+
<div className="flex flex-wrap gap-2">
|
|
208
|
+
{place.type && (
|
|
209
|
+
<Tag variant="default" className="capitalize">
|
|
210
|
+
{getTypeLabel(place.type)}
|
|
211
|
+
</Tag>
|
|
212
|
+
)}
|
|
213
|
+
{place.business_status && (
|
|
214
|
+
<StatusTag
|
|
215
|
+
status={place.business_status}
|
|
216
|
+
label={getStatusLabel(place.business_status)}
|
|
217
|
+
/>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</section>
|
|
222
|
+
),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (hasContact) {
|
|
227
|
+
sections.push({
|
|
228
|
+
key: "contact",
|
|
229
|
+
node: (
|
|
230
|
+
<section className="px-4 space-y-2">
|
|
231
|
+
<SectionHeading>{labels.contactAndLocation}</SectionHeading>
|
|
232
|
+
<div className="space-y-2.5">
|
|
233
|
+
{place.location?.address && (
|
|
143
234
|
<div>
|
|
144
|
-
<Text as="p" variant="body-sm" color="muted"
|
|
145
|
-
{
|
|
146
|
-
</Text>
|
|
147
|
-
<Text as="p" color="default">
|
|
148
|
-
{place.review_summary}
|
|
235
|
+
<Text as="p" variant="body-sm" color="muted">
|
|
236
|
+
{place.location.address}
|
|
149
237
|
</Text>
|
|
238
|
+
{neighborhood && (
|
|
239
|
+
<Text as="p" variant="body-sm" color="muted" className="mt-0.5">
|
|
240
|
+
{neighborhood}
|
|
241
|
+
</Text>
|
|
242
|
+
)}
|
|
150
243
|
</div>
|
|
151
244
|
)}
|
|
152
|
-
|
|
153
|
-
</Card>
|
|
154
|
-
)}
|
|
155
|
-
|
|
156
|
-
<Card>
|
|
157
|
-
<CardHeader>
|
|
158
|
-
<CardTitle className="flex items-center gap-2">
|
|
159
|
-
<MapPin className="w-5 h-5" aria-hidden="true" />
|
|
160
|
-
{labels.contactAndLocation}
|
|
161
|
-
</CardTitle>
|
|
162
|
-
</CardHeader>
|
|
163
|
-
<CardContent className="space-y-4">
|
|
164
|
-
{formattedAddress && (
|
|
165
|
-
<div>
|
|
166
|
-
<Text as="p" variant="body-sm" color="muted" className="mb-1">
|
|
167
|
-
{labels.address}
|
|
168
|
-
</Text>
|
|
169
|
-
<Text as="p" color="default">
|
|
170
|
-
{formattedAddress}
|
|
171
|
-
</Text>
|
|
172
|
-
</div>
|
|
173
|
-
)}
|
|
174
|
-
{place.phone && (
|
|
175
|
-
<div className="flex items-center gap-2">
|
|
176
|
-
<Phone className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
245
|
+
{place.phone && (
|
|
177
246
|
<a
|
|
178
247
|
href={`tel:${place.phone}`}
|
|
179
248
|
onClick={() => handleLinkClick("phone", place.phone)}
|
|
180
|
-
className="text-foreground hover:text-primary transition-colors"
|
|
249
|
+
className="flex items-center gap-2 text-foreground hover:text-primary transition-colors"
|
|
181
250
|
>
|
|
182
|
-
|
|
251
|
+
<Phone className="w-4 h-4 text-muted-foreground shrink-0" aria-hidden />
|
|
252
|
+
<Text as="span" variant="body-sm">
|
|
253
|
+
{place.phone}
|
|
254
|
+
</Text>
|
|
183
255
|
</a>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{place.website_url && (
|
|
187
|
-
<div className="flex items-center gap-2">
|
|
188
|
-
<Globe className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
256
|
+
)}
|
|
257
|
+
{place.website_url && (
|
|
189
258
|
<a
|
|
190
259
|
href={place.website_url}
|
|
191
260
|
target="_blank"
|
|
192
261
|
rel="noopener noreferrer"
|
|
193
262
|
onClick={() => handleLinkClick("website", place.website_url)}
|
|
194
|
-
className="text-foreground hover:text-primary transition-colors break-all"
|
|
263
|
+
className="flex items-center gap-2 text-foreground hover:text-primary transition-colors break-all"
|
|
195
264
|
>
|
|
196
|
-
|
|
265
|
+
<Globe className="w-4 h-4 text-muted-foreground shrink-0" aria-hidden />
|
|
266
|
+
<Text as="span" variant="body-sm" className="break-all">
|
|
267
|
+
{place.website_url}
|
|
268
|
+
</Text>
|
|
197
269
|
</a>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{place.socials?.instagram_url && (
|
|
201
|
-
<div className="flex items-center gap-2">
|
|
202
|
-
<Instagram className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
270
|
+
)}
|
|
271
|
+
{place.socials?.instagram_url && (
|
|
203
272
|
<a
|
|
204
273
|
href={place.socials.instagram_url}
|
|
205
274
|
target="_blank"
|
|
206
275
|
rel="noopener noreferrer"
|
|
207
276
|
onClick={() => handleLinkClick("instagram", place.socials?.instagram_url)}
|
|
208
|
-
className="text-foreground hover:text-primary transition-colors"
|
|
277
|
+
className="flex items-center gap-2 text-foreground hover:text-primary transition-colors"
|
|
209
278
|
>
|
|
210
|
-
|
|
279
|
+
<Instagram className="w-4 h-4 text-muted-foreground shrink-0" aria-hidden />
|
|
280
|
+
<Text as="span" variant="body-sm">
|
|
281
|
+
{labels.instagram}
|
|
282
|
+
</Text>
|
|
211
283
|
</a>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
{place.socials?.facebook_url && (
|
|
215
|
-
<div className="flex items-center gap-2">
|
|
216
|
-
<Facebook className="w-4 h-4 text-muted-foreground" aria-hidden="true" />
|
|
284
|
+
)}
|
|
285
|
+
{place.socials?.facebook_url && (
|
|
217
286
|
<a
|
|
218
287
|
href={place.socials.facebook_url}
|
|
219
288
|
target="_blank"
|
|
220
289
|
rel="noopener noreferrer"
|
|
221
290
|
onClick={() => handleLinkClick("facebook", place.socials?.facebook_url)}
|
|
222
|
-
className="text-foreground hover:text-primary transition-colors"
|
|
291
|
+
className="flex items-center gap-2 text-foreground hover:text-primary transition-colors"
|
|
223
292
|
>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)}
|
|
228
|
-
</CardContent>
|
|
229
|
-
</Card>
|
|
230
|
-
|
|
231
|
-
{todayHours && todayHours.length > 0 && (
|
|
232
|
-
<Card>
|
|
233
|
-
<CardHeader>
|
|
234
|
-
<CardTitle className="flex items-center gap-2">
|
|
235
|
-
<Clock className="w-5 h-5" aria-hidden="true" />
|
|
236
|
-
{labels.todayHours}
|
|
237
|
-
</CardTitle>
|
|
238
|
-
</CardHeader>
|
|
239
|
-
<CardContent>
|
|
240
|
-
<div className="space-y-3">
|
|
241
|
-
<div className="flex items-center gap-2">
|
|
242
|
-
<span
|
|
243
|
-
className={cn(
|
|
244
|
-
"w-2 h-2 rounded-full",
|
|
245
|
-
place.open_now ? "bg-green-500" : "bg-muted-foreground"
|
|
246
|
-
)}
|
|
247
|
-
/>
|
|
248
|
-
<Text
|
|
249
|
-
as="span"
|
|
250
|
-
weight="medium"
|
|
251
|
-
className={place.open_now ? "text-green-600 dark:text-green-400" : ""}
|
|
252
|
-
>
|
|
253
|
-
{place.open_now ? labels.statusOpen : labels.statusClosed}
|
|
293
|
+
<Facebook className="w-4 h-4 text-muted-foreground shrink-0" aria-hidden />
|
|
294
|
+
<Text as="span" variant="body-sm">
|
|
295
|
+
{labels.facebook}
|
|
254
296
|
</Text>
|
|
255
|
-
</
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
))}
|
|
263
|
-
</div>
|
|
264
|
-
</CardContent>
|
|
265
|
-
</Card>
|
|
266
|
-
)}
|
|
267
|
-
|
|
268
|
-
{busynessSlot && place.busyness && place.busyness.length > 0 && (
|
|
269
|
-
<Card>
|
|
270
|
-
<CardHeader>
|
|
271
|
-
<CardTitle className="flex items-center gap-2">
|
|
272
|
-
<Clock className="w-5 h-5" aria-hidden="true" />
|
|
273
|
-
{labels.typicalBusyness}
|
|
274
|
-
</CardTitle>
|
|
275
|
-
</CardHeader>
|
|
276
|
-
<CardContent>{busynessSlot}</CardContent>
|
|
277
|
-
</Card>
|
|
278
|
-
)}
|
|
297
|
+
</a>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
</section>
|
|
301
|
+
),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
279
304
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
if (hasSchedule) {
|
|
306
|
+
sections.push({
|
|
307
|
+
key: "hours",
|
|
308
|
+
node: (
|
|
309
|
+
<section className="px-4 space-y-2">
|
|
310
|
+
<SectionHeading>{labels.todayHours}</SectionHeading>
|
|
311
|
+
<PlaceScheduleWeek
|
|
312
|
+
schedule={place.schedule}
|
|
313
|
+
labels={{
|
|
314
|
+
days: labels.scheduleDays,
|
|
315
|
+
closed: labels.statusClosed,
|
|
316
|
+
noHoursAvailable: labels.noHoursAvailable,
|
|
317
|
+
}}
|
|
318
|
+
autoScrollToToday={false}
|
|
319
|
+
/>
|
|
320
|
+
</section>
|
|
321
|
+
),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
299
324
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
</Text>
|
|
312
|
-
<Text as="span" color="default">
|
|
313
|
-
{getAmenityLabel(amenity)}
|
|
314
|
-
</Text>
|
|
315
|
-
</div>
|
|
316
|
-
))}
|
|
317
|
-
</div>
|
|
318
|
-
</CardContent>
|
|
319
|
-
</Card>
|
|
320
|
-
)}
|
|
325
|
+
if (hasBusyness) {
|
|
326
|
+
sections.push({
|
|
327
|
+
key: "busyness",
|
|
328
|
+
node: (
|
|
329
|
+
<section className="px-4 space-y-2">
|
|
330
|
+
<SectionHeading>{labels.typicalBusyness}</SectionHeading>
|
|
331
|
+
{busynessSlot}
|
|
332
|
+
</section>
|
|
333
|
+
),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
321
336
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
</Badge>
|
|
336
|
-
))}
|
|
337
|
-
</div>
|
|
338
|
-
</CardContent>
|
|
339
|
-
</Card>
|
|
340
|
-
)}
|
|
337
|
+
if (hasCuisines) {
|
|
338
|
+
sections.push({
|
|
339
|
+
key: "cuisines",
|
|
340
|
+
node: (
|
|
341
|
+
<section className="px-4 space-y-2">
|
|
342
|
+
<SectionHeading>{labels.cuisines}</SectionHeading>
|
|
343
|
+
<ChipRow
|
|
344
|
+
items={place.cuisines!.map((c) => c.charAt(0).toUpperCase() + c.slice(1))}
|
|
345
|
+
/>
|
|
346
|
+
</section>
|
|
347
|
+
),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
341
350
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
<div key={index} className="flex items-center gap-2 text-sm">
|
|
354
|
-
<Text as="span" color="muted">
|
|
355
|
-
•
|
|
356
|
-
</Text>
|
|
357
|
-
<Text as="span" color="default">
|
|
358
|
-
{getAccessibilityLabel(feature)}
|
|
359
|
-
</Text>
|
|
360
|
-
</div>
|
|
361
|
-
))}
|
|
362
|
-
</div>
|
|
363
|
-
</CardContent>
|
|
364
|
-
</Card>
|
|
365
|
-
)}
|
|
351
|
+
if (hasAmenities) {
|
|
352
|
+
sections.push({
|
|
353
|
+
key: "amenities",
|
|
354
|
+
node: (
|
|
355
|
+
<section className="px-4 space-y-2">
|
|
356
|
+
<SectionHeading>{labels.amenities}</SectionHeading>
|
|
357
|
+
<ChecklistGrid items={place.amenities!.map(getAmenityLabel)} />
|
|
358
|
+
</section>
|
|
359
|
+
),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
366
362
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
<Badge key={index} variant="outline">
|
|
379
|
-
{getParkingLabel(parking)}
|
|
380
|
-
</Badge>
|
|
381
|
-
))}
|
|
382
|
-
</div>
|
|
383
|
-
</CardContent>
|
|
384
|
-
</Card>
|
|
385
|
-
)}
|
|
363
|
+
if (hasPayment) {
|
|
364
|
+
sections.push({
|
|
365
|
+
key: "payment",
|
|
366
|
+
node: (
|
|
367
|
+
<section className="px-4 space-y-2">
|
|
368
|
+
<SectionHeading>{labels.paymentOptions}</SectionHeading>
|
|
369
|
+
<ChipRow items={place.payment_options!.map(getPaymentLabel)} />
|
|
370
|
+
</section>
|
|
371
|
+
),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
386
374
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
<div key={index} className="flex justify-between items-center">
|
|
399
|
-
<Text as="span" color="default">
|
|
400
|
-
{landmark.name}
|
|
401
|
-
</Text>
|
|
402
|
-
{landmark.distance_meters !== undefined &&
|
|
403
|
-
landmark.distance_meters !== null && (
|
|
404
|
-
<Text as="span" variant="body-sm" color="muted">
|
|
405
|
-
{(landmark.distance_meters / 1000).toFixed(1)} km
|
|
406
|
-
</Text>
|
|
407
|
-
)}
|
|
408
|
-
</div>
|
|
409
|
-
))}
|
|
410
|
-
</div>
|
|
411
|
-
</CardContent>
|
|
412
|
-
</Card>
|
|
413
|
-
)}
|
|
375
|
+
if (hasAccessibility) {
|
|
376
|
+
sections.push({
|
|
377
|
+
key: "accessibility",
|
|
378
|
+
node: (
|
|
379
|
+
<section className="px-4 space-y-2">
|
|
380
|
+
<SectionHeading>{labels.accessibility}</SectionHeading>
|
|
381
|
+
<ChecklistGrid items={place.accessibility_features!.map(getAccessibilityLabel)} />
|
|
382
|
+
</section>
|
|
383
|
+
),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
414
386
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
<Badge key={index} variant="secondary">
|
|
427
|
-
{area.name}
|
|
428
|
-
</Badge>
|
|
429
|
-
))}
|
|
430
|
-
</div>
|
|
431
|
-
</CardContent>
|
|
432
|
-
</Card>
|
|
433
|
-
)}
|
|
387
|
+
if (hasParking) {
|
|
388
|
+
sections.push({
|
|
389
|
+
key: "parking",
|
|
390
|
+
node: (
|
|
391
|
+
<section className="px-4 space-y-2">
|
|
392
|
+
<SectionHeading>{labels.parking}</SectionHeading>
|
|
393
|
+
<ChipRow items={place.parking_options!.map(getParkingLabel)} />
|
|
394
|
+
</section>
|
|
395
|
+
),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
434
398
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
399
|
+
if (hasLandmarks) {
|
|
400
|
+
sections.push({
|
|
401
|
+
key: "landmarks",
|
|
402
|
+
node: (
|
|
403
|
+
<section className="px-4 space-y-2">
|
|
404
|
+
<SectionHeading>{labels.nearbyLandmarks}</SectionHeading>
|
|
405
|
+
<div className="space-y-2">
|
|
406
|
+
{place.landmarks!.map((landmark, i) => (
|
|
407
|
+
<div key={i} className="flex justify-between items-baseline gap-3">
|
|
408
|
+
<Text as="span" variant="body-sm">
|
|
409
|
+
{landmark.name}
|
|
445
410
|
</Text>
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
<Badge
|
|
457
|
-
variant={
|
|
458
|
-
place.business_status === "operational"
|
|
459
|
-
? "default"
|
|
460
|
-
: place.business_status === "closed_temporarily"
|
|
461
|
-
? "secondary"
|
|
462
|
-
: "destructive"
|
|
463
|
-
}
|
|
464
|
-
>
|
|
465
|
-
{getStatusLabel(place.business_status)}
|
|
466
|
-
</Badge>
|
|
411
|
+
{landmark.distance_meters != null && (
|
|
412
|
+
<Text
|
|
413
|
+
as="span"
|
|
414
|
+
variant="body-sm"
|
|
415
|
+
color="muted"
|
|
416
|
+
className="tabular-nums shrink-0"
|
|
417
|
+
>
|
|
418
|
+
{(landmark.distance_meters / 1000).toFixed(1)} km
|
|
419
|
+
</Text>
|
|
420
|
+
)}
|
|
467
421
|
</div>
|
|
468
|
-
)}
|
|
469
|
-
</
|
|
470
|
-
</
|
|
471
|
-
)
|
|
422
|
+
))}
|
|
423
|
+
</div>
|
|
424
|
+
</section>
|
|
425
|
+
),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<div className={cn("space-y-6 pt-4", className)}>
|
|
431
|
+
{mapSlot}
|
|
432
|
+
{sections.map((s, i) => (
|
|
433
|
+
<Fragment key={s.key}>
|
|
434
|
+
{(i > 0 || mapSlot) && <Divider />}
|
|
435
|
+
{s.node}
|
|
436
|
+
</Fragment>
|
|
437
|
+
))}
|
|
472
438
|
</div>
|
|
473
439
|
);
|
|
474
440
|
}
|