@bailierich/booking-components 2.0.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/README.md +319 -0
- package/TENANT_DATA_INTEGRATION.md +402 -0
- package/TENANT_SETUP.md +316 -0
- package/components/BookingFlow/BookingFlow.tsx +790 -0
- package/components/BookingFlow/index.ts +5 -0
- package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
- package/components/BookingFlow/steps/Confirmation.tsx +185 -0
- package/components/BookingFlow/steps/ContactForm.tsx +292 -0
- package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
- package/components/BookingFlow/steps/DateSelection.tsx +473 -0
- package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
- package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
- package/components/BookingFlow/steps/index.ts +10 -0
- package/components/BottomSheet/index.tsx +120 -0
- package/components/Forms/FormBlock.tsx +283 -0
- package/components/Forms/FormField.tsx +385 -0
- package/components/Forms/FormRenderer.tsx +216 -0
- package/components/Forms/FormValidation.ts +122 -0
- package/components/Forms/index.ts +4 -0
- package/components/HoldTimer/HoldTimer.tsx +266 -0
- package/components/HoldTimer/index.ts +2 -0
- package/components/SectionRenderer.tsx +558 -0
- package/components/Sections/About.tsx +145 -0
- package/components/Sections/BeforeAfter.tsx +81 -0
- package/components/Sections/BookingSection.tsx +76 -0
- package/components/Sections/Contact.tsx +103 -0
- package/components/Sections/FAQSection.tsx +239 -0
- package/components/Sections/FeatureContent.tsx +113 -0
- package/components/Sections/FeaturedLink.tsx +103 -0
- package/components/Sections/FixedInfoCard.tsx +189 -0
- package/components/Sections/Gallery.tsx +83 -0
- package/components/Sections/Header.tsx +78 -0
- package/components/Sections/Hero.tsx +178 -0
- package/components/Sections/ImageSection.tsx +147 -0
- package/components/Sections/InstagramFeed.tsx +38 -0
- package/components/Sections/LinkList.tsx +76 -0
- package/components/Sections/LocationMap.tsx +202 -0
- package/components/Sections/Logo.tsx +61 -0
- package/components/Sections/MinimalFooter.tsx +78 -0
- package/components/Sections/MinimalHeader.tsx +81 -0
- package/components/Sections/MinimalNavigation.tsx +63 -0
- package/components/Sections/Navbar.tsx +258 -0
- package/components/Sections/PricingTable.tsx +106 -0
- package/components/Sections/ScrollingTextDivider.tsx +138 -0
- package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
- package/components/Sections/ServicesPreview.tsx +129 -0
- package/components/Sections/SocialBar.tsx +177 -0
- package/components/Sections/Team.tsx +80 -0
- package/components/Sections/Testimonials.tsx +92 -0
- package/components/Sections/TextSection.tsx +116 -0
- package/components/Sections/VideoSection.tsx +178 -0
- package/components/Sections/index.ts +57 -0
- package/components/index.ts +21 -0
- package/dist/index-DAai7Glf.d.mts +474 -0
- package/dist/index-DAai7Glf.d.ts +474 -0
- package/dist/index.d.mts +1075 -0
- package/dist/index.d.ts +1075 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles/index.d.mts +1 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.js +2 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/index.mjs +2 -0
- package/dist/styles/index.mjs.map +1 -0
- package/docs/API.md +849 -0
- package/docs/CALLBACKS.md +760 -0
- package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
- package/docs/DATA_SHAPES.md +684 -0
- package/docs/MIGRATION.md +662 -0
- package/docs/PAYMENT_INTEGRATION.md +766 -0
- package/docs/SESSION_SUMMARY.md +185 -0
- package/docs/STYLING.md +735 -0
- package/index.ts +4 -0
- package/lib/storage.ts +239 -0
- package/package.json +59 -0
- package/styles/animations.ts +210 -0
- package/styles/index.ts +1 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +13 -0
- package/types/index.ts +369 -0
package/docs/STYLING.md
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
# Styling & Theme Customization
|
|
2
|
+
|
|
3
|
+
Complete guide to customizing the appearance of `@oviah/booking-components`.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Theme Configuration](#theme-configuration)
|
|
8
|
+
- [Color Schemes](#color-schemes)
|
|
9
|
+
- [Typography](#typography)
|
|
10
|
+
- [Custom CSS](#custom-css)
|
|
11
|
+
- [Animations](#animations)
|
|
12
|
+
- [Responsive Design](#responsive-design)
|
|
13
|
+
- [Dark Mode](#dark-mode)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Theme Configuration
|
|
19
|
+
|
|
20
|
+
### Basic Theme Setup
|
|
21
|
+
|
|
22
|
+
All components accept `colors` and `typography` props for theming:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
const theme = {
|
|
26
|
+
colors: {
|
|
27
|
+
primary: '#D8C4FF',
|
|
28
|
+
secondary: '#014421',
|
|
29
|
+
text: '#000000',
|
|
30
|
+
bookingText: '#1A1A1A',
|
|
31
|
+
linkBackground: '#D8C4FF',
|
|
32
|
+
linkText: '#FFFFFF',
|
|
33
|
+
buttonText: '#FFFFFF'
|
|
34
|
+
},
|
|
35
|
+
typography: {
|
|
36
|
+
headingFont: 'Geist Sans',
|
|
37
|
+
bodyFont: 'Inter',
|
|
38
|
+
bodySize: '16px'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Use with any component
|
|
43
|
+
<Hero
|
|
44
|
+
headline="Welcome"
|
|
45
|
+
colors={theme.colors}
|
|
46
|
+
typography={theme.typography}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Global Theme Provider (Recommended)
|
|
51
|
+
|
|
52
|
+
Create a theme context to avoid prop drilling:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// contexts/ThemeContext.tsx
|
|
56
|
+
import { createContext, useContext } from 'react';
|
|
57
|
+
|
|
58
|
+
interface Theme {
|
|
59
|
+
colors: ColorScheme;
|
|
60
|
+
typography: TypographyConfig;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ThemeContext = createContext<Theme | null>(null);
|
|
64
|
+
|
|
65
|
+
export function ThemeProvider({ children, theme }: { children: React.ReactNode; theme: Theme }) {
|
|
66
|
+
return (
|
|
67
|
+
<ThemeContext.Provider value={theme}>
|
|
68
|
+
{children}
|
|
69
|
+
</ThemeContext.Provider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useTheme() {
|
|
74
|
+
const theme = useContext(ThemeContext);
|
|
75
|
+
if (!theme) throw new Error('useTheme must be used within ThemeProvider');
|
|
76
|
+
return theme;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Usage
|
|
80
|
+
<ThemeProvider theme={theme}>
|
|
81
|
+
<BookingFlow config={config} colors={theme.colors} />
|
|
82
|
+
</ThemeProvider>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Color Schemes
|
|
88
|
+
|
|
89
|
+
### Primary Colors
|
|
90
|
+
|
|
91
|
+
Define your brand's main colors:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface ColorScheme {
|
|
95
|
+
primary: string; // Main brand color
|
|
96
|
+
secondary: string; // Secondary/accent color
|
|
97
|
+
text: string; // Main text color
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Examples:**
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// Purple & Green (Default OVIAH theme)
|
|
105
|
+
const oviahTheme = {
|
|
106
|
+
primary: '#D8C4FF',
|
|
107
|
+
secondary: '#014421',
|
|
108
|
+
text: '#000000'
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Blue & Orange
|
|
112
|
+
const blueOrangeTheme = {
|
|
113
|
+
primary: '#3B82F6',
|
|
114
|
+
secondary: '#F97316',
|
|
115
|
+
text: '#1F2937'
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Pink & Teal
|
|
119
|
+
const pinkTealTheme = {
|
|
120
|
+
primary: '#EC4899',
|
|
121
|
+
secondary: '#14B8A6',
|
|
122
|
+
text: '#111827'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Monochrome
|
|
126
|
+
const monochromeTheme = {
|
|
127
|
+
primary: '#000000',
|
|
128
|
+
secondary: '#6B7280',
|
|
129
|
+
text: '#111827'
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Extended Colors
|
|
134
|
+
|
|
135
|
+
Additional colors for specific use cases:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface ExtendedColorScheme extends ColorScheme {
|
|
139
|
+
bookingText?: string; // Text color in booking widget
|
|
140
|
+
linkBackground?: string; // Link button background
|
|
141
|
+
linkText?: string; // Link button text
|
|
142
|
+
buttonText?: string; // Button text color
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Example:**
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
const extendedTheme = {
|
|
150
|
+
primary: '#D8C4FF',
|
|
151
|
+
secondary: '#014421',
|
|
152
|
+
text: '#000000',
|
|
153
|
+
bookingText: '#1A1A1A', // Slightly softer than pure black
|
|
154
|
+
linkBackground: '#D8C4FF', // Same as primary
|
|
155
|
+
linkText: '#FFFFFF', // White text on colored buttons
|
|
156
|
+
buttonText: '#FFFFFF' // White text on primary buttons
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Color Utilities
|
|
161
|
+
|
|
162
|
+
Helper functions for color manipulation:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Convert hex to RGBA
|
|
166
|
+
function hexToRgba(hex: string, alpha: number): string {
|
|
167
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
168
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
169
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
170
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Usage
|
|
174
|
+
const transparentPrimary = hexToRgba(colors.primary, 0.1);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Get contrast color (black or white) for backgrounds
|
|
179
|
+
function getContrastColor(hexColor: string): string {
|
|
180
|
+
const hex = hexColor.replace('#', '');
|
|
181
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
182
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
183
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
184
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
185
|
+
return luminance > 0.5 ? '#000000' : '#FFFFFF';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Usage
|
|
189
|
+
const textColor = getContrastColor(colors.primary);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Typography
|
|
195
|
+
|
|
196
|
+
### Font Configuration
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
interface TypographyConfig {
|
|
200
|
+
headingFont?: string; // Font family for headings
|
|
201
|
+
bodyFont?: string; // Font family for body text
|
|
202
|
+
bodySize?: string; // Base font size
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Loading Custom Fonts
|
|
207
|
+
|
|
208
|
+
#### Using Google Fonts (Next.js)
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
// app/layout.tsx
|
|
212
|
+
import { Geist_Sans, Inter } from 'next/font/google';
|
|
213
|
+
|
|
214
|
+
const geistSans = Geist_Sans({
|
|
215
|
+
subsets: ['latin'],
|
|
216
|
+
variable: '--font-geist-sans'
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const inter = Inter({
|
|
220
|
+
subsets: ['latin'],
|
|
221
|
+
variable: '--font-inter'
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
225
|
+
return (
|
|
226
|
+
<html lang="en" className={`${geistSans.variable} ${inter.variable}`}>
|
|
227
|
+
<body>{children}</body>
|
|
228
|
+
</html>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Use in theme
|
|
233
|
+
const theme = {
|
|
234
|
+
typography: {
|
|
235
|
+
headingFont: 'var(--font-geist-sans)',
|
|
236
|
+
bodyFont: 'var(--font-inter)',
|
|
237
|
+
bodySize: '16px'
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Using Custom Fonts
|
|
243
|
+
|
|
244
|
+
```css
|
|
245
|
+
/* globals.css */
|
|
246
|
+
@font-face {
|
|
247
|
+
font-family: 'MyCustomFont';
|
|
248
|
+
src: url('/fonts/MyCustomFont.woff2') format('woff2');
|
|
249
|
+
font-weight: normal;
|
|
250
|
+
font-style: normal;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@font-face {
|
|
254
|
+
font-family: 'MyCustomFont';
|
|
255
|
+
src: url('/fonts/MyCustomFont-Bold.woff2') format('woff2');
|
|
256
|
+
font-weight: bold;
|
|
257
|
+
font-style: normal;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
const theme = {
|
|
263
|
+
typography: {
|
|
264
|
+
headingFont: 'MyCustomFont',
|
|
265
|
+
bodyFont: 'system-ui',
|
|
266
|
+
bodySize: '16px'
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Font Size Scale
|
|
272
|
+
|
|
273
|
+
Recommended font sizes for different screen sizes:
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
const fontSizes = {
|
|
277
|
+
mobile: {
|
|
278
|
+
xs: '12px',
|
|
279
|
+
sm: '14px',
|
|
280
|
+
base: '16px',
|
|
281
|
+
lg: '18px',
|
|
282
|
+
xl: '20px',
|
|
283
|
+
'2xl': '24px',
|
|
284
|
+
'3xl': '30px'
|
|
285
|
+
},
|
|
286
|
+
tablet: {
|
|
287
|
+
xs: '12px',
|
|
288
|
+
sm: '14px',
|
|
289
|
+
base: '16px',
|
|
290
|
+
lg: '18px',
|
|
291
|
+
xl: '22px',
|
|
292
|
+
'2xl': '28px',
|
|
293
|
+
'3xl': '36px'
|
|
294
|
+
},
|
|
295
|
+
desktop: {
|
|
296
|
+
xs: '12px',
|
|
297
|
+
sm: '14px',
|
|
298
|
+
base: '16px',
|
|
299
|
+
lg: '20px',
|
|
300
|
+
xl: '24px',
|
|
301
|
+
'2xl': '32px',
|
|
302
|
+
'3xl': '48px'
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Custom CSS
|
|
310
|
+
|
|
311
|
+
### Overriding Component Styles
|
|
312
|
+
|
|
313
|
+
Use CSS custom properties (CSS variables) for easy overrides:
|
|
314
|
+
|
|
315
|
+
```css
|
|
316
|
+
/* globals.css */
|
|
317
|
+
:root {
|
|
318
|
+
/* Override component spacing */
|
|
319
|
+
--booking-padding: 1.5rem;
|
|
320
|
+
--booking-gap: 1rem;
|
|
321
|
+
|
|
322
|
+
/* Override component sizes */
|
|
323
|
+
--button-height: 44px;
|
|
324
|
+
--input-height: 48px;
|
|
325
|
+
|
|
326
|
+
/* Override borders */
|
|
327
|
+
--border-radius: 12px;
|
|
328
|
+
--border-width: 2px;
|
|
329
|
+
|
|
330
|
+
/* Override shadows */
|
|
331
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
332
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
333
|
+
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Custom Wrapper Styles
|
|
338
|
+
|
|
339
|
+
Wrap components with custom styling:
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
<div className="custom-booking-wrapper">
|
|
343
|
+
<BookingFlow
|
|
344
|
+
config={config}
|
|
345
|
+
colors={colors}
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<style jsx>{`
|
|
350
|
+
.custom-booking-wrapper {
|
|
351
|
+
max-width: 800px;
|
|
352
|
+
margin: 0 auto;
|
|
353
|
+
padding: 2rem;
|
|
354
|
+
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
|
|
355
|
+
border-radius: 24px;
|
|
356
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@media (max-width: 768px) {
|
|
360
|
+
.custom-booking-wrapper {
|
|
361
|
+
padding: 1rem;
|
|
362
|
+
border-radius: 16px;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
`}</style>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Tailwind CSS Integration
|
|
369
|
+
|
|
370
|
+
If using Tailwind CSS:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
<div className="max-w-4xl mx-auto p-6 bg-gradient-to-br from-purple-50 to-green-50 rounded-3xl shadow-2xl">
|
|
374
|
+
<BookingFlow
|
|
375
|
+
config={config}
|
|
376
|
+
colors={{
|
|
377
|
+
primary: '#D8C4FF',
|
|
378
|
+
secondary: '#014421',
|
|
379
|
+
text: '#000000'
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Animations
|
|
388
|
+
|
|
389
|
+
### Using Built-in Animations
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import {
|
|
393
|
+
createEntranceAnimation,
|
|
394
|
+
createStaggerAnimation,
|
|
395
|
+
getSlideVariants,
|
|
396
|
+
getFadeVariants
|
|
397
|
+
} from '@oviah/booking-components/styles';
|
|
398
|
+
|
|
399
|
+
// Entrance animation
|
|
400
|
+
<motion.div {...createEntranceAnimation(0.2)}>
|
|
401
|
+
<Hero headline="Welcome" colors={colors} />
|
|
402
|
+
</motion.div>
|
|
403
|
+
|
|
404
|
+
// Stagger animation for lists
|
|
405
|
+
const staggerConfig = createStaggerAnimation(items.length, 0.1, 0.05);
|
|
406
|
+
|
|
407
|
+
{items.map((item, index) => (
|
|
408
|
+
<motion.div
|
|
409
|
+
key={item.id}
|
|
410
|
+
initial="hidden"
|
|
411
|
+
animate="visible"
|
|
412
|
+
variants={staggerConfig.variants}
|
|
413
|
+
transition={{ delay: staggerConfig.getDelay(index) }}
|
|
414
|
+
>
|
|
415
|
+
{item.content}
|
|
416
|
+
</motion.div>
|
|
417
|
+
))}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Custom Transition Speeds
|
|
421
|
+
|
|
422
|
+
Configure animation speeds in BookingFlow:
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
<BookingFlow
|
|
426
|
+
config={{
|
|
427
|
+
steps: { ... },
|
|
428
|
+
transitions: {
|
|
429
|
+
style: 'slide', // 'slide' | 'fade'
|
|
430
|
+
speed: 'fast' // 'fast' | 'normal' | 'slow'
|
|
431
|
+
}
|
|
432
|
+
}}
|
|
433
|
+
colors={colors}
|
|
434
|
+
/>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Respecting User Preferences
|
|
438
|
+
|
|
439
|
+
The components automatically respect `prefers-reduced-motion`:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
// This is handled internally
|
|
443
|
+
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
444
|
+
const duration = reducedMotion ? 0.15 : 0.3;
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Responsive Design
|
|
450
|
+
|
|
451
|
+
### Breakpoints
|
|
452
|
+
|
|
453
|
+
Components use these breakpoints:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
const breakpoints = {
|
|
457
|
+
sm: '640px', // Mobile landscape
|
|
458
|
+
md: '768px', // Tablet
|
|
459
|
+
lg: '1024px', // Desktop
|
|
460
|
+
xl: '1280px', // Large desktop
|
|
461
|
+
'2xl': '1536px' // Extra large
|
|
462
|
+
};
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Responsive Layouts
|
|
466
|
+
|
|
467
|
+
#### Service Selection
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
<ServiceSelection
|
|
471
|
+
settings={{
|
|
472
|
+
serviceLayout: 'grid',
|
|
473
|
+
columns: 2 // 1 col on mobile, 2 on tablet+
|
|
474
|
+
}}
|
|
475
|
+
colors={colors}
|
|
476
|
+
/>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### Gallery
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
<Gallery
|
|
483
|
+
columns={3} // Responsive: 2 cols on mobile, 3 on tablet+
|
|
484
|
+
images={images}
|
|
485
|
+
colors={colors}
|
|
486
|
+
/>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Mobile-First Considerations
|
|
490
|
+
|
|
491
|
+
```css
|
|
492
|
+
/* Mobile-first approach */
|
|
493
|
+
.booking-container {
|
|
494
|
+
padding: 1rem;
|
|
495
|
+
font-size: 14px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@media (min-width: 768px) {
|
|
499
|
+
.booking-container {
|
|
500
|
+
padding: 2rem;
|
|
501
|
+
font-size: 16px;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@media (min-width: 1024px) {
|
|
506
|
+
.booking-container {
|
|
507
|
+
padding: 3rem;
|
|
508
|
+
font-size: 18px;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Dark Mode
|
|
516
|
+
|
|
517
|
+
### Implementing Dark Mode
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
'use client';
|
|
521
|
+
|
|
522
|
+
import { useState, useEffect } from 'react';
|
|
523
|
+
|
|
524
|
+
function useDarkMode() {
|
|
525
|
+
const [isDark, setIsDark] = useState(false);
|
|
526
|
+
|
|
527
|
+
useEffect(() => {
|
|
528
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
529
|
+
setIsDark(mediaQuery.matches);
|
|
530
|
+
|
|
531
|
+
const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
|
|
532
|
+
mediaQuery.addEventListener('change', handler);
|
|
533
|
+
return () => mediaQuery.removeEventListener('change', handler);
|
|
534
|
+
}, []);
|
|
535
|
+
|
|
536
|
+
return isDark;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Usage
|
|
540
|
+
export function BookingWithDarkMode() {
|
|
541
|
+
const isDark = useDarkMode();
|
|
542
|
+
|
|
543
|
+
const lightTheme = {
|
|
544
|
+
colors: {
|
|
545
|
+
primary: '#D8C4FF',
|
|
546
|
+
secondary: '#014421',
|
|
547
|
+
text: '#000000',
|
|
548
|
+
bookingText: '#1A1A1A'
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const darkTheme = {
|
|
553
|
+
colors: {
|
|
554
|
+
primary: '#B8A4DF',
|
|
555
|
+
secondary: '#02652F',
|
|
556
|
+
text: '#FFFFFF',
|
|
557
|
+
bookingText: '#E5E5E5'
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const theme = isDark ? darkTheme : lightTheme;
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
<div style={{
|
|
565
|
+
backgroundColor: isDark ? '#1A1A1A' : '#FFFFFF',
|
|
566
|
+
minHeight: '100vh'
|
|
567
|
+
}}>
|
|
568
|
+
<BookingFlow
|
|
569
|
+
config={config}
|
|
570
|
+
colors={theme.colors}
|
|
571
|
+
/>
|
|
572
|
+
</div>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Dark Mode Color Palettes
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
const themes = {
|
|
581
|
+
light: {
|
|
582
|
+
colors: {
|
|
583
|
+
primary: '#D8C4FF',
|
|
584
|
+
secondary: '#014421',
|
|
585
|
+
text: '#000000',
|
|
586
|
+
background: '#FFFFFF',
|
|
587
|
+
surface: '#F9FAFB'
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
dark: {
|
|
591
|
+
colors: {
|
|
592
|
+
primary: '#B8A4DF',
|
|
593
|
+
secondary: '#02652F',
|
|
594
|
+
text: '#FFFFFF',
|
|
595
|
+
background: '#1A1A1A',
|
|
596
|
+
surface: '#2D2D2D'
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## Best Practices
|
|
605
|
+
|
|
606
|
+
### 1. Use CSS Variables for Consistency
|
|
607
|
+
|
|
608
|
+
```css
|
|
609
|
+
:root {
|
|
610
|
+
--primary-color: #D8C4FF;
|
|
611
|
+
--secondary-color: #014421;
|
|
612
|
+
--text-color: #000000;
|
|
613
|
+
--spacing-sm: 0.5rem;
|
|
614
|
+
--spacing-md: 1rem;
|
|
615
|
+
--spacing-lg: 2rem;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### 2. Maintain WCAG Contrast Ratios
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
// Ensure sufficient contrast (minimum 4.5:1 for normal text)
|
|
623
|
+
const colors = {
|
|
624
|
+
primary: '#D8C4FF',
|
|
625
|
+
text: '#000000' // Good contrast against white backgrounds
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// For text on colored backgrounds
|
|
629
|
+
const buttonStyles = {
|
|
630
|
+
backgroundColor: colors.primary,
|
|
631
|
+
color: getContrastColor(colors.primary) // Automatically white or black
|
|
632
|
+
};
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### 3. Test on Multiple Devices
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
// Use responsive testing
|
|
639
|
+
const testSizes = {
|
|
640
|
+
mobile: '375px',
|
|
641
|
+
tablet: '768px',
|
|
642
|
+
desktop: '1440px'
|
|
643
|
+
};
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### 4. Keep Brand Consistency
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
// Create a brand constants file
|
|
650
|
+
export const BRAND_COLORS = {
|
|
651
|
+
primary: '#D8C4FF',
|
|
652
|
+
secondary: '#014421',
|
|
653
|
+
accent: '#FF6F61'
|
|
654
|
+
} as const;
|
|
655
|
+
|
|
656
|
+
export const BRAND_FONTS = {
|
|
657
|
+
heading: 'Geist Sans',
|
|
658
|
+
body: 'Inter'
|
|
659
|
+
} as const;
|
|
660
|
+
|
|
661
|
+
// Use throughout your app
|
|
662
|
+
<BookingFlow
|
|
663
|
+
config={config}
|
|
664
|
+
colors={BRAND_COLORS}
|
|
665
|
+
/>
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### 5. Optimize for Performance
|
|
669
|
+
|
|
670
|
+
```tsx
|
|
671
|
+
// Lazy load heavy components
|
|
672
|
+
import dynamic from 'next/dynamic';
|
|
673
|
+
|
|
674
|
+
const BookingFlow = dynamic(
|
|
675
|
+
() => import('@oviah/booking-components').then(mod => mod.BookingFlow),
|
|
676
|
+
{ ssr: false }
|
|
677
|
+
);
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Example: Complete Custom Theme
|
|
683
|
+
|
|
684
|
+
```tsx
|
|
685
|
+
// theme.ts
|
|
686
|
+
export const customTheme = {
|
|
687
|
+
colors: {
|
|
688
|
+
primary: '#8B5CF6', // Violet
|
|
689
|
+
secondary: '#10B981', // Emerald
|
|
690
|
+
text: '#111827', // Gray 900
|
|
691
|
+
bookingText: '#374151', // Gray 700
|
|
692
|
+
linkBackground: '#8B5CF6',
|
|
693
|
+
linkText: '#FFFFFF',
|
|
694
|
+
buttonText: '#FFFFFF'
|
|
695
|
+
},
|
|
696
|
+
typography: {
|
|
697
|
+
headingFont: 'Poppins',
|
|
698
|
+
bodyFont: 'Inter',
|
|
699
|
+
bodySize: '16px'
|
|
700
|
+
},
|
|
701
|
+
spacing: {
|
|
702
|
+
container: '1.5rem',
|
|
703
|
+
section: '4rem',
|
|
704
|
+
element: '1rem'
|
|
705
|
+
},
|
|
706
|
+
borderRadius: {
|
|
707
|
+
sm: '8px',
|
|
708
|
+
md: '12px',
|
|
709
|
+
lg: '16px',
|
|
710
|
+
full: '9999px'
|
|
711
|
+
},
|
|
712
|
+
shadows: {
|
|
713
|
+
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
|
|
714
|
+
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
715
|
+
lg: '0 10px 15px rgba(0, 0, 0, 0.15)',
|
|
716
|
+
xl: '0 20px 25px rgba(0, 0, 0, 0.15)'
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// Use throughout your app
|
|
721
|
+
<ThemeProvider theme={customTheme}>
|
|
722
|
+
<BookingFlow
|
|
723
|
+
config={config}
|
|
724
|
+
colors={customTheme.colors}
|
|
725
|
+
/>
|
|
726
|
+
</ThemeProvider>
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## See Also
|
|
732
|
+
|
|
733
|
+
- [DATA_SHAPES.md](./DATA_SHAPES.md) - Data structures
|
|
734
|
+
- [API.md](./API.md) - Component API reference
|
|
735
|
+
- [CALLBACKS.md](./CALLBACKS.md) - Event handling
|
package/index.ts
ADDED