@aws505/sheetsite 1.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 +105 -0
- package/dist/components/index.js +1696 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +1630 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.js +1840 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1793 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/data/index.js +1296 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/index.mjs +1220 -0
- package/dist/data/index.mjs.map +1 -0
- package/dist/index.js +5433 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5285 -0
- package/dist/index.mjs.map +1 -0
- package/dist/seo/index.js +187 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/index.mjs +155 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/theme/index.js +552 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/index.mjs +526 -0
- package/dist/theme/index.mjs.map +1 -0
- package/package.json +96 -0
- package/src/components/index.ts +41 -0
- package/src/components/layout/Footer.tsx +234 -0
- package/src/components/layout/Header.tsx +134 -0
- package/src/components/sections/FAQ.tsx +178 -0
- package/src/components/sections/Gallery.tsx +107 -0
- package/src/components/sections/Hero.tsx +202 -0
- package/src/components/sections/Hours.tsx +225 -0
- package/src/components/sections/Services.tsx +216 -0
- package/src/components/sections/Testimonials.tsx +184 -0
- package/src/components/ui/Button.tsx +158 -0
- package/src/components/ui/Card.tsx +162 -0
- package/src/components/ui/Icons.tsx +508 -0
- package/src/config/index.ts +207 -0
- package/src/config/presets/generic.ts +153 -0
- package/src/config/presets/home-kitchen.ts +154 -0
- package/src/config/presets/index.ts +708 -0
- package/src/config/presets/professional.ts +165 -0
- package/src/config/presets/repair.ts +160 -0
- package/src/config/presets/restaurant.ts +162 -0
- package/src/config/presets/salon.ts +178 -0
- package/src/config/presets/tailor.ts +159 -0
- package/src/config/types.ts +314 -0
- package/src/data/csv-parser.ts +154 -0
- package/src/data/defaults.ts +202 -0
- package/src/data/google-drive.ts +148 -0
- package/src/data/index.ts +535 -0
- package/src/data/sheets.ts +709 -0
- package/src/data/types.ts +379 -0
- package/src/seo/index.ts +272 -0
- package/src/theme/colors.ts +351 -0
- package/src/theme/index.ts +249 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Library
|
|
3
|
+
*
|
|
4
|
+
* SVG icons for common use cases across business websites.
|
|
5
|
+
* All icons use currentColor for easy theming.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
export interface IconProps {
|
|
11
|
+
className?: string;
|
|
12
|
+
size?: number;
|
|
13
|
+
'aria-hidden'?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaultProps: IconProps = {
|
|
17
|
+
className: '',
|
|
18
|
+
size: 24,
|
|
19
|
+
'aria-hidden': true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// GENERAL ICONS
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
export function PhoneIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
27
|
+
return (
|
|
28
|
+
<svg
|
|
29
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
30
|
+
width={size}
|
|
31
|
+
height={size}
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
strokeWidth="2"
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
strokeLinejoin="round"
|
|
38
|
+
className={className}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function MailIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
47
|
+
return (
|
|
48
|
+
<svg
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
width={size}
|
|
51
|
+
height={size}
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
fill="none"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
strokeWidth="2"
|
|
56
|
+
strokeLinecap="round"
|
|
57
|
+
strokeLinejoin="round"
|
|
58
|
+
className={className}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<rect width="20" height="16" x="2" y="4" rx="2" />
|
|
62
|
+
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
|
63
|
+
</svg>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function MapPinIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
68
|
+
return (
|
|
69
|
+
<svg
|
|
70
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
71
|
+
width={size}
|
|
72
|
+
height={size}
|
|
73
|
+
viewBox="0 0 24 24"
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
strokeWidth="2"
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
className={className}
|
|
80
|
+
{...props}
|
|
81
|
+
>
|
|
82
|
+
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
|
|
83
|
+
<circle cx="12" cy="10" r="3" />
|
|
84
|
+
</svg>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function ClockIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
89
|
+
return (
|
|
90
|
+
<svg
|
|
91
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
92
|
+
width={size}
|
|
93
|
+
height={size}
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
strokeWidth="2"
|
|
98
|
+
strokeLinecap="round"
|
|
99
|
+
strokeLinejoin="round"
|
|
100
|
+
className={className}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
<circle cx="12" cy="12" r="10" />
|
|
104
|
+
<polyline points="12 6 12 12 16 14" />
|
|
105
|
+
</svg>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function StarIcon({ className = '', size = 24, filled = false, ...props }: IconProps & { filled?: boolean }) {
|
|
110
|
+
return (
|
|
111
|
+
<svg
|
|
112
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
113
|
+
width={size}
|
|
114
|
+
height={size}
|
|
115
|
+
viewBox="0 0 24 24"
|
|
116
|
+
fill={filled ? 'currentColor' : 'none'}
|
|
117
|
+
stroke="currentColor"
|
|
118
|
+
strokeWidth="2"
|
|
119
|
+
strokeLinecap="round"
|
|
120
|
+
strokeLinejoin="round"
|
|
121
|
+
className={className}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
|
125
|
+
</svg>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function ChevronDownIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
130
|
+
return (
|
|
131
|
+
<svg
|
|
132
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
133
|
+
width={size}
|
|
134
|
+
height={size}
|
|
135
|
+
viewBox="0 0 24 24"
|
|
136
|
+
fill="none"
|
|
137
|
+
stroke="currentColor"
|
|
138
|
+
strokeWidth="2"
|
|
139
|
+
strokeLinecap="round"
|
|
140
|
+
strokeLinejoin="round"
|
|
141
|
+
className={className}
|
|
142
|
+
{...props}
|
|
143
|
+
>
|
|
144
|
+
<path d="m6 9 6 6 6-6" />
|
|
145
|
+
</svg>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function ChevronRightIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
150
|
+
return (
|
|
151
|
+
<svg
|
|
152
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
153
|
+
width={size}
|
|
154
|
+
height={size}
|
|
155
|
+
viewBox="0 0 24 24"
|
|
156
|
+
fill="none"
|
|
157
|
+
stroke="currentColor"
|
|
158
|
+
strokeWidth="2"
|
|
159
|
+
strokeLinecap="round"
|
|
160
|
+
strokeLinejoin="round"
|
|
161
|
+
className={className}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<path d="m9 18 6-6-6-6" />
|
|
165
|
+
</svg>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function MenuIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
170
|
+
return (
|
|
171
|
+
<svg
|
|
172
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
173
|
+
width={size}
|
|
174
|
+
height={size}
|
|
175
|
+
viewBox="0 0 24 24"
|
|
176
|
+
fill="none"
|
|
177
|
+
stroke="currentColor"
|
|
178
|
+
strokeWidth="2"
|
|
179
|
+
strokeLinecap="round"
|
|
180
|
+
strokeLinejoin="round"
|
|
181
|
+
className={className}
|
|
182
|
+
{...props}
|
|
183
|
+
>
|
|
184
|
+
<line x1="4" x2="20" y1="12" y2="12" />
|
|
185
|
+
<line x1="4" x2="20" y1="6" y2="6" />
|
|
186
|
+
<line x1="4" x2="20" y1="18" y2="18" />
|
|
187
|
+
</svg>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function XIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
192
|
+
return (
|
|
193
|
+
<svg
|
|
194
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
195
|
+
width={size}
|
|
196
|
+
height={size}
|
|
197
|
+
viewBox="0 0 24 24"
|
|
198
|
+
fill="none"
|
|
199
|
+
stroke="currentColor"
|
|
200
|
+
strokeWidth="2"
|
|
201
|
+
strokeLinecap="round"
|
|
202
|
+
strokeLinejoin="round"
|
|
203
|
+
className={className}
|
|
204
|
+
{...props}
|
|
205
|
+
>
|
|
206
|
+
<path d="M18 6 6 18" />
|
|
207
|
+
<path d="m6 6 12 12" />
|
|
208
|
+
</svg>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function CheckIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
213
|
+
return (
|
|
214
|
+
<svg
|
|
215
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
216
|
+
width={size}
|
|
217
|
+
height={size}
|
|
218
|
+
viewBox="0 0 24 24"
|
|
219
|
+
fill="none"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
strokeWidth="2"
|
|
222
|
+
strokeLinecap="round"
|
|
223
|
+
strokeLinejoin="round"
|
|
224
|
+
className={className}
|
|
225
|
+
{...props}
|
|
226
|
+
>
|
|
227
|
+
<path d="M20 6 9 17l-5-5" />
|
|
228
|
+
</svg>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// =============================================================================
|
|
233
|
+
// SERVICE ICONS
|
|
234
|
+
// =============================================================================
|
|
235
|
+
|
|
236
|
+
export function ScissorsIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
237
|
+
return (
|
|
238
|
+
<svg
|
|
239
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
240
|
+
width={size}
|
|
241
|
+
height={size}
|
|
242
|
+
viewBox="0 0 24 24"
|
|
243
|
+
fill="none"
|
|
244
|
+
stroke="currentColor"
|
|
245
|
+
strokeWidth="2"
|
|
246
|
+
strokeLinecap="round"
|
|
247
|
+
strokeLinejoin="round"
|
|
248
|
+
className={className}
|
|
249
|
+
{...props}
|
|
250
|
+
>
|
|
251
|
+
<circle cx="6" cy="6" r="3" />
|
|
252
|
+
<path d="M8.12 8.12 12 12" />
|
|
253
|
+
<path d="M20 4 8.12 15.88" />
|
|
254
|
+
<circle cx="6" cy="18" r="3" />
|
|
255
|
+
<path d="M14.8 14.8 20 20" />
|
|
256
|
+
</svg>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function WrenchIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
261
|
+
return (
|
|
262
|
+
<svg
|
|
263
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
264
|
+
width={size}
|
|
265
|
+
height={size}
|
|
266
|
+
viewBox="0 0 24 24"
|
|
267
|
+
fill="none"
|
|
268
|
+
stroke="currentColor"
|
|
269
|
+
strokeWidth="2"
|
|
270
|
+
strokeLinecap="round"
|
|
271
|
+
strokeLinejoin="round"
|
|
272
|
+
className={className}
|
|
273
|
+
{...props}
|
|
274
|
+
>
|
|
275
|
+
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
|
276
|
+
</svg>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function SparklesIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
281
|
+
return (
|
|
282
|
+
<svg
|
|
283
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
284
|
+
width={size}
|
|
285
|
+
height={size}
|
|
286
|
+
viewBox="0 0 24 24"
|
|
287
|
+
fill="none"
|
|
288
|
+
stroke="currentColor"
|
|
289
|
+
strokeWidth="2"
|
|
290
|
+
strokeLinecap="round"
|
|
291
|
+
strokeLinejoin="round"
|
|
292
|
+
className={className}
|
|
293
|
+
{...props}
|
|
294
|
+
>
|
|
295
|
+
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
|
|
296
|
+
<path d="M5 3v4" />
|
|
297
|
+
<path d="M19 17v4" />
|
|
298
|
+
<path d="M3 5h4" />
|
|
299
|
+
<path d="M17 19h4" />
|
|
300
|
+
</svg>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function HeartIcon({ className = '', size = 24, filled = false, ...props }: IconProps & { filled?: boolean }) {
|
|
305
|
+
return (
|
|
306
|
+
<svg
|
|
307
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
308
|
+
width={size}
|
|
309
|
+
height={size}
|
|
310
|
+
viewBox="0 0 24 24"
|
|
311
|
+
fill={filled ? 'currentColor' : 'none'}
|
|
312
|
+
stroke="currentColor"
|
|
313
|
+
strokeWidth="2"
|
|
314
|
+
strokeLinecap="round"
|
|
315
|
+
strokeLinejoin="round"
|
|
316
|
+
className={className}
|
|
317
|
+
{...props}
|
|
318
|
+
>
|
|
319
|
+
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
|
|
320
|
+
</svg>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function UtensilsIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
325
|
+
return (
|
|
326
|
+
<svg
|
|
327
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
328
|
+
width={size}
|
|
329
|
+
height={size}
|
|
330
|
+
viewBox="0 0 24 24"
|
|
331
|
+
fill="none"
|
|
332
|
+
stroke="currentColor"
|
|
333
|
+
strokeWidth="2"
|
|
334
|
+
strokeLinecap="round"
|
|
335
|
+
strokeLinejoin="round"
|
|
336
|
+
className={className}
|
|
337
|
+
{...props}
|
|
338
|
+
>
|
|
339
|
+
<path d="M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2" />
|
|
340
|
+
<path d="M7 2v20" />
|
|
341
|
+
<path d="M21 15V2v0a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7" />
|
|
342
|
+
</svg>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function CakeIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
347
|
+
return (
|
|
348
|
+
<svg
|
|
349
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
350
|
+
width={size}
|
|
351
|
+
height={size}
|
|
352
|
+
viewBox="0 0 24 24"
|
|
353
|
+
fill="none"
|
|
354
|
+
stroke="currentColor"
|
|
355
|
+
strokeWidth="2"
|
|
356
|
+
strokeLinecap="round"
|
|
357
|
+
strokeLinejoin="round"
|
|
358
|
+
className={className}
|
|
359
|
+
{...props}
|
|
360
|
+
>
|
|
361
|
+
<path d="M20 21v-8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8" />
|
|
362
|
+
<path d="M4 16s.5-1 2-1 2.5 2 4 2 2.5-2 4-2 2.5 2 4 2 2-1 2-1" />
|
|
363
|
+
<path d="M2 21h20" />
|
|
364
|
+
<path d="M7 8v3" />
|
|
365
|
+
<path d="M12 8v3" />
|
|
366
|
+
<path d="M17 8v3" />
|
|
367
|
+
<path d="M7 4h.01" />
|
|
368
|
+
<path d="M12 4h.01" />
|
|
369
|
+
<path d="M17 4h.01" />
|
|
370
|
+
</svg>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function BriefcaseIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
375
|
+
return (
|
|
376
|
+
<svg
|
|
377
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
378
|
+
width={size}
|
|
379
|
+
height={size}
|
|
380
|
+
viewBox="0 0 24 24"
|
|
381
|
+
fill="none"
|
|
382
|
+
stroke="currentColor"
|
|
383
|
+
strokeWidth="2"
|
|
384
|
+
strokeLinecap="round"
|
|
385
|
+
strokeLinejoin="round"
|
|
386
|
+
className={className}
|
|
387
|
+
{...props}
|
|
388
|
+
>
|
|
389
|
+
<rect width="20" height="14" x="2" y="7" rx="2" ry="2" />
|
|
390
|
+
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
|
|
391
|
+
</svg>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// =============================================================================
|
|
396
|
+
// SOCIAL ICONS
|
|
397
|
+
// =============================================================================
|
|
398
|
+
|
|
399
|
+
export function YelpIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
400
|
+
return (
|
|
401
|
+
<svg
|
|
402
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
403
|
+
width={size}
|
|
404
|
+
height={size}
|
|
405
|
+
viewBox="0 0 24 24"
|
|
406
|
+
fill="currentColor"
|
|
407
|
+
className={className}
|
|
408
|
+
{...props}
|
|
409
|
+
>
|
|
410
|
+
<path d="M20.16 12.73l-3.29 1.47c-.64.28-1.31-.26-1.2-.97l.52-3.4c.08-.56.49-.98 1.03-1.04l3.39-.38c.7-.08 1.22.58 1 1.23l-1.07 2.62c-.14.35-.36.41-.38.47zM14.13 15.58l-.52 3.4c-.11.7.55 1.25 1.2.97l3.29-1.47c.02-.06.24-.12.38-.47l1.07-2.62c.22-.65-.3-1.31-1-1.23l-3.39.38c-.54.06-.95.48-1.03 1.04zM10.98 3.05c-.47-.6-1.37-.5-1.7.2l-3.6 7.51c-.24.5-.01 1.1.51 1.33l2.63 1.21c.62.28 1.3-.2 1.27-.89l-.33-7.97c-.02-.56-.3-1.01-.78-1.39zM8.37 14.35l-2.63-1.21c-.52-.23-.75-.83-.51-1.33l3.6-7.51c.33-.7 1.23-.8 1.7-.2.48.38.76.83.78 1.39l.33 7.97c.03.69-.65 1.17-1.27.89zM10.04 16.42l-3.22 1.8c-.62.35-1.36-.14-1.28-.86l.44-3.52c.06-.53.42-.95.92-1.08l3.02-.78c.69-.18 1.32.43 1.09 1.1l-1.24 3.06c-.14.33-.38.34-.42.34-.04 0-.18-.01-.31.04v-.1z" />
|
|
411
|
+
</svg>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function InstagramIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
416
|
+
return (
|
|
417
|
+
<svg
|
|
418
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
419
|
+
width={size}
|
|
420
|
+
height={size}
|
|
421
|
+
viewBox="0 0 24 24"
|
|
422
|
+
fill="none"
|
|
423
|
+
stroke="currentColor"
|
|
424
|
+
strokeWidth="2"
|
|
425
|
+
strokeLinecap="round"
|
|
426
|
+
strokeLinejoin="round"
|
|
427
|
+
className={className}
|
|
428
|
+
{...props}
|
|
429
|
+
>
|
|
430
|
+
<rect width="20" height="20" x="2" y="2" rx="5" ry="5" />
|
|
431
|
+
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
|
|
432
|
+
<line x1="17.5" x2="17.51" y1="6.5" y2="6.5" />
|
|
433
|
+
</svg>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function FacebookIcon({ className = '', size = 24, ...props }: IconProps) {
|
|
438
|
+
return (
|
|
439
|
+
<svg
|
|
440
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
441
|
+
width={size}
|
|
442
|
+
height={size}
|
|
443
|
+
viewBox="0 0 24 24"
|
|
444
|
+
fill="none"
|
|
445
|
+
stroke="currentColor"
|
|
446
|
+
strokeWidth="2"
|
|
447
|
+
strokeLinecap="round"
|
|
448
|
+
strokeLinejoin="round"
|
|
449
|
+
className={className}
|
|
450
|
+
{...props}
|
|
451
|
+
>
|
|
452
|
+
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
|
|
453
|
+
</svg>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// ICON MAP
|
|
459
|
+
// =============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Map of icon names to components.
|
|
463
|
+
* Use this to render icons dynamically based on string names.
|
|
464
|
+
*/
|
|
465
|
+
export const iconMap: Record<string, React.FC<IconProps>> = {
|
|
466
|
+
phone: PhoneIcon,
|
|
467
|
+
mail: MailIcon,
|
|
468
|
+
email: MailIcon,
|
|
469
|
+
'map-pin': MapPinIcon,
|
|
470
|
+
location: MapPinIcon,
|
|
471
|
+
clock: ClockIcon,
|
|
472
|
+
time: ClockIcon,
|
|
473
|
+
star: StarIcon,
|
|
474
|
+
'chevron-down': ChevronDownIcon,
|
|
475
|
+
'chevron-right': ChevronRightIcon,
|
|
476
|
+
menu: MenuIcon,
|
|
477
|
+
x: XIcon,
|
|
478
|
+
close: XIcon,
|
|
479
|
+
check: CheckIcon,
|
|
480
|
+
scissors: ScissorsIcon,
|
|
481
|
+
wrench: WrenchIcon,
|
|
482
|
+
tool: WrenchIcon,
|
|
483
|
+
sparkles: SparklesIcon,
|
|
484
|
+
heart: HeartIcon,
|
|
485
|
+
utensils: UtensilsIcon,
|
|
486
|
+
food: UtensilsIcon,
|
|
487
|
+
cake: CakeIcon,
|
|
488
|
+
briefcase: BriefcaseIcon,
|
|
489
|
+
yelp: YelpIcon,
|
|
490
|
+
instagram: InstagramIcon,
|
|
491
|
+
facebook: FacebookIcon,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Get an icon component by name.
|
|
496
|
+
*/
|
|
497
|
+
export function getIcon(name: string): React.FC<IconProps> | null {
|
|
498
|
+
return iconMap[name.toLowerCase()] || null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Render an icon by name.
|
|
503
|
+
*/
|
|
504
|
+
export function Icon({ name, ...props }: IconProps & { name: string }) {
|
|
505
|
+
const IconComponent = getIcon(name);
|
|
506
|
+
if (!IconComponent) return null;
|
|
507
|
+
return <IconComponent {...props} />;
|
|
508
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SheetSite Configuration Module
|
|
3
|
+
*
|
|
4
|
+
* Provides configuration types, business type presets, and utilities
|
|
5
|
+
* for setting up and customizing websites.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
export * from './types';
|
|
10
|
+
|
|
11
|
+
// Presets
|
|
12
|
+
export {
|
|
13
|
+
presets,
|
|
14
|
+
getPreset,
|
|
15
|
+
getSupportedBusinessTypes,
|
|
16
|
+
businessCategories,
|
|
17
|
+
recommendBusinessType,
|
|
18
|
+
tailorPreset,
|
|
19
|
+
restaurantPreset,
|
|
20
|
+
homeKitchenPreset,
|
|
21
|
+
salonPreset,
|
|
22
|
+
repairPreset,
|
|
23
|
+
professionalPreset,
|
|
24
|
+
genericPreset,
|
|
25
|
+
} from './presets';
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// CONFIGURATION UTILITIES
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
SiteConfig,
|
|
33
|
+
BusinessType,
|
|
34
|
+
BusinessPreset,
|
|
35
|
+
ThemePreset,
|
|
36
|
+
PageConfig,
|
|
37
|
+
HomeSectionType,
|
|
38
|
+
} from './types';
|
|
39
|
+
import { getPreset } from './presets';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a site configuration from a business preset.
|
|
43
|
+
* Allows overriding specific values.
|
|
44
|
+
*/
|
|
45
|
+
export function createSiteConfig(
|
|
46
|
+
businessType: BusinessType | string,
|
|
47
|
+
overrides?: Partial<SiteConfig>
|
|
48
|
+
): SiteConfig {
|
|
49
|
+
const preset = getPreset(businessType);
|
|
50
|
+
const config = { ...preset.config };
|
|
51
|
+
|
|
52
|
+
if (overrides) {
|
|
53
|
+
// Merge theme
|
|
54
|
+
if (overrides.theme) {
|
|
55
|
+
config.theme = { ...config.theme, ...overrides.theme };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Merge pages
|
|
59
|
+
if (overrides.pages) {
|
|
60
|
+
config.pages = { ...config.pages, ...overrides.pages } as SiteConfig['pages'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Merge SEO
|
|
64
|
+
if (overrides.seo) {
|
|
65
|
+
config.seo = { ...config.seo, ...overrides.seo };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Merge contact form
|
|
69
|
+
if (overrides.contactForm) {
|
|
70
|
+
config.contactForm = { ...config.contactForm, ...overrides.contactForm };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Merge features
|
|
74
|
+
if (overrides.features) {
|
|
75
|
+
config.features = { ...config.features, ...overrides.features };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Simple overrides
|
|
79
|
+
if (overrides.siteUrl) config.siteUrl = overrides.siteUrl;
|
|
80
|
+
if (overrides.customClasses) {
|
|
81
|
+
config.customClasses = { ...config.customClasses, ...overrides.customClasses };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return config;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the sheet template for a business type.
|
|
90
|
+
* Useful for generating documentation or Google Sheet templates.
|
|
91
|
+
*/
|
|
92
|
+
export function getSheetTemplate(businessType: BusinessType | string): BusinessPreset['sheetTemplate'] {
|
|
93
|
+
const preset = getPreset(businessType);
|
|
94
|
+
return preset.sheetTemplate;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get default data for a business type.
|
|
99
|
+
*/
|
|
100
|
+
export function getDefaultData(businessType: BusinessType | string): BusinessPreset['defaults'] {
|
|
101
|
+
const preset = getPreset(businessType);
|
|
102
|
+
return preset.defaults;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get icon suggestions for a business type.
|
|
107
|
+
*/
|
|
108
|
+
export function getIconSuggestions(businessType: BusinessType | string): string[] {
|
|
109
|
+
const preset = getPreset(businessType);
|
|
110
|
+
return preset.iconSuggestions;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get image suggestions for a business type.
|
|
115
|
+
*/
|
|
116
|
+
export function getImageSuggestions(businessType: BusinessType | string): string[] {
|
|
117
|
+
const preset = getPreset(businessType);
|
|
118
|
+
return preset.imageSuggestions;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate a site configuration.
|
|
123
|
+
*/
|
|
124
|
+
export function validateSiteConfig(config: SiteConfig): { valid: boolean; errors: string[] } {
|
|
125
|
+
const errors: string[] = [];
|
|
126
|
+
|
|
127
|
+
// Check required fields
|
|
128
|
+
if (!config.businessType) {
|
|
129
|
+
errors.push('businessType is required');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!config.pages?.home) {
|
|
133
|
+
errors.push('pages.home configuration is required');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!config.theme) {
|
|
137
|
+
errors.push('theme configuration is required');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check home page sections
|
|
141
|
+
if (config.pages?.home?.sections) {
|
|
142
|
+
const validSections: HomeSectionType[] = [
|
|
143
|
+
'hero', 'services', 'menu', 'products', 'gallery',
|
|
144
|
+
'testimonials', 'team', 'faq', 'hours', 'location',
|
|
145
|
+
'contact-form', 'cta', 'how-it-works', 'announcements', 'about-preview',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
for (const section of config.pages.home.sections) {
|
|
149
|
+
if (!validSections.includes(section)) {
|
|
150
|
+
errors.push(`Invalid home section: ${section}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
valid: errors.length === 0,
|
|
157
|
+
errors,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// THEME HELPERS
|
|
163
|
+
// =============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get available theme presets.
|
|
167
|
+
*/
|
|
168
|
+
export const themePresets: ThemePreset[] = [
|
|
169
|
+
'warm-brown',
|
|
170
|
+
'cool-blue',
|
|
171
|
+
'earth-green',
|
|
172
|
+
'warm-amber',
|
|
173
|
+
'elegant-gold',
|
|
174
|
+
'fresh-teal',
|
|
175
|
+
'bold-red',
|
|
176
|
+
'soft-pink',
|
|
177
|
+
'slate-gray',
|
|
178
|
+
'forest-green',
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get a description for a theme preset.
|
|
183
|
+
*/
|
|
184
|
+
export function getThemeDescription(preset: ThemePreset): string {
|
|
185
|
+
const descriptions: Record<ThemePreset, string> = {
|
|
186
|
+
'warm-brown': 'Warm, earthy tones perfect for tailoring, leather goods, and traditional crafts',
|
|
187
|
+
'cool-blue': 'Professional and trustworthy, ideal for professional services and tech',
|
|
188
|
+
'earth-green': 'Natural and organic, great for eco-friendly, outdoor, and health businesses',
|
|
189
|
+
'warm-amber': 'Welcoming and appetizing, perfect for food, hospitality, and home businesses',
|
|
190
|
+
'elegant-gold': 'Luxurious and sophisticated, suited for upscale and formal businesses',
|
|
191
|
+
'fresh-teal': 'Modern and creative, ideal for spas, creative studios, and wellness',
|
|
192
|
+
'bold-red': 'Energetic and urgent, great for automotive, sports, and action-oriented businesses',
|
|
193
|
+
'soft-pink': 'Gentle and feminine, perfect for beauty, weddings, and delicate services',
|
|
194
|
+
'slate-gray': 'Minimalist and industrial, suited for modern, urban businesses',
|
|
195
|
+
'forest-green': 'Deep and natural, ideal for outdoor, nature, and sustainability-focused businesses',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return descriptions[preset];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Recommend a theme based on business type.
|
|
203
|
+
*/
|
|
204
|
+
export function recommendTheme(businessType: BusinessType | string): ThemePreset {
|
|
205
|
+
const preset = getPreset(businessType);
|
|
206
|
+
return preset.config.theme.preset || 'cool-blue';
|
|
207
|
+
}
|