@idkwebsites/components 0.1.16 → 0.1.18
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/BOOKING-WIDGET.md +340 -0
- package/dist/index.cjs +2058 -1132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -4
- package/dist/index.d.ts +167 -4
- package/dist/index.js +2037 -1107
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1394 -0
- package/dist/styles.css.map +1 -1
- package/package.json +25 -9
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Booking Widget — Porting Guide
|
|
2
|
+
|
|
3
|
+
Reusable booking component built on `@idkwebsites/components`. This doc covers everything needed to port the widget to another project or extract it into the package.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `app/components/booking-widget-panel.tsx` | The widget component (all logic + render) |
|
|
12
|
+
| `app/globals.css` (`.bw-*` section) | All widget styles, starting at the `/* ── Booking Widget ──` comment |
|
|
13
|
+
| `app/booking/page.tsx` | Example booking page (site-specific layout wrapper) |
|
|
14
|
+
|
|
15
|
+
## Dependencies
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"@idkwebsites/components": "useBookingWidget, useServices, useTeam hooks",
|
|
20
|
+
"framer-motion": "AnimatePresence + motion for staff trigger animation, chevron rotation",
|
|
21
|
+
"smooth-scrollbar": "Smooth scrollbar with bounce overscroll on services list + mobile body",
|
|
22
|
+
"lucide-react": "Scissors, Sparkles, Heart, Palette, CircleDot, User, ChevronLeft/Right/Down, Check, ArrowRight/Left, Globe, Menu, X",
|
|
23
|
+
"next": "next/link for mobile nav links"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
interface BookingWidgetProps {
|
|
33
|
+
/** Navigation links for the mobile menu overlay. Omit to hide the hamburger entirely. */
|
|
34
|
+
navLinks?: { label: string; href: string }[];
|
|
35
|
+
/** Custom icon for the mobile menu open button (default: lucide Menu 20px) */
|
|
36
|
+
menuIcon?: React.ReactNode;
|
|
37
|
+
/** Custom icon for the mobile menu close button (default: lucide X 20px) */
|
|
38
|
+
closeIcon?: React.ReactNode;
|
|
39
|
+
/** Override the category → icon mapping (keyword → lucide component) */
|
|
40
|
+
categoryIcons?: Record<string, LucideIcon>;
|
|
41
|
+
/** Override step titles for the mobile 4-step flow */
|
|
42
|
+
stepTitles?: [string, string, string, string];
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Usage example
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { BookingWidgetPanel } from "@/app/components/booking-widget-panel";
|
|
50
|
+
import { primaryNavLinks } from "@/app/lib/site-config";
|
|
51
|
+
import { Flower2 } from "lucide-react";
|
|
52
|
+
|
|
53
|
+
<BookingWidgetPanel
|
|
54
|
+
navLinks={primaryNavLinks}
|
|
55
|
+
menuIcon={<Flower2 size={20} />}
|
|
56
|
+
categoryIcons={{ botanical: Flower2 }}
|
|
57
|
+
stepTitles={[
|
|
58
|
+
"Choose your treatment",
|
|
59
|
+
"Select a service",
|
|
60
|
+
"Pick a date & time",
|
|
61
|
+
"Your information",
|
|
62
|
+
]}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Page Layout
|
|
69
|
+
|
|
70
|
+
The booking page should fill the entire viewport (navbar + widget, nothing else). **No footer. No floating elements (phone button, chat, etc.).**
|
|
71
|
+
|
|
72
|
+
Do NOT use your site shell component (e.g. `SiteShell`). Site shells typically include footers, floating phone buttons, and other chrome that shouldn't appear on the booking page. Import only the site header directly.
|
|
73
|
+
|
|
74
|
+
### Recommended: Flex wrapper (handles any navbar height automatically)
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// app/booking/page.tsx
|
|
78
|
+
import { SiteHeader } from "@/app/components/site-header";
|
|
79
|
+
import { BookingWidgetPanel } from "@/app/components/booking-widget-panel";
|
|
80
|
+
import { primaryNavLinks } from "@/app/lib/site-config";
|
|
81
|
+
|
|
82
|
+
export default function BookingPage() {
|
|
83
|
+
return (
|
|
84
|
+
<div className="booking-page">
|
|
85
|
+
<SiteHeader />
|
|
86
|
+
<BookingWidgetPanel navLinks={primaryNavLinks} />
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
/* Site-specific CSS */
|
|
94
|
+
.booking-page {
|
|
95
|
+
height: 100vh;
|
|
96
|
+
height: 100dvh;
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.booking-page > .bw {
|
|
103
|
+
flex: 1;
|
|
104
|
+
min-height: 0;
|
|
105
|
+
height: auto;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Hide site header on mobile — the widget has its own topbar with hamburger */
|
|
109
|
+
@media (max-width: 1024px) {
|
|
110
|
+
.booking-page > .site-header { display: none; }
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Key points:**
|
|
115
|
+
- The wrapper div uses `overflow: hidden` — no scrolling on this page, ever (desktop). Mobile uses smooth-scrollbar on the body for bounce overscroll.
|
|
116
|
+
- `.bw` uses `flex: 1` to fill remaining space — no hardcoded navbar heights needed.
|
|
117
|
+
- On mobile (≤1024px), the site header is hidden. The widget's own topbar shows progress dots + a hamburger menu that opens a nav overlay. Pass `navLinks` to populate it.
|
|
118
|
+
|
|
119
|
+
### Alternative: Offset variable (when you can't control the page layout)
|
|
120
|
+
|
|
121
|
+
If you must use a shell/layout you can't modify, set `--bw-offset-top` instead:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.bw {
|
|
125
|
+
--bw-offset-top: 73px; /* navbar height including borders */
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (max-width: 1024px) {
|
|
129
|
+
.bw {
|
|
130
|
+
--bw-offset-top: 61px;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The base `.bw` styles use `height: calc(100dvh - var(--bw-offset-top))`. Default is `0px`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Theming
|
|
140
|
+
|
|
141
|
+
Override these CSS custom properties on `.bw` or a parent to re-skin the widget:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
.bw {
|
|
145
|
+
--bw-font: "Inter", sans-serif;
|
|
146
|
+
--bw-bg: #FFFFFF;
|
|
147
|
+
--bw-text: #1A1A1A;
|
|
148
|
+
--bw-text-secondary: #6B6B6B;
|
|
149
|
+
--bw-text-muted: #999999;
|
|
150
|
+
--bw-border: #E8E8E8;
|
|
151
|
+
--bw-border-light: #F0F0F0;
|
|
152
|
+
--bw-radius: 8px;
|
|
153
|
+
--bw-radius-lg: 12px;
|
|
154
|
+
--bw-primary: #1A1A1A; /* buttons, selected states, active borders */
|
|
155
|
+
--bw-primary-text: #FFFFFF;
|
|
156
|
+
--bw-hover: #F8F8F8;
|
|
157
|
+
--bw-error-bg: #fef2f2;
|
|
158
|
+
--bw-error-text: #b91c1c;
|
|
159
|
+
--bw-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); /* dropdowns */
|
|
160
|
+
--bw-shadow-badge: 0 1px 4px rgba(0, 0, 0, 0.12); /* price badge on image */
|
|
161
|
+
--bw-offset-top: 0px; /* see Page Layout section */
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
All visual styling uses these tokens. Change them per-site and the widget adapts.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Component Structure
|
|
170
|
+
|
|
171
|
+
### Desktop (>1024px): 3-column grid
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
┌──────────────────────────────────────────────────────────┐
|
|
175
|
+
│ Schedule your visit │
|
|
176
|
+
├────────────┬─────────────────────────┬───────────────────┤
|
|
177
|
+
│ LEFT │ CENTER │ RIGHT │
|
|
178
|
+
│ │ │ │
|
|
179
|
+
│ [Dropdown] │ February 2026 ˅ < >│ Full name │
|
|
180
|
+
│ Specialist │ │ Email │
|
|
181
|
+
│ ───────── │ Calendar grid │ Phone │
|
|
182
|
+
│ ✂ Hair ˅ │ │ Notes │
|
|
183
|
+
│ ☐ Cut │ ───────────── │ │
|
|
184
|
+
│ ☑ Color │ Time slots │ Booking Summary │
|
|
185
|
+
│ ☐ Style │ (4-per-row grid) │ · Specialist │
|
|
186
|
+
│ ✦ Skin › │ │ [Confirm Booking] │
|
|
187
|
+
│ ♥ Spa › │ 🌐 Timezone │ │
|
|
188
|
+
│ │ │ │
|
|
189
|
+
└────────────┴─────────────────────────┴───────────────────┘
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **Left column (22%):** Specialist custom dropdown → divider → category accordion cards with services nested under the expanded category
|
|
193
|
+
- **Center column (flexible):** Calendar card with custom month dropdown + time slots (4 per row)
|
|
194
|
+
- **Right column (22%):** Form fields, booking summary (with specialist), confirm button
|
|
195
|
+
|
|
196
|
+
### Mobile (≤1024px): 4-step flow
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
┌─────────────────────────┐
|
|
200
|
+
│ ●● ○ ○ ≡ │ ← progress dots + hamburger
|
|
201
|
+
├─────────────────────────┤
|
|
202
|
+
│ Schedule your visit │ ← step title
|
|
203
|
+
│ │
|
|
204
|
+
│ [content for step] │ ← scrollable body with bounce overscroll
|
|
205
|
+
│ │
|
|
206
|
+
├─────────────────────────┤
|
|
207
|
+
│ Next → │ ← footer (back + next/confirm)
|
|
208
|
+
└─────────────────────────┘
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Step 1: Specialist dropdown + category accordion
|
|
212
|
+
Step 2: Service list (with images + price badges when available)
|
|
213
|
+
Step 3: Calendar + time slots
|
|
214
|
+
Step 4: Form fields (scrollable) + booking summary (in footer)
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Key Behaviors
|
|
219
|
+
|
|
220
|
+
### Specialist selection
|
|
221
|
+
- Custom dropdown (NOT a native `<select>`). Comes FIRST because services/categories may depend on which specialist is chosen.
|
|
222
|
+
- **Always visible** — shows "Any Available" by default.
|
|
223
|
+
- **Staff list comes from `useTeam()` hook** — NOT from `bw.staffOptions`. The hook's built-in `staffOptions` filters by selected service, which is the opposite of what we want.
|
|
224
|
+
- **Staff selection filters services** — services are client-side filtered by `assignedStaff`.
|
|
225
|
+
- **Dropdown is portaled** to `.bw-portal` (inside `.bw` but outside `.bw-body`) to escape smooth-scrollbar's `transform` context. Uses `position: fixed` + `z-index: 10000`.
|
|
226
|
+
- **Booking summary** includes specialist name (or "Any Available") on both desktop and mobile.
|
|
227
|
+
|
|
228
|
+
### Category selection (accordion)
|
|
229
|
+
- Categories render as accordion cards with auto-mapped icons.
|
|
230
|
+
- **Animated with CSS grid:** `grid-template-rows: 0fr → 1fr` transition (250ms). No framer-motion for the accordion — pure CSS for smooth, jitter-free animation.
|
|
231
|
+
- **Each accordion section filters its own services** from the full service list by category. This prevents the closing section from showing the wrong services during the collapse animation.
|
|
232
|
+
- Icon mapping (override via `categoryIcons` prop): `hair/cut/barber/styling → Scissors`, `skin/facial/beauty → Sparkles`, `massage/wellness/spa → Heart`, `nail/color/manicure/pedicure → Palette`, default → `CircleDot`.
|
|
233
|
+
|
|
234
|
+
### Services list — varied content
|
|
235
|
+
Services adapt their layout based on available data:
|
|
236
|
+
|
|
237
|
+
**Desktop (inline, inside accordion):**
|
|
238
|
+
- Checkbox (18px) + name (12px) + meta "dur · price" (11px) + optional description (11px, truncated) + optional thumbnail (36×36px, 6px radius)
|
|
239
|
+
|
|
240
|
+
**Mobile step 2 (full):**
|
|
241
|
+
- **With image:** Checkbox (20px) + name (14px) + duration (12px) + description (11px) + 120×120px image (14px radius) with price badge overlay (white bg, 8px radius, 13px bold, shadow)
|
|
242
|
+
- **Without image:** Checkbox (20px) + name + duration + description + price text on right (14px, 600 weight)
|
|
243
|
+
- No dividers between rows
|
|
244
|
+
|
|
245
|
+
### Mobile topbar
|
|
246
|
+
- **Progress dots** on the left (28px wide, 3px tall, 8px gap)
|
|
247
|
+
- **Hamburger menu** on the right (customizable via `menuIcon` / `closeIcon` props)
|
|
248
|
+
- Opens a full-screen nav overlay with links from `navLinks` prop
|
|
249
|
+
- Topbar sits above the overlay (`z-index: 101` vs `100`) so the toggle button is always clickable
|
|
250
|
+
- If `navLinks` is empty/omitted, the hamburger is hidden entirely
|
|
251
|
+
|
|
252
|
+
### Step 1 validation
|
|
253
|
+
- "Next" button is disabled until a category is selected (`categoryFilter !== "all"`)
|
|
254
|
+
|
|
255
|
+
### Mobile scrolling
|
|
256
|
+
- Body uses `smooth-scrollbar` with `OverscrollPlugin` (bounce effect)
|
|
257
|
+
- Scroll position resets instantly on step change (`scrollTo(0, 0, 0)`)
|
|
258
|
+
- Scrollbar tracks are hidden
|
|
259
|
+
|
|
260
|
+
### Booking summary
|
|
261
|
+
- Shows: Service, Specialist, Date, Time, Duration, Estimated total
|
|
262
|
+
- No dividers between rows (clean stacked layout)
|
|
263
|
+
- On mobile step 4, summary is rendered in the footer (attached to the confirm button), not in the scrollable body
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Smooth Scrollbar Setup
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import Scrollbar from "smooth-scrollbar";
|
|
271
|
+
import OverscrollPlugin from "smooth-scrollbar/plugins/overscroll";
|
|
272
|
+
|
|
273
|
+
Scrollbar.use(OverscrollPlugin);
|
|
274
|
+
|
|
275
|
+
const opts = {
|
|
276
|
+
damping: 0.08,
|
|
277
|
+
continuousScrolling: false,
|
|
278
|
+
alwaysShowTracks: false,
|
|
279
|
+
plugins: {
|
|
280
|
+
overscroll: { effect: "bounce", damping: 0.15, maxOverscroll: 80 },
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Applied to:
|
|
286
|
+
- `.bw-svc-scroll-wrap` (services list) — desktop only (≥1025px)
|
|
287
|
+
- `.bw-body` (mobile body) — mobile only (≤1024px), bounce overscroll
|
|
288
|
+
|
|
289
|
+
Scrollbar is re-initialized on media query changes and when category filter changes.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## CSS Architecture
|
|
294
|
+
|
|
295
|
+
All widget classes use the `bw-` prefix. No Tailwind classes — pure CSS for portability.
|
|
296
|
+
|
|
297
|
+
### Critical layout chain (prevents viewport overflow)
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
.bw → height from flex or offset, overflow: hidden, flex column, position: relative
|
|
301
|
+
.bw-topbar → hidden on desktop, flex row on mobile (dots + hamburger)
|
|
302
|
+
.bw-nav-overlay → mobile nav (position: absolute, z-index: 100)
|
|
303
|
+
.bw-header → natural height
|
|
304
|
+
.bw-body → flex: 1, grid on desktop, flex column on mobile
|
|
305
|
+
.bw-body-inner → display: contents on desktop, flex column on mobile
|
|
306
|
+
.bw-col--left → specialist + categories + services
|
|
307
|
+
.bw-col--center → calendar + time slots
|
|
308
|
+
.bw-col--right → form + summary
|
|
309
|
+
.bw-footer → mobile only (next/back buttons, step 4 summary)
|
|
310
|
+
.bw-portal → portal target for staff dropdown (outside .bw-body)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Responsive breakpoints
|
|
314
|
+
|
|
315
|
+
| Breakpoint | Layout |
|
|
316
|
+
|------------|--------|
|
|
317
|
+
| >1024px | 3-column grid: 22% / 1fr / 22%, site header visible |
|
|
318
|
+
| ≤1024px | Single column, 4-step mobile flow, site header hidden, widget topbar with hamburger |
|
|
319
|
+
| ≤480px | Tighter padding, smaller fonts and touch targets |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Porting Checklist
|
|
324
|
+
|
|
325
|
+
1. Copy `booking-widget-panel.tsx` and the `.bw-*` CSS block (from `/* ── Booking Widget ──` to the end of the responsive blocks)
|
|
326
|
+
2. Install deps: `@idkwebsites/components`, `smooth-scrollbar`, `lucide-react`, `framer-motion`
|
|
327
|
+
3. Set up the booking page:
|
|
328
|
+
- Use a flex wrapper div (`.booking-page`) — NOT your site shell
|
|
329
|
+
- Import only the site header, no footer or floating elements
|
|
330
|
+
- Set `overflow: hidden` on the wrapper
|
|
331
|
+
- Add `.booking-page > .bw { flex: 1; min-height: 0; height: auto; }` to your CSS
|
|
332
|
+
- Hide the site header on mobile: `.booking-page > .site-header { display: none; }` at ≤1024px
|
|
333
|
+
4. Pass props:
|
|
334
|
+
- `navLinks` — your site's navigation links for the mobile hamburger menu
|
|
335
|
+
- `menuIcon` / `closeIcon` — custom icons if you don't want the default lucide Menu/X
|
|
336
|
+
- `categoryIcons` — override category-to-icon mapping if your categories don't match the defaults
|
|
337
|
+
- `stepTitles` — override the 4 step titles if needed
|
|
338
|
+
5. Override `--bw-*` CSS tokens to match your brand
|
|
339
|
+
6. The widget handles booking logic via `useBookingWidget`, `useServices`, and `useTeam` hooks
|
|
340
|
+
7. If your site has a floating phone/chat button, hide it on the booking page
|