@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.
@@ -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