@pixelated-tech/components 3.2.12 → 3.2.14
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.COMPONENTS.md +146 -48
- package/README.md +130 -100
- package/dist/components/callout/callout.scss +0 -3
- package/dist/components/cms/flickr.js +8 -2
- package/dist/components/cms/google.reviews.components.js +1 -1
- package/dist/components/cms/wordpress.components.js +1 -1
- package/dist/components/cms/wordpress.css +7 -0
- package/dist/components/menu/menu-expando.js +7 -1
- package/dist/components/nerdjoke/nerdjoke.js +13 -7
- package/dist/components/pagebuilder/components/ComponentPropertiesForm.js +1 -1
- package/dist/components/pagebuilder/form/form.css +5 -1
- package/dist/components/pagebuilder/form/formcomponents.js +1 -1
- package/dist/components/seo/manifest.js +40 -0
- package/dist/components/seo/schema-localbusiness.js +46 -2
- package/dist/components/seo/schema-website.js +31 -2
- package/dist/components/seo/sitemap.js +3 -3
- package/dist/data/routes.json +25 -0
- package/dist/data/routes2.json +25 -0
- package/dist/index.js +2 -2
- package/dist/index.server.js +0 -1
- package/dist/types/components/cms/flickr.d.ts.map +1 -1
- package/dist/types/components/cms/google.reviews.components.d.ts.map +1 -1
- package/dist/types/components/config/config.types.d.ts +30 -0
- package/dist/types/components/config/config.types.d.ts.map +1 -1
- package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
- package/dist/types/components/nerdjoke/nerdjoke.d.ts.map +1 -1
- package/dist/types/components/pagebuilder/components/ComponentPropertiesForm.d.ts +1 -1
- package/dist/types/components/seo/manifest.d.ts +19 -0
- package/dist/types/components/seo/manifest.d.ts.map +1 -0
- package/dist/types/components/seo/schema-localbusiness.d.ts +22 -23
- package/dist/types/components/seo/schema-localbusiness.d.ts.map +1 -1
- package/dist/types/components/seo/schema-website.d.ts +17 -18
- package/dist/types/components/seo/schema-website.d.ts.map +1 -1
- package/dist/types/components/seo/sitemap.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.server.d.ts +0 -1
- package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.schema.stories.d.ts +23 -0
- package/dist/types/stories/seo/seo.schema.stories.d.ts.map +1 -0
- package/dist/types/tests/component-properties-form.test.d.ts +2 -0
- package/dist/types/tests/component-properties-form.test.d.ts.map +1 -0
- package/dist/types/tests/component-selector.test.d.ts +2 -0
- package/dist/types/tests/component-selector.test.d.ts.map +1 -0
- package/dist/types/tests/component-tree.test.d.ts +2 -0
- package/dist/types/tests/component-tree.test.d.ts.map +1 -0
- package/dist/types/tests/manifest.test.d.ts +2 -0
- package/dist/types/tests/manifest.test.d.ts.map +1 -0
- package/dist/types/tests/page-builder-ui.test.d.ts +2 -0
- package/dist/types/tests/page-builder-ui.test.d.ts.map +1 -0
- package/dist/types/tests/page-engine.test.d.ts +2 -0
- package/dist/types/tests/page-engine.test.d.ts.map +1 -0
- package/dist/types/tests/save-load-section.test.d.ts +2 -0
- package/dist/types/tests/save-load-section.test.d.ts.map +1 -0
- package/package.json +5 -5
- package/dist/components/utilities/api.js +0 -36
- package/dist/types/components/utilities/api.d.ts +0 -16
- package/dist/types/components/utilities/api.d.ts.map +0 -1
- package/dist/types/tests/api.test.d.ts +0 -2
- package/dist/types/tests/api.test.d.ts.map +0 -1
package/README.COMPONENTS.md
CHANGED
|
@@ -44,6 +44,7 @@ This guide provides detailed API documentation and usage examples for all Pixela
|
|
|
44
44
|
- [GoogleMap](#googlemap)
|
|
45
45
|
- [GoogleSearch](#googlesearch)
|
|
46
46
|
- [JSON-LD Schemas](#json-ld-schemas)
|
|
47
|
+
- [Manifest](#manifest)
|
|
47
48
|
- [MetadataComponents](#metadatacomponents)
|
|
48
49
|
|
|
49
50
|
### Shopping Cart
|
|
@@ -881,11 +882,16 @@ import { SaveLoadSection } from '@pixelated-tech/components';
|
|
|
881
882
|
|
|
882
883
|
Structured data components for SEO.
|
|
883
884
|
|
|
885
|
+
**Configuration**: The LocalBusiness and Website schema components can use `siteInfo` data from the routes JSON file as fallback values when props are not explicitly provided. This allows for centralized site-wide configuration of business/website information.
|
|
886
|
+
|
|
884
887
|
#### LocalBusiness
|
|
885
888
|
|
|
889
|
+
Generates LocalBusiness JSON-LD structured data. When props are not provided, falls back to `siteInfo` configuration from routes JSON.
|
|
890
|
+
|
|
886
891
|
```tsx
|
|
887
892
|
import { LocalBusinessSchema } from '@pixelated-tech/components';
|
|
888
893
|
|
|
894
|
+
// With explicit props
|
|
889
895
|
<LocalBusinessSchema
|
|
890
896
|
name="My Business"
|
|
891
897
|
address={{
|
|
@@ -896,6 +902,19 @@ import { LocalBusinessSchema } from '@pixelated-tech/components';
|
|
|
896
902
|
}}
|
|
897
903
|
telephone="(555) 123-4567"
|
|
898
904
|
/>
|
|
905
|
+
|
|
906
|
+
// Or with siteinfo object (recommended)
|
|
907
|
+
<LocalBusinessSchema
|
|
908
|
+
siteInfo={siteInfoData}
|
|
909
|
+
streetAddress="123 Main St"
|
|
910
|
+
addressLocality="City"
|
|
911
|
+
addressRegion="State"
|
|
912
|
+
postalCode="12345"
|
|
913
|
+
/>
|
|
914
|
+
|
|
915
|
+
// Or with minimal props (uses siteInfo fallbacks)
|
|
916
|
+
<LocalBusinessSchema />
|
|
917
|
+
```
|
|
899
918
|
```
|
|
900
919
|
|
|
901
920
|
#### Recipe
|
|
@@ -941,9 +960,12 @@ import { ServicesSchema } from '@pixelated-tech/components';
|
|
|
941
960
|
|
|
942
961
|
#### Website
|
|
943
962
|
|
|
963
|
+
Generates Website JSON-LD structured data. When props are not provided, falls back to `siteInfo` configuration from routes JSON.
|
|
964
|
+
|
|
944
965
|
```tsx
|
|
945
966
|
import { WebsiteSchema } from '@pixelated-tech/components';
|
|
946
967
|
|
|
968
|
+
// With explicit props
|
|
947
969
|
<WebsiteSchema
|
|
948
970
|
name="My Website"
|
|
949
971
|
url="https://example.com"
|
|
@@ -953,6 +975,22 @@ import { WebsiteSchema } from '@pixelated-tech/components';
|
|
|
953
975
|
logo: "https://example.com/logo.png"
|
|
954
976
|
}}
|
|
955
977
|
/>
|
|
978
|
+
|
|
979
|
+
// Or with siteinfo object (recommended)
|
|
980
|
+
<WebsiteSchema
|
|
981
|
+
siteInfo={siteInfoData}
|
|
982
|
+
potentialAction={{
|
|
983
|
+
'@type': 'SearchAction',
|
|
984
|
+
target: {
|
|
985
|
+
'@type': 'EntryPoint',
|
|
986
|
+
urlTemplate: 'https://example.com/search?q={search_term}'
|
|
987
|
+
}
|
|
988
|
+
}}
|
|
989
|
+
/>
|
|
990
|
+
|
|
991
|
+
// Or with minimal props (uses siteInfo fallbacks)
|
|
992
|
+
<WebsiteSchema />
|
|
993
|
+
```
|
|
956
994
|
```
|
|
957
995
|
|
|
958
996
|
#### BlogPosting
|
|
@@ -1057,6 +1095,52 @@ import { GoogleSearch } from '@pixelated-tech/components';
|
|
|
1057
1095
|
| `apiKey` | `string` | - | Google API key |
|
|
1058
1096
|
| `placeholder` | `string` | `'Search...'` | Search input placeholder |
|
|
1059
1097
|
|
|
1098
|
+
### Manifest
|
|
1099
|
+
|
|
1100
|
+
Generates a complete PWA manifest from siteinfo configuration. This component centralizes PWA manifest generation and ensures consistency across sites.
|
|
1101
|
+
|
|
1102
|
+
```tsx
|
|
1103
|
+
import { Manifest } from '@pixelated-tech/components';
|
|
1104
|
+
|
|
1105
|
+
// Basic usage with siteinfo
|
|
1106
|
+
export default function manifest() {
|
|
1107
|
+
return Manifest({ siteInfo: myRoutes.siteInfo });
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// With custom properties for site-specific overrides
|
|
1111
|
+
export default function manifest() {
|
|
1112
|
+
return Manifest({
|
|
1113
|
+
siteInfo: myRoutes.siteInfo,
|
|
1114
|
+
customProperties: {
|
|
1115
|
+
orientation: 'portrait',
|
|
1116
|
+
categories: ['business', 'productivity'],
|
|
1117
|
+
lang: 'en-US'
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
#### Props
|
|
1124
|
+
| Prop | Type | Default | Description |
|
|
1125
|
+
|------|------|---------|-------------|
|
|
1126
|
+
| `siteInfo` | `SiteInfo` | - | Site configuration object from routes.json |
|
|
1127
|
+
| `customProperties` | `Partial<MetadataRoute.Manifest>` | `{}` | Optional custom manifest properties to override defaults |
|
|
1128
|
+
|
|
1129
|
+
#### Generated Properties
|
|
1130
|
+
|
|
1131
|
+
The component automatically generates these manifest properties from `siteInfo`:
|
|
1132
|
+
|
|
1133
|
+
- `name` & `short_name`: From `siteInfo.name`
|
|
1134
|
+
- `description`: From `siteInfo.description`
|
|
1135
|
+
- `theme_color`: From `siteInfo.theme_color`
|
|
1136
|
+
- `background_color`: From `siteInfo.background_color`
|
|
1137
|
+
- `display`: From `siteInfo.display`
|
|
1138
|
+
- `homepage_url`: From `siteInfo.url`
|
|
1139
|
+
- `developer`: Object with `name` and `url` from siteInfo
|
|
1140
|
+
- `icons`: Array with favicon configuration
|
|
1141
|
+
- `author`: From `siteInfo.author` (non-standard property)
|
|
1142
|
+
- `default_locale`: From `siteInfo.default_locale`
|
|
1143
|
+
|
|
1060
1144
|
### MetadataComponents
|
|
1061
1145
|
|
|
1062
1146
|
Dynamic meta tag injection for SEO.
|
|
@@ -1312,7 +1396,19 @@ import { NerdJoke } from '@pixelated-tech/components';
|
|
|
1312
1396
|
|
|
1313
1397
|
### PixelatedClientConfigProvider Setup
|
|
1314
1398
|
|
|
1315
|
-
|
|
1399
|
+
The PixelatedClientConfigProvider enables components to access centralized configuration data. Configuration can be loaded from environment variables or a `routes.json` file in your project.
|
|
1400
|
+
|
|
1401
|
+
**Config Consumers:**
|
|
1402
|
+
- **LocalBusinessSchema & WebsiteSchema**: Use `siteInfo` for fallback business/website data
|
|
1403
|
+
- **CloudinaryImage & SmartImage**: Use `cloudinary` for image optimization settings
|
|
1404
|
+
- **WordPress**: Use `wordpress` for API connections
|
|
1405
|
+
- **ContentfulItems**: Use `contentful` for CMS integration
|
|
1406
|
+
- **eBay**: Use `ebay` for store integration
|
|
1407
|
+
- **Flickr**: Use `flickr` for photo gallery integration
|
|
1408
|
+
- **GoogleAnalytics**: Use `googleAnalytics` for tracking
|
|
1409
|
+
- **HubSpot**: Use `hubspot` for CRM integration
|
|
1410
|
+
- **PayPal**: Use `paypal` for payment processing
|
|
1411
|
+
- **Proxy**: Use `proxy` for API proxy settings
|
|
1316
1412
|
|
|
1317
1413
|
```tsx
|
|
1318
1414
|
// app/layout.tsx (Next.js 13+ App Router)
|
|
@@ -1327,15 +1423,63 @@ export default function RootLayout({
|
|
|
1327
1423
|
<html lang="en">
|
|
1328
1424
|
<body>
|
|
1329
1425
|
<PixelatedClientConfigProvider config={{
|
|
1426
|
+
// Site-wide business/website information (used by schema components)
|
|
1427
|
+
siteInfo: {
|
|
1428
|
+
name: 'Your Business Name',
|
|
1429
|
+
description: 'Your business description',
|
|
1430
|
+
url: 'https://yourwebsite.com',
|
|
1431
|
+
email: 'contact@yourwebsite.com',
|
|
1432
|
+
telephone: '(555) 123-4567',
|
|
1433
|
+
address: {
|
|
1434
|
+
streetAddress: '123 Main St',
|
|
1435
|
+
addressLocality: 'City',
|
|
1436
|
+
addressRegion: 'State',
|
|
1437
|
+
postalCode: '12345',
|
|
1438
|
+
addressCountry: 'United States'
|
|
1439
|
+
},
|
|
1440
|
+
openingHours: 'Mo-Fr 09:00-18:00'
|
|
1441
|
+
},
|
|
1442
|
+
// Image optimization
|
|
1330
1443
|
cloudinary: {
|
|
1331
1444
|
product_env: 'production',
|
|
1332
1445
|
baseUrl: 'https://res.cloudinary.com/your-account',
|
|
1333
1446
|
transforms: 'f_auto,q_auto,w_auto'
|
|
1334
1447
|
},
|
|
1448
|
+
// CMS integrations
|
|
1335
1449
|
wordpress: {
|
|
1336
1450
|
site: 'your-blog.wordpress.com'
|
|
1337
1451
|
},
|
|
1338
|
-
|
|
1452
|
+
contentful: {
|
|
1453
|
+
spaceId: 'your-space-id',
|
|
1454
|
+
accessToken: 'your-access-token',
|
|
1455
|
+
environment: 'master'
|
|
1456
|
+
},
|
|
1457
|
+
// E-commerce
|
|
1458
|
+
ebay: {
|
|
1459
|
+
appId: 'your-app-id',
|
|
1460
|
+
globalId: 'EBAY-US'
|
|
1461
|
+
},
|
|
1462
|
+
paypal: {
|
|
1463
|
+
sandboxPayPalApiKey: 'your-sandbox-key',
|
|
1464
|
+
payPalApiKey: 'your-production-key'
|
|
1465
|
+
},
|
|
1466
|
+
// Analytics & CRM
|
|
1467
|
+
googleAnalytics: {
|
|
1468
|
+
id: 'GA-XXXXXXXXX'
|
|
1469
|
+
},
|
|
1470
|
+
hubspot: {
|
|
1471
|
+
portalId: 'your-portal-id',
|
|
1472
|
+
formId: 'your-form-id'
|
|
1473
|
+
},
|
|
1474
|
+
// Media services
|
|
1475
|
+
flickr: {
|
|
1476
|
+
api_key: 'your-flickr-api-key',
|
|
1477
|
+
user_id: 'your-user-id'
|
|
1478
|
+
},
|
|
1479
|
+
// API proxy
|
|
1480
|
+
proxy: {
|
|
1481
|
+
proxyURL: 'https://proxy.pixelated.tech/prod/proxy?url='
|
|
1482
|
+
}
|
|
1339
1483
|
}}>
|
|
1340
1484
|
{children}
|
|
1341
1485
|
</PixelatedClientConfigProvider>
|
|
@@ -1345,52 +1489,6 @@ export default function RootLayout({
|
|
|
1345
1489
|
}
|
|
1346
1490
|
```
|
|
1347
1491
|
|
|
1348
|
-
### Cloudinary Configuration
|
|
1349
|
-
|
|
1350
|
-
```tsx
|
|
1351
|
-
const cloudinaryConfig = {
|
|
1352
|
-
product_env: 'production', // Environment identifier
|
|
1353
|
-
baseUrl: 'https://res.cloudinary.com/your-account', // Your Cloudinary URL
|
|
1354
|
-
transforms: 'f_auto,q_auto,w_auto' // Default transformations
|
|
1355
|
-
};
|
|
1356
|
-
```
|
|
1357
|
-
|
|
1358
|
-
### WordPress Configuration
|
|
1359
|
-
|
|
1360
|
-
```tsx
|
|
1361
|
-
const wordpressConfig = {
|
|
1362
|
-
site: 'your-blog.wordpress.com', // WordPress site URL
|
|
1363
|
-
apiVersion: '1.1' // API version (optional)
|
|
1364
|
-
};
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
### Contentful Configuration
|
|
1368
|
-
|
|
1369
|
-
```tsx
|
|
1370
|
-
const contentfulConfig = {
|
|
1371
|
-
spaceId: 'your-space-id', // Contentful space ID
|
|
1372
|
-
accessToken: 'your-access-token', // Contentful access token
|
|
1373
|
-
environment: 'master' // Contentful environment
|
|
1374
|
-
};
|
|
1375
|
-
```
|
|
1376
|
-
|
|
1377
|
-
### Other Service Configurations
|
|
1378
|
-
|
|
1379
|
-
```tsx
|
|
1380
|
-
const config = {
|
|
1381
|
-
calendly: {
|
|
1382
|
-
username: 'your-calendly-username'
|
|
1383
|
-
},
|
|
1384
|
-
hubspot: {
|
|
1385
|
-
portalId: 'your-portal-id',
|
|
1386
|
-
formId: 'your-form-id'
|
|
1387
|
-
},
|
|
1388
|
-
instagram: {
|
|
1389
|
-
accessToken: 'your-access-token'
|
|
1390
|
-
}
|
|
1391
|
-
};
|
|
1392
|
-
```
|
|
1393
|
-
|
|
1394
1492
|
---
|
|
1395
1493
|
|
|
1396
1494
|
## TypeScript Support
|
package/README.md
CHANGED
|
@@ -168,106 +168,11 @@ npm run storybook
|
|
|
168
168
|
**Access locally at:** `http://localhost:6006`
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
## 🧪 Testing
|
|
173
|
-
|
|
174
|
-
### Overview
|
|
175
|
-
|
|
176
|
-
**Current Status**: ✅ 2,184 tests passing across 59 test files (7 skipped)
|
|
177
|
-
|
|
178
|
-
| Metric | Value |
|
|
179
|
-
|--------|-------|
|
|
180
|
-
| Test Files | 59 |
|
|
181
|
-
| Total Tests | 2,184 |
|
|
182
|
-
| Skipped Tests | 7 |
|
|
183
|
-
| Coverage (Statements) | 79.26% |
|
|
184
|
-
| Coverage (Lines) | 82.73% |
|
|
185
|
-
| Coverage (Functions) | 84.73% |
|
|
186
|
-
| Coverage (Branches) | 67.19% |
|
|
187
|
-
| Test Framework | Vitest 4.x |
|
|
188
|
-
| Testing Library | @testing-library/react + jsdom |
|
|
189
|
-
|
|
190
|
-
### Quick Start
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
npm run test # Watch mode
|
|
194
|
-
npm run test:ui # Interactive UI dashboard
|
|
195
|
-
npm run test:coverage # Generate coverage reports
|
|
196
|
-
npm run test:run # Single run (for CI)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Component Coverage
|
|
200
|
-
|
|
201
|
-
**52 of 52 Frontend Components + 2 Utility Modules Fully Tested (100%)**
|
|
202
|
-
|
|
203
|
-
#### Component Coverage (Sorted by Statement Coverage)
|
|
204
|
-
- **sitemap.ts**: 100% statements
|
|
205
|
-
- **google.reviews.functions.ts**: 100% statements
|
|
206
|
-
- **googlesearch.tsx**: 100% statements
|
|
207
|
-
- **formvalidations.tsx**: 100% statements
|
|
208
|
-
- **tiles.tsx**: 100% statements
|
|
209
|
-
- **markdown.tsx**: 100% statements
|
|
210
|
-
- **buzzwordbingo.tsx**: 100% statements
|
|
211
|
-
- **timeline.tsx**: 100% statements
|
|
212
|
-
- **config.server.tsx**: 100% statements
|
|
213
|
-
- **modal.tsx**: 100% statements
|
|
214
|
-
- **google.reviews.components.tsx**: 100% statements
|
|
215
|
-
- **recipe.tsx**: 98.8% statements
|
|
216
|
-
- **sidepanel.tsx**: 97.5% statements
|
|
217
|
-
- **resume.tsx**: 94.38% statements
|
|
218
|
-
- **callout.tsx**: 93.75% statements
|
|
219
|
-
- **contentful.delivery.ts**: 92.5% statements
|
|
220
|
-
- **css.tsx**: 91.42% statements
|
|
221
|
-
- **functions.ts**: 90.9% statements
|
|
222
|
-
- **config.client.tsx**: 90% statements
|
|
223
|
-
- **api.ts**: 87.5% statements
|
|
224
|
-
- **loading.tsx**: 85.71% statements
|
|
225
|
-
- **table.tsx**: 84.48% statements
|
|
226
|
-
- **cloudinary.ts**: 83.33% statements
|
|
227
|
-
- **shoppingcart.functions.ts**: 81.69% statements
|
|
228
|
-
- **carousel.tsx**: 76.19% statements
|
|
229
|
-
- **nerdjoke.tsx**: 70.58% statements
|
|
230
|
-
- **menu-accordion.tsx**: 68.13% statements
|
|
231
|
-
- **carousel.tsx**: 58.49% statements
|
|
232
|
-
- **config.ts**: 55.17% statements
|
|
233
|
-
|
|
234
|
-
### Test Configuration
|
|
235
|
-
|
|
236
|
-
**Coverage Summary (latest run)**:
|
|
237
|
-
- **Statements**: 79.26%
|
|
238
|
-
- **Lines**: 82.73%
|
|
239
|
-
- **Functions**: 84.73%
|
|
240
|
-
- **Branches**: 67.19%
|
|
241
|
-
|
|
242
|
-
**Coverage Targets** (configured in `vitest.config.ts`):
|
|
243
|
-
- **Statements**: 70% threshold
|
|
244
|
-
- **Lines**: 70% threshold
|
|
245
|
-
- **Functions**: 70% threshold
|
|
246
|
-
- **Branches**: 60% threshold
|
|
247
|
-
|
|
248
|
-
**Coverage Thresholds in vitest.config.ts**:
|
|
249
|
-
- Lines: 70% threshold
|
|
250
|
-
- Functions: 70% threshold
|
|
251
|
-
- Branches: 60% threshold
|
|
252
|
-
- Statements: 70% threshold
|
|
253
|
-
|
|
254
|
-
**Test Environment**: jsdom with @testing-library/react
|
|
255
|
-
**Test Pattern**: Data-focused validation + behavioral testing
|
|
256
|
-
|
|
257
|
-
### Tools & Dependencies
|
|
258
|
-
|
|
259
|
-
| Tool | Purpose |
|
|
260
|
-
|------|---------|
|
|
261
|
-
| Vitest 4.x | Test runner |
|
|
262
|
-
| @testing-library/react | Component testing utilities |
|
|
263
|
-
| jsdom | DOM environment for tests |
|
|
264
|
-
| v8 | Coverage reporting |
|
|
265
|
-
|
|
266
|
-
|
|
267
171
|
<!-- ROADMAP -->
|
|
268
172
|
## Roadmap
|
|
269
173
|
|
|
270
174
|
### New Components
|
|
175
|
+
- [ ] **IN PROGRESS** - Testimonial Block (Nextdoor/Yelp/Google): ingest review feeds + render carousel/grid.
|
|
271
176
|
- [ ] **ON HOLD** LinkedIn Recommendations Integration (Not possible with current LinkedIn API)
|
|
272
177
|
- [ ] **ON HOLD** eBay Feedback Integration - requires user OAuth login
|
|
273
178
|
- [ ] **ON HOLD** Yelp Recommendations integration (Cost Prohibitive)
|
|
@@ -277,7 +182,8 @@ npm run test:run # Single run (for CI)
|
|
|
277
182
|
- [ ] Buffer Integration (or Sendible, Sprout Social, Hootsuite)
|
|
278
183
|
- [ ] Zapier Integration
|
|
279
184
|
- [ ] Hero Banner: headline, subtext, CTA, background image/video, overlay.
|
|
280
|
-
- [ ]
|
|
185
|
+
- [ ] Accessibility Enhancer: wrapper component that automatically improves accessibility across Pixelated sites by adding ARIA labels, roles, and states to existing components. Includes color contrast checking, keyboard navigation helpers, and alt-text suggestions for images.
|
|
186
|
+
- [ ] SEO Dashboard with AI Integration: component that analyzes site content, suggests optimizations, integrates with AI for meta descriptions and keyword research.
|
|
281
187
|
|
|
282
188
|
### CI / CD Improvements
|
|
283
189
|
- [ ] Add CI workflow to run tests and lints on pull requests.
|
|
@@ -298,8 +204,15 @@ npm run test:run # Single run (for CI)
|
|
|
298
204
|
- [ ] **GoogleReviews Component**: Add API key to config provider instead of hardcoding.
|
|
299
205
|
- [ ] **Instagram Component**: Add accessToken and userId to config provider for centralized API credentials.
|
|
300
206
|
|
|
301
|
-
|
|
302
|
-
|
|
207
|
+
### SSR Fixes
|
|
208
|
+
- [ ] **cloudinary.image.tsx** (`SmartImage`): Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
209
|
+
- [ ] **wordpress.components.tsx** (`BlogPostList`, etc.): Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
210
|
+
- [ ] **pagebuilder/form/formcomponents.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
211
|
+
- [ ] **cms/hubspot.components.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
212
|
+
- [ ] **cms/gravatar.components.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
213
|
+
- [ ] **structured/recipe.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
214
|
+
- [ ] **structured/timeline.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
215
|
+
- [ ] **structured/markdown.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
|
|
303
216
|
|
|
304
217
|
See the [open issues](https://github.com/brianwhaley/pixelated-components/issues) for a full list of proposed features (and known issues).
|
|
305
218
|
|
|
@@ -344,7 +257,7 @@ Distributed under the MIT License. See `LICENSE.txt` for more information.
|
|
|
344
257
|
<!-- CONTACT -->
|
|
345
258
|
## Contact
|
|
346
259
|
|
|
347
|
-
|
|
260
|
+
Brian Whaley - [@brianwhaley](https://twitter.com/@brianwhaley) - brian.whaley@gmail.com
|
|
348
261
|
|
|
349
262
|
Project Link: [https://github.com/brianwhaley/pixelated-components](https://github.com/brianwhaley/pixelated-components)
|
|
350
263
|
|
|
@@ -353,6 +266,123 @@ Project Link: [https://github.com/brianwhaley/pixelated-components](https://gith
|
|
|
353
266
|
|
|
354
267
|
|
|
355
268
|
|
|
269
|
+
## 🧪 Testing
|
|
270
|
+
|
|
271
|
+
### Overview
|
|
272
|
+
|
|
273
|
+
**Current Status**: ✅ 2,210 tests passing across 65 test files
|
|
274
|
+
|
|
275
|
+
| Metric | Value |
|
|
276
|
+
|--------|-------|
|
|
277
|
+
| Test Files | 65 |
|
|
278
|
+
| Total Tests | 2,210 |
|
|
279
|
+
| Coverage (Statements) | 77.92% |
|
|
280
|
+
| Coverage (Lines) | 81.09% |
|
|
281
|
+
| Coverage (Functions) | 81.27% |
|
|
282
|
+
| Coverage (Branches) | 67.31% |
|
|
283
|
+
| Test Framework | Vitest 4.x |
|
|
284
|
+
| Testing Library | @testing-library/react + jsdom |
|
|
285
|
+
|
|
286
|
+
### Quick Start
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
npm run test # Watch mode
|
|
290
|
+
npm run test:ui # Interactive UI dashboard
|
|
291
|
+
npm run test:coverage # Generate coverage reports
|
|
292
|
+
npm run test:run # Single run (for CI)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Component Coverage
|
|
296
|
+
|
|
297
|
+
**Component Coverage Summary**
|
|
298
|
+
|
|
299
|
+
#### Component Coverage (Sorted by Statement Coverage)
|
|
300
|
+
- **config.server.tsx**: 100% statements
|
|
301
|
+
- **modal.tsx**: 100% statements
|
|
302
|
+
- **accordion.tsx**: 100% statements
|
|
303
|
+
- **tiles.tsx**: 100% statements
|
|
304
|
+
- **googlesearch.tsx**: 100% statements
|
|
305
|
+
- **formvalidations.tsx**: 100% statements
|
|
306
|
+
- **buzzwordbingo.tsx**: 100% statements
|
|
307
|
+
- **timeline.tsx**: 100% statements
|
|
308
|
+
- **markdown.tsx**: 100% statements
|
|
309
|
+
- **ComponentPropertiesForm.tsx**: 100% statements
|
|
310
|
+
- **ComponentSelector.tsx**: 100% statements
|
|
311
|
+
- **ComponentTree.tsx**: 100% statements
|
|
312
|
+
- **schema-localbusiness.tsx**: 100% statements
|
|
313
|
+
- **schema-recipe.tsx**: 100% statements
|
|
314
|
+
- **schema-services.tsx**: 100% statements
|
|
315
|
+
- **schema-website.tsx**: 100% statements
|
|
316
|
+
- **google.reviews.functions.ts**: 100% statements
|
|
317
|
+
- **sidepanel.tsx**: 97.5% statements
|
|
318
|
+
- **config.ts**: 96.55% statements
|
|
319
|
+
- **google.reviews.components.tsx**: 95.83% statements
|
|
320
|
+
- **schema-blogposting.tsx**: 95.23% statements
|
|
321
|
+
- **recipe.tsx**: 94.59% statements
|
|
322
|
+
- **resume.tsx**: 94.38% statements
|
|
323
|
+
- **contentful.delivery.ts**: 92.5% statements
|
|
324
|
+
- **css.tsx**: 91.42% statements
|
|
325
|
+
- **functions.ts**: 90.9% statements
|
|
326
|
+
- **config.client.tsx**: 90% statements
|
|
327
|
+
- **menu-expando.tsx**: 90.12% statements
|
|
328
|
+
- **cloudinary.ts**: 83.33% statements
|
|
329
|
+
- **form.tsx**: 83.2% statements
|
|
330
|
+
- **shoppingcart.functions.ts**: 81.69% statements
|
|
331
|
+
- **callout.tsx**: 80% statements
|
|
332
|
+
- **microinteractions.tsx**: 80% statements
|
|
333
|
+
- **sitemap.ts**: 76.05% statements
|
|
334
|
+
- **manifest.tsx**: 75% statements
|
|
335
|
+
- **carousel.tsx**: 71.69% statements
|
|
336
|
+
- **nerdjoke.tsx**: 69.44% statements
|
|
337
|
+
- **menu-accordion.tsx**: 68.13% statements
|
|
338
|
+
- **semantic.tsx**: 63.51% statements
|
|
339
|
+
- **flickr.ts**: 51.42% statements
|
|
340
|
+
- **PageEngine.tsx**: 48% statements
|
|
341
|
+
- **SaveLoadSection.tsx**: 84.84% statements
|
|
342
|
+
- **table.tsx**: 84.48% statements
|
|
343
|
+
- **loading.tsx**: 85.71% statements
|
|
344
|
+
- **socialcard.tsx**: 29.5% statements
|
|
345
|
+
- **PageBuilderUI.tsx**: 26.66% statements
|
|
346
|
+
|
|
347
|
+
### Testing Next Steps
|
|
348
|
+
|
|
349
|
+
#### Integration Testing Gaps
|
|
350
|
+
- [ ] **Cross-component interactions** - Test how components work together (e.g., forms with validation, carousels with loading states)
|
|
351
|
+
- [ ] **Form validation edge cases** - Test URL validation, required fields, and complex validation rules under various conditions
|
|
352
|
+
- [ ] **CMS API integrations** - Test API failures, rate limiting, authentication errors, and network timeouts
|
|
353
|
+
- [ ] **Responsive design breakpoints** - Test component behavior across different screen sizes and device types
|
|
354
|
+
- [ ] **Accessibility (a11y) compliance** - Test keyboard navigation, screen reader compatibility, and ARIA attributes
|
|
355
|
+
|
|
356
|
+
### Test Configuration
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
**Coverage Targets** (configured in `vitest.config.ts`):
|
|
360
|
+
- **Statements**: 70% threshold
|
|
361
|
+
- **Lines**: 70% threshold
|
|
362
|
+
- **Functions**: 70% threshold
|
|
363
|
+
- **Branches**: 60% threshold
|
|
364
|
+
|
|
365
|
+
**Coverage Thresholds in vitest.config.ts**:
|
|
366
|
+
- Lines: 70% threshold
|
|
367
|
+
- Functions: 70% threshold
|
|
368
|
+
- Branches: 60% threshold
|
|
369
|
+
- Statements: 70% threshold
|
|
370
|
+
|
|
371
|
+
**Test Environment**: jsdom with @testing-library/react
|
|
372
|
+
**Test Pattern**: Data-focused validation + behavioral testing
|
|
373
|
+
|
|
374
|
+
### Tools & Dependencies
|
|
375
|
+
|
|
376
|
+
| Tool | Purpose |
|
|
377
|
+
|------|---------|
|
|
378
|
+
| Vitest 4.x | Test runner |
|
|
379
|
+
| @testing-library/react | Component testing utilities |
|
|
380
|
+
| jsdom | DOM environment for tests |
|
|
381
|
+
| v8 | Coverage reporting |
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
356
386
|
|
|
357
387
|
<!-- MARKDOWN LINKS & IMAGES -->
|
|
358
388
|
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
|
-
import { generateURL } from '../utilities/api';
|
|
3
2
|
import { mergeDeep } from '../utilities/functions';
|
|
4
3
|
const defaultFlickr = {
|
|
5
4
|
flickr: {
|
|
@@ -57,7 +56,14 @@ export function GetFlickrData(props) {
|
|
|
57
56
|
flickrConfig = mergeDeep(flickrConfig, props.flickr);
|
|
58
57
|
}
|
|
59
58
|
const flickr = flickrConfig;
|
|
60
|
-
|
|
59
|
+
// Build URL with query parameters
|
|
60
|
+
let myURL = flickr.baseURL;
|
|
61
|
+
let queryParams = '';
|
|
62
|
+
Object.keys(flickr.urlProps).forEach((prop) => {
|
|
63
|
+
const value = flickr.urlProps[prop];
|
|
64
|
+
queryParams += (queryParams.length === 0) ? prop + '=' + value : '&' + prop + '=' + value;
|
|
65
|
+
});
|
|
66
|
+
myURL += queryParams;
|
|
61
67
|
const fetchFlickrData = async () => {
|
|
62
68
|
try {
|
|
63
69
|
const response = await fetch(myURL);
|
|
@@ -50,5 +50,5 @@ export function GoogleReviewsCard(props) {
|
|
|
50
50
|
if (error) {
|
|
51
51
|
return (_jsx("div", { className: "google-reviews-card", children: _jsxs("p", { className: "error", children: ["Error: ", error] }) }));
|
|
52
52
|
}
|
|
53
|
-
return (_jsxs("div", { className: "google-reviews-card", children: [_jsx("h3", { children: place?.name || 'Reviews' }), place?.formatted_address && (_jsx("p", { className: "address", children: place.formatted_address })), reviews.length === 0 ? (_jsx("p", { className: "no-reviews", children: "No reviews found." })) : (_jsx("ul", { children: reviews.map((r, i) => (_jsxs("li", { children: [_jsxs("div", { className: "review-header", children: [r.profile_photo_url && (_jsx("img", { src: r.profile_photo_url, alt: r.author_name, className: "profile-photo" })), _jsxs("div", { children: [_jsx("div", { className: "author-name", children: r.author_name }), _jsxs("div", { className: "rating", children: ['★'.repeat(r.rating), '☆'.repeat(5 - r.rating), " ", r.rating, "/5", r.relative_time_description && _jsxs("span", { children: [" \u00B7 ", r.relative_time_description] })] })] })] }), r.text && _jsx("div", { className: "review-text", children: r.text })] }, i))) })), place && (_jsx("a", { href: `https://search.google.com/local/writereview?placeid=${place.place_id}`, target: "_blank", rel: "noopener noreferrer", className: "write-review", children: "Write a review on Google \u2192" }))] }));
|
|
53
|
+
return (_jsxs("div", { className: "google-reviews-card", children: [_jsx("h3", { children: place?.name || 'Reviews' }), place?.formatted_address && (_jsx("p", { className: "address", children: place.formatted_address })), reviews.length === 0 ? (_jsx("p", { className: "no-reviews", children: "No reviews found." })) : (_jsx("ul", { children: reviews.map((r, i) => (_jsxs("li", { children: [_jsxs("div", { className: "review-header", children: [r.profile_photo_url && (_jsx("div", { className: "profile-photo-container", children: _jsx("img", { src: r.profile_photo_url, alt: r.author_name, className: "profile-photo" }) })), _jsxs("div", { children: [_jsx("div", { className: "author-name", children: r.author_name }), _jsxs("div", { className: "rating", children: ['★'.repeat(r.rating), '☆'.repeat(5 - r.rating), " ", r.rating, "/5", r.relative_time_description && _jsxs("span", { children: [" \u00B7 ", r.relative_time_description] })] })] })] }), r.text && _jsx("div", { className: "review-text", children: r.text })] }, i))) })), place && (_jsx("a", { href: `https://search.google.com/local/writereview?placeid=${place.place_id}`, target: "_blank", rel: "noopener noreferrer", className: "write-review", children: "Write a review on Google \u2192" }))] }));
|
|
54
54
|
}
|
|
@@ -45,7 +45,7 @@ export function BlogPostSummary(props) {
|
|
|
45
45
|
const myCategoryImages = Object.entries(props.categories).map(([category, index]) => [category.trim().toLowerCase().replace(/[ /]+/g, '-'), index]).sort();
|
|
46
46
|
const config = usePixelatedConfig();
|
|
47
47
|
const myExcerpt = decodeString(props.excerpt).replace(/\[…\]/g, '<a href="' + props.URL + '" target="_blank" rel="noopener noreferrer">[…]</a>');
|
|
48
|
-
return (_jsx("div", { className: "blog-post-summary", children: _jsxs("article", { className: "h-entry", children: [_jsx("h2", { className: "p-name", children: _jsx("a", { className: "u-url blog-post-url", href: props.URL, target: "_blank", rel: "noopener noreferrer", children: decodeString(props.title) }) }), _jsxs("div", { className: "dt-published", children: ["Published: ", new Date(props.date).toLocaleDateString()] }), props.featured_image ? (_jsxs("div", { className: "article-body row-12col", children: [_jsx("div", { className: "article-featured-image grid-s1-e4", children: _jsx(SmartImage, { className: "u-photo", src: props.featured_image, alt: decodeString(props.title), title: decodeString(props.title), style: {
|
|
48
|
+
return (_jsx("div", { className: "blog-post-summary", children: _jsxs("article", { className: "h-entry", children: [_jsx("h2", { className: "p-name", children: _jsx("a", { className: "u-url blog-post-url", href: props.URL, target: "_blank", rel: "noopener noreferrer", children: decodeString(props.title) }) }), _jsxs("div", { className: "dt-published", children: ["Published: ", new Date(props.date).toLocaleDateString()] }), props.featured_image ? (_jsxs("div", { className: "article-body row-12col", children: [_jsx("div", { className: "article-featured-image grid-s1-e4", children: _jsx(SmartImage, { className: "u-photo", src: props.featured_image, alt: decodeString(props.title), title: decodeString(props.title), style: {}, cloudinaryEnv: config?.cloudinary?.product_env ?? undefined, cloudinaryDomain: config?.cloudinary?.baseUrl ?? undefined, cloudinaryTransforms: config?.cloudinary?.transforms ?? undefined }) }), _jsx("div", { className: "article-excerpt grid-s4-e13", children: _jsx("div", { className: "p-summary", dangerouslySetInnerHTML: { __html: myExcerpt } }) })] })) :
|
|
49
49
|
_jsx("div", { className: "article-excerpt grid-s1-e13", children: _jsx("div", { className: "p-summary", dangerouslySetInnerHTML: { __html: myExcerpt } }) }), props.showCategories !== false && (_jsxs("div", { children: ["Categories:", myCategoryImages.map(([categoryImg, index]) => (_jsx("span", { className: "p-category", children: _jsx(SmartImage, { src: `/images/icons/${categoryImg}.png`, title: String(categoryImg), alt: String(categoryImg), cloudinaryEnv: config?.cloudinary?.product_env ?? undefined, cloudinaryDomain: config?.cloudinary?.baseUrl ?? undefined, cloudinaryTransforms: config?.cloudinary?.transforms ?? undefined }) }, categoryImg + "-" + index)))] }))] }) }, props.ID));
|
|
50
50
|
}
|
|
51
51
|
export function BlogPostCategories(props) {
|
|
@@ -136,6 +136,12 @@ export function MenuExpandoButton() {
|
|
|
136
136
|
if (details)
|
|
137
137
|
details.open = !details.open;
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
function handleKeyDown(event) {
|
|
140
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
handleMenuExpandoButtonClick(event);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return (_jsx("div", { className: "menuExpandoButton", id: "menuExpandoButton", onClick: handleMenuExpandoButtonClick, onKeyDown: handleKeyDown, tabIndex: 0, role: "button", "aria-label": "Toggle mobile menu", children: _jsx("img", { src: "/images/icons/mobile-menu2.png", title: "Mobile Menu", alt: "Mobile Menu" }) }));
|
|
140
146
|
}
|
|
141
147
|
MenuExpandoButton.propTypes = {};
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
|
-
import { getXHRData, generateURL } from "../utilities/api";
|
|
6
5
|
import "../../css/pixelated.grid.scss";
|
|
7
6
|
import "./nerdjoke.css";
|
|
8
7
|
const debug = false;
|
|
@@ -36,7 +35,7 @@ export function NerdJoke( /* props: NerdJokeType */) {
|
|
|
36
35
|
elapsedPath.style.width = myWidth;
|
|
37
36
|
}
|
|
38
37
|
}, [formatTimeLeft]);
|
|
39
|
-
const loadJoke = useCallback(() => {
|
|
38
|
+
const loadJoke = useCallback(async () => {
|
|
40
39
|
if (debug)
|
|
41
40
|
console.log("Loading Joke");
|
|
42
41
|
timePassedRef.current = 0;
|
|
@@ -52,11 +51,18 @@ export function NerdJoke( /* props: NerdJokeType */) {
|
|
|
52
51
|
}, TIME_LIMIT * 1000);
|
|
53
52
|
const myURL = "https://vvqyc1xpw6.execute-api.us-east-2.amazonaws.com/prod/nerdjokes?";
|
|
54
53
|
const myURLProps = { command: "%2Fnerdjokes", text: "getjokejson" };
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
try {
|
|
55
|
+
const url = myURL + "command=" + myURLProps.command + "&text=" + myURLProps.text;
|
|
56
|
+
const response = await fetch(url);
|
|
57
|
+
if (!response.ok)
|
|
58
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
59
|
+
const jokeData = await response.json();
|
|
60
|
+
setJoke(jokeData);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('Failed to fetch joke:', error);
|
|
64
|
+
// Optionally set a fallback joke or handle error
|
|
65
|
+
}
|
|
60
66
|
}, []);
|
|
61
67
|
const startTimer = useCallback(() => {
|
|
62
68
|
if (debug)
|
|
@@ -7,7 +7,7 @@ import { FormEngine } from '../form/form';
|
|
|
7
7
|
* Shows FormEngine when component is selected, placeholder otherwise
|
|
8
8
|
*/
|
|
9
9
|
ComponentPropertiesForm.propTypes = {
|
|
10
|
-
editableComponent: PropTypes.object
|
|
10
|
+
editableComponent: PropTypes.object,
|
|
11
11
|
onSubmit: PropTypes.func.isRequired,
|
|
12
12
|
};
|
|
13
13
|
export function ComponentPropertiesForm({ editableComponent, onSubmit }) {
|