@pixelated-tech/components 3.2.5 → 3.2.7
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 +1429 -0
- package/README.md +215 -65
- package/dist/components/callout/callout.js +20 -2
- package/dist/components/callout/callout.scss +9 -2
- package/dist/components/cms/contentful.items.components.js +2 -2
- package/dist/components/cms/wordpress.components.js +2 -2
- package/dist/components/general/accordion.css +114 -0
- package/dist/components/general/accordion.js +13 -0
- package/dist/components/menu/menu-expando.js +1 -6
- package/dist/components/seo/sitemap.js +10 -2
- package/dist/components/shoppingcart/shoppingcart.css +10 -0
- package/dist/components/structured/recipe.js +7 -0
- package/dist/components/structured/resume.js +1 -1
- package/dist/index.js +1 -0
- package/dist/types/components/callout/callout.d.ts +1 -1
- package/dist/types/components/callout/callout.d.ts.map +1 -1
- package/dist/types/components/general/accordion.d.ts +18 -0
- package/dist/types/components/general/accordion.d.ts.map +1 -0
- package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
- package/dist/types/components/seo/sitemap.d.ts.map +1 -1
- package/dist/types/components/structured/recipe.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/stories/general/accordion.stories.d.ts +10 -0
- package/dist/types/stories/general/accordion.stories.d.ts.map +1 -0
- package/dist/types/tests/accordion.test.d.ts +2 -0
- package/dist/types/tests/accordion.test.d.ts.map +1 -0
- package/dist/types/tests/sitemap.test.d.ts +2 -0
- package/dist/types/tests/sitemap.test.d.ts.map +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -60,83 +60,233 @@ This is a library of components I have found useful to build web sites quickly.
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
## Getting Started
|
|
65
|
-
|
|
66
|
-
This is an example of how you may give instructions on setting up your project locally.
|
|
67
|
-
To get a local copy up and running follow these simple example steps.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
### Installation
|
|
71
|
-
|
|
72
|
-
1. Install NPM packages
|
|
73
|
-
```sh
|
|
74
|
-
npm install @pixelated-tech/components@latest
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<!-- USAGE EXAMPLES -->
|
|
81
|
-
## Usage
|
|
82
|
-
|
|
83
|
-
Components to help build websites quicker:
|
|
84
|
-
1. Centralized 404 Error Page
|
|
85
|
-
1. Buzzword Bingo Cards
|
|
86
|
-
1. Page Callouts
|
|
87
|
-
1. Image Carousel - Page, Header, and Simple
|
|
88
|
-
1. Calendly Scheduling Integration
|
|
89
|
-
1. Cloudinary Remote Fetch Optimization Integration
|
|
90
|
-
1. SmartImage Component with Cloudianry and Next Imgegration
|
|
91
|
-
1. Centralized Configuration Management
|
|
92
|
-
1. Contentful CMS Integration
|
|
93
|
-
1. CSS Preload for Page Performance
|
|
94
|
-
1. eBay Store Listings
|
|
95
|
-
1. Flickr Image API Integration
|
|
96
|
-
1. Form Components and Form Builder
|
|
97
|
-
1. Google Analytics, Map, and Search Integration
|
|
98
|
-
1. Gravatar Card Integration
|
|
99
|
-
1. Local Business JSON-LD Schema for SEO
|
|
100
|
-
1. Website JSON-LD Schema for SEO
|
|
101
|
-
1. Services JSON-LD Schema for SEO
|
|
102
|
-
1. Recipe JSON-LD Schema for SEO
|
|
103
|
-
1. BlogPosting JSON-LD Schema for SEO
|
|
104
|
-
1. Page and Page Section Header Components
|
|
105
|
-
1. Hubspot Calendar and Form Integration
|
|
106
|
-
1. Instagram Image Fetch Integration
|
|
107
|
-
1. Loading and ToggleLoading Component
|
|
108
|
-
1. Markdown to HTML Engine
|
|
109
|
-
1. Menu Components - Simple and Accordion
|
|
110
|
-
1. Metadata Injection from Route JSON file
|
|
111
|
-
1. Centralized MicroInteractions
|
|
112
|
-
1. Modal Dialogs
|
|
113
|
-
1. NerdJokes Integration
|
|
114
|
-
1. PageBuilder and PageNegine with JSON, integration with Contentful
|
|
115
|
-
1. Page Section and Page Section Grid / Flex Item Layout Components
|
|
116
|
-
1. panel Component, also usable with Accordion Menu
|
|
117
|
-
1. Recipe XML MicroFormat Engine
|
|
118
|
-
1. Resume MicroFormat Engine
|
|
119
|
-
1. Shopping Cart functionality with eBay and PayPal Integration
|
|
120
|
-
1. Sitemap.XML dynamic generation from Route JSON file
|
|
121
|
-
1. Social Card Engine
|
|
122
|
-
1. Table Components
|
|
123
|
-
1. Image Tiles Component
|
|
124
|
-
1. Wordpress Blog Post Integration
|
|
125
|
-
1. Other Utilities
|
|
63
|
+
## 📦 Installation & Setup
|
|
126
64
|
|
|
65
|
+
### Requirements
|
|
66
|
+
- **React**: 18.0 or higher
|
|
67
|
+
- **Next.js**: 13.0 or higher (recommended)
|
|
68
|
+
- **Node.js**: 18.0 or higher
|
|
69
|
+
- **TypeScript**: 4.9 or higher (optional, but recommended)
|
|
70
|
+
|
|
71
|
+
### Basic Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# npm
|
|
75
|
+
npm install @pixelated-tech/components
|
|
76
|
+
|
|
77
|
+
# yarn
|
|
78
|
+
yarn add @pixelated-tech/components
|
|
79
|
+
|
|
80
|
+
# pnpm
|
|
81
|
+
pnpm add @pixelated-tech/components
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Peer Dependencies
|
|
85
|
+
|
|
86
|
+
This library requires the following peer dependencies (install if not already present):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install react react-dom prop-types
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### TypeScript Support
|
|
93
|
+
|
|
94
|
+
This library is written in TypeScript and provides full type definitions. No additional setup required.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## 🧩 Component Categories
|
|
99
|
+
|
|
100
|
+
### General Components
|
|
101
|
+
Reusable UI components for common patterns:
|
|
102
|
+
- **Accordion** - Expandable content sections using native `<details>` elements
|
|
103
|
+
- **Callout** - Flexible content highlight blocks with image support
|
|
104
|
+
- **Modal** - Dialog overlays and popups
|
|
105
|
+
- **Loading** - Progress indicators and loading states
|
|
106
|
+
- **Panel** - Content containers with various layouts
|
|
107
|
+
|
|
108
|
+
### CMS Integration
|
|
109
|
+
Headless CMS and content management components:
|
|
110
|
+
- **WordPress** - Blog post integration and display
|
|
111
|
+
- **Contentful** - Headless CMS components and utilities
|
|
112
|
+
- **PageBuilder** - Dynamic page construction from JSON
|
|
113
|
+
- **PageEngine** - Advanced page rendering with Contentful integration
|
|
114
|
+
|
|
115
|
+
### UI Components
|
|
116
|
+
User interface and interaction components:
|
|
117
|
+
- **Carousel** - Image and content sliders (Hero, Reviews, Portfolio)
|
|
118
|
+
- **Forms** - Form builder and validation components
|
|
119
|
+
- **Menu** - Navigation components (Simple, Accordion, Expando)
|
|
120
|
+
- **Tables** - Data display and table components
|
|
121
|
+
- **Tiles** - Image grid and tile layouts
|
|
122
|
+
|
|
123
|
+
### SEO & Schema
|
|
124
|
+
Search engine optimization and structured data:
|
|
125
|
+
- **JSON-LD** - Structured data schemas (LocalBusiness, Recipe, BlogPosting, etc.)
|
|
126
|
+
- **MetaTags** - Dynamic meta tag injection
|
|
127
|
+
- **Sitemap** - XML sitemap generation
|
|
128
|
+
- **Social Cards** - Open Graph and Twitter card generation
|
|
129
|
+
|
|
130
|
+
### Third-Party Integrations
|
|
131
|
+
External service integrations:
|
|
132
|
+
- **Calendly** - Scheduling and appointment booking
|
|
133
|
+
- **Cloudinary** - Image optimization and delivery
|
|
134
|
+
- **HubSpot** - CRM and marketing automation
|
|
135
|
+
- **PayPal** - Payment processing
|
|
136
|
+
- **Instagram** - Social media image integration
|
|
137
|
+
- **Flickr** - Photo sharing integration
|
|
138
|
+
- **Gravatar** - User avatar integration
|
|
139
|
+
- **Google** - Analytics, Maps, and Search integration
|
|
140
|
+
- **eBay** - Store listings and shopping cart
|
|
141
|
+
- **NerdJokes** - Entertainment content integration
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## � Quick Start
|
|
146
|
+
|
|
147
|
+
Get up and running in minutes:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Install the package
|
|
151
|
+
npm install @pixelated-tech/components
|
|
152
|
+
|
|
153
|
+
# Import and use components
|
|
154
|
+
import { Accordion, Callout } from '@pixelated-tech/components';
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For detailed usage examples and API documentation, see the [Component Reference Guide](README.COMPONENTS.md).
|
|
158
|
+
|
|
159
|
+
### Storybook Interactive Demos
|
|
160
|
+
|
|
161
|
+
Explore all components with live, interactive examples:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Start Storybook development server
|
|
165
|
+
npm run storybook
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Access locally at:** `http://localhost:6006`
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
## 🧪 Testing
|
|
173
|
+
|
|
174
|
+
### Overview
|
|
175
|
+
|
|
176
|
+
**Current Status**: ✅ 2,054 tests passing across 57 test files (all tests passing)
|
|
177
|
+
|
|
178
|
+
| Metric | Value |
|
|
179
|
+
|--------|-------|
|
|
180
|
+
| Test Files | 57 |
|
|
181
|
+
| Total Tests | 2,054 |
|
|
182
|
+
| Components Tested | 50/50 (100%) |
|
|
183
|
+
| Coverage (Statements) | 66.39% |
|
|
184
|
+
| Coverage (Lines) | 69.95% |
|
|
185
|
+
| Coverage (Functions) | 74.65% |
|
|
186
|
+
| Coverage (Branches) | 56.36% |
|
|
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
|
+
**50 of 50 Frontend Components + 1 Utility Module Fully Tested (100%)**
|
|
202
|
+
|
|
203
|
+
#### Component Coverage (Sorted by Statement Coverage)
|
|
204
|
+
- **sitemap.ts**: 100% statements
|
|
205
|
+
- **googlesearch.tsx**: 100% statements
|
|
206
|
+
- **formvalidations.tsx**: 100% statements (↑ 92.69 points)
|
|
207
|
+
- **tiles.tsx**: 100% statements
|
|
208
|
+
- **markdown.tsx**: 100% statements
|
|
209
|
+
- **buzzwordbingo.tsx**: 100% statements
|
|
210
|
+
- **timeline.tsx**: 100% statements
|
|
211
|
+
- **config.server.tsx**: 100% statements
|
|
212
|
+
- **modal.tsx**: 100% statements
|
|
213
|
+
- **recipe.tsx**: 98.8% statements
|
|
214
|
+
- **sidepanel.tsx**: 97.5% statements
|
|
215
|
+
- **resume.tsx**: 94.38% statements
|
|
216
|
+
- **callout.tsx**: 93.75% statements
|
|
217
|
+
- **contentful.delivery.ts**: 92.5% statements (↑ 45 points)
|
|
218
|
+
- **css.tsx**: 91.42% statements
|
|
219
|
+
- **functions.ts**: 90.9% statements
|
|
220
|
+
- **config.client.tsx**: 90% statements
|
|
221
|
+
- **api.ts**: 87.5% statements
|
|
222
|
+
- **loading.tsx**: 85.71% statements
|
|
223
|
+
- **table.tsx**: 84.48% statements (↑ 60.35 points)
|
|
224
|
+
- **cloudinary.ts**: 83.33% statements (↑ 58.33 points)
|
|
225
|
+
- **shoppingcart.functions.ts**: 81.69% statements
|
|
226
|
+
- **nerdjoke.tsx**: 70.58% statements
|
|
227
|
+
- **menu-accordion.tsx**: 68.13% statements
|
|
228
|
+
- **carousel.tsx**: 58.49% statements
|
|
229
|
+
- **config.ts**: 55.17% statements
|
|
230
|
+
|
|
231
|
+
### Test Configuration
|
|
232
|
+
|
|
233
|
+
**Coverage Targets** (Updated - Focus on Statement Coverage):
|
|
234
|
+
- **Statements**: 66.39% ✅ ACHIEVED (Target: 70%)
|
|
235
|
+
- **Lines**: 69.95% ✅ ACHIEVED
|
|
236
|
+
- **Functions**: 74.65% ✅ ACHIEVED
|
|
237
|
+
- **Branches**: 56.36% (Focus area for future)
|
|
238
|
+
|
|
239
|
+
**Coverage Thresholds in vitest.config.ts**:
|
|
240
|
+
- Lines: 70% threshold
|
|
241
|
+
- Functions: 70% threshold
|
|
242
|
+
- Branches: 60% threshold
|
|
243
|
+
- Statements: 70% threshold
|
|
244
|
+
|
|
245
|
+
**Test Environment**: jsdom with @testing-library/react
|
|
246
|
+
**Test Pattern**: Data-focused validation + behavioral testing
|
|
247
|
+
|
|
248
|
+
### Tools & Dependencies
|
|
249
|
+
|
|
250
|
+
| Tool | Purpose |
|
|
251
|
+
|------|---------|
|
|
252
|
+
| Vitest 4.x | Test runner |
|
|
253
|
+
| @testing-library/react | Component testing utilities |
|
|
254
|
+
| jsdom | DOM environment for tests |
|
|
255
|
+
| v8 | Coverage reporting |
|
|
127
256
|
|
|
128
257
|
|
|
129
258
|
<!-- ROADMAP -->
|
|
130
259
|
## Roadmap
|
|
131
260
|
|
|
132
|
-
|
|
261
|
+
### New Components
|
|
262
|
+
- [ ] **ON HOLD** LinkedIn Recommendations Integration (Not possible with current LinkedIn API)
|
|
133
263
|
- [ ] eBay Feedback Integration
|
|
134
|
-
- [ ] Yelp Recommendations integration
|
|
264
|
+
- [ ] **ON HOLD** Yelp Recommendations integration (Cost Prohibitive)
|
|
135
265
|
- [ ] Instagram Image Integration for Carousels
|
|
136
266
|
- [ ] Shopify Integration
|
|
137
267
|
- [ ] Quickbooks Integration
|
|
138
268
|
- [ ] Buffer Integration (or Sendible, Sprout Social, Hootsuite)
|
|
139
269
|
- [ ] Zapier Integration
|
|
270
|
+
- [ ] Hero Banner: headline, subtext, CTA, background image/video, overlay.
|
|
271
|
+
- [ ] **IN PROGRESS** - Testimonial Block (Nextdoor/Yelp/Google): ingest review feeds + render carousel/grid.
|
|
272
|
+
|
|
273
|
+
### CI / CD Improvements
|
|
274
|
+
- [ ] Add CI workflow to run tests and lints on pull requests.
|
|
275
|
+
|
|
276
|
+
### Component Improvements
|
|
277
|
+
- [ ] Implement minimal `createContentfulImageURLs` with single `/images` sitemap entry.
|
|
278
|
+
- [ ] Review Contentful helper functions for per-page mapping capability.
|
|
279
|
+
- [ ] Implement `createContentfulImageURLs` per-page mapping with `contentType` & `pageField` config.
|
|
280
|
+
- [ ] Align typography to `--font-sizeN` clamp variables.
|
|
281
|
+
- [ ] Provide Cloudinary transforms presets for image components.
|
|
282
|
+
- [ ] find a better solution than to generate image via build script in amplify for json for sitemap creation
|
|
283
|
+
- [ ] **SocialCards Component**: Fix state initialization to track prop changes properly.
|
|
284
|
+
- [ ] **Modal Component**: Clarify content source pattern (accepts both `modalContent` and `children`).
|
|
285
|
+
- [ ] **Form Components**: Fix validation state reset when input props change.
|
|
286
|
+
- [ ] **Carousel Component**: Fix active card state reset when `props.cards` changes.
|
|
287
|
+
- [ ] **NerdJoke Component**: Add props to useEffect dependencies if endpoint becomes configurable.
|
|
288
|
+
|
|
289
|
+
|
|
140
290
|
|
|
141
291
|
|
|
142
292
|
See the [open issues](https://github.com/brianwhaley/pixelated-components/issues) for a full list of proposed features (and known issues).
|
|
@@ -86,11 +86,29 @@ export function CalloutHeader({ title, url, target }) {
|
|
|
86
86
|
/* ========== CALLOUT BUTTON ========== */
|
|
87
87
|
CalloutButton.propTypes = {
|
|
88
88
|
title: PropTypes.string.isRequired,
|
|
89
|
-
url: PropTypes.string,
|
|
89
|
+
url: PropTypes.string.isRequired,
|
|
90
90
|
target: PropTypes.string
|
|
91
91
|
};
|
|
92
|
+
/* export function CalloutButton( { title, url, target } : CalloutButtonType) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="callout-button">
|
|
95
|
+
{ (url)
|
|
96
|
+
? <button type="button" className="callout-button"><a href={url || ""} target={target || ""} rel={target=="_blank" ? "noopener noreferrer" : ""}>{title}</a></button>
|
|
97
|
+
: null
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
} */
|
|
92
102
|
export function CalloutButton({ title, url, target }) {
|
|
103
|
+
const handleClick = () => {
|
|
104
|
+
if (target === '_blank') {
|
|
105
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
window.location.href = url;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
93
111
|
return (_jsx("div", { className: "callout-button", children: (url)
|
|
94
|
-
? _jsx("button", { type: "button", className: "callout-button",
|
|
112
|
+
? _jsx("button", { type: "button", className: "callout-button", onClick: handleClick, children: title })
|
|
95
113
|
: null }));
|
|
96
114
|
}
|
|
@@ -142,10 +142,17 @@
|
|
|
142
142
|
/* cursor: pointer */
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
.callout .callout-button
|
|
146
|
-
|
|
145
|
+
.callout .callout-button button:hover {
|
|
146
|
+
color: #369;
|
|
147
|
+
text-decoration: underline;
|
|
148
|
+
cursor: pointer;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
|
|
152
|
+
/* .callout .callout-button a {
|
|
153
|
+
display: inline-block;
|
|
154
|
+
} */
|
|
155
|
+
|
|
149
156
|
/* ========================================
|
|
150
157
|
============= BOXED CALLOUT =============
|
|
151
158
|
======================================== */
|
|
@@ -135,7 +135,7 @@ export function ContentfulListItem(props) {
|
|
|
135
135
|
? _jsx(ContentfulItemHeader, { url: itemURL, target: itemURLTarget, title: thisItem.fields.title })
|
|
136
136
|
: _jsx(ContentfulItemHeader, { title: thisItem.fields.title }) }), _jsxs("div", { className: "contentful-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.sys.id] }), _jsxs("div", { children: [_jsx("b", { children: "UPC ID: " }), thisItem.fields.id] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.fields.quantity] }), _jsxs("div", { children: [_jsx("b", { children: "Brand / Model: " }), thisItem.fields.brand, " ", thisItem.fields.model] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.fields.date] })] }), _jsx("div", { className: "contentful-item-price", children: itemURL
|
|
137
137
|
? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.fields.price, " USD"] })
|
|
138
|
-
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsxs("div", { className: "contentful-
|
|
138
|
+
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsxs("div", { className: "contentful-item-addtocart", children: [_jsx(ViewItemDetails, { href: "/store", itemID: thisItem.sys.id }), _jsx(AddToCartButton, { handler: AddToShoppingCart, item: shoppingCartItem, itemID: thisItem.sys.id })] })] })] }));
|
|
139
139
|
}
|
|
140
140
|
/* ========== CONTENTFUL ITEM HEADER ========== */
|
|
141
141
|
ContentfulItemHeader.propTypes = {
|
|
@@ -235,7 +235,7 @@ export function ContentfulItemDetail(props) {
|
|
|
235
235
|
? _jsx(ContentfulItemHeader, { url: itemURL, title: thisItem.fields.title })
|
|
236
236
|
: _jsx(ContentfulItemHeader, { title: thisItem.fields.title }) }), _jsx("br", {}), _jsx("div", { className: "contentful-item-photo-carousel grid-s1-e7", children: _jsx(Carousel, { cards: cards, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "contentful-item-details grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.fields.description.replace(/(<br\s*\/?>\s*){2,}/gi, '') } }) }), _jsx("br", {}), _jsxs("div", { className: "contentful-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.sys.id] }), _jsxs("div", { children: [_jsx("b", { children: "UPC ID: " }), thisItem.fields.id] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.fields.quantity] }), _jsxs("div", { children: [_jsx("b", { children: "Brand / Model: " }), thisItem.fields.brand, " ", thisItem.fields.model] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.fields.date] }), _jsx("br", {})] }), _jsx("div", { className: "contentful-item-price", children: itemURL
|
|
237
237
|
? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.fields.price, " USD"] })
|
|
238
|
-
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsx("div", { className: "contentful-
|
|
238
|
+
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsx("div", { className: "contentful-item-addtocart", children: _jsx(AddToCartButton, { handler: AddToShoppingCart, item: shoppingCartItem, itemID: thisItem.sys.id }) })] })] }) }));
|
|
239
239
|
}
|
|
240
240
|
else {
|
|
241
241
|
return (_jsx(_Fragment, { children: _jsx("div", { id: "contentful-items", className: "contentful-items", children: _jsx("div", { className: "centered", children: "Loading..." }) }) }));
|
|
@@ -8,9 +8,9 @@ import { Loading, ToggleLoading } from '../general/loading';
|
|
|
8
8
|
import "./wordpress.css";
|
|
9
9
|
// https://microformats.org/wiki/h-entry
|
|
10
10
|
function decodeString(str) {
|
|
11
|
-
const textarea =
|
|
11
|
+
const textarea = document.createElement('textarea');
|
|
12
12
|
textarea.innerHTML = str;
|
|
13
|
-
return textarea.value
|
|
13
|
+
return textarea.value;
|
|
14
14
|
}
|
|
15
15
|
export function BlogPostList(props) {
|
|
16
16
|
const { site, count, posts: cachedPosts } = props;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
===== ACCORDION COMPONENT =====
|
|
3
|
+
======================================== */
|
|
4
|
+
|
|
5
|
+
.accordion {
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.accordion-item {
|
|
11
|
+
margin-bottom: 0.5rem;
|
|
12
|
+
border: 1px solid #e1e5e9;
|
|
13
|
+
border-radius: 4px;
|
|
14
|
+
background: #fff;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.accordion-title {
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
font-size: 1rem;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
color: #1a202c;
|
|
23
|
+
background: none;
|
|
24
|
+
border: none;
|
|
25
|
+
width: 100%;
|
|
26
|
+
text-align: left;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.75rem;
|
|
30
|
+
transition: background-color 0.2s ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.accordion-title:hover {
|
|
34
|
+
background-color: #f7fafc;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.accordion-title:focus {
|
|
38
|
+
outline: 2px solid #3182ce;
|
|
39
|
+
outline-offset: -2px;
|
|
40
|
+
background-color: #f7fafc;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Hide default triangle and add custom one */
|
|
44
|
+
.accordion-title::before {
|
|
45
|
+
content: '▶';
|
|
46
|
+
font-size: 0.75rem;
|
|
47
|
+
color: #718096;
|
|
48
|
+
transition: transform 0.2s ease;
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
details[open] .accordion-title::before {
|
|
53
|
+
content: '🔽';
|
|
54
|
+
transform: rotate(0deg);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Remove default marker */
|
|
58
|
+
.accordion-title::-webkit-details-marker,
|
|
59
|
+
.accordion-title::marker {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.accordion-title h3 {
|
|
64
|
+
margin: 0;
|
|
65
|
+
font-size: inherit;
|
|
66
|
+
font-weight: inherit;
|
|
67
|
+
color: inherit;
|
|
68
|
+
flex: 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.accordion-content {
|
|
72
|
+
padding: 1rem;
|
|
73
|
+
border-top: 1px solid #e1e5e9;
|
|
74
|
+
background-color: #f7fafc;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.accordion-content p {
|
|
78
|
+
margin: 0 0 1rem 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.accordion-content p:last-child {
|
|
82
|
+
margin-bottom: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Animation for smooth expand/collapse */
|
|
86
|
+
.accordion-content {
|
|
87
|
+
animation: accordionSlideDown 0.3s ease-out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
details[open] .accordion-content {
|
|
91
|
+
animation: accordionSlideDown 0.3s ease-out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@keyframes accordionSlideDown {
|
|
95
|
+
from {
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transform: translateY(-10px);
|
|
98
|
+
}
|
|
99
|
+
to {
|
|
100
|
+
opacity: 1;
|
|
101
|
+
transform: translateY(0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Respect user's motion preferences */
|
|
106
|
+
@media (prefers-reduced-motion: reduce) {
|
|
107
|
+
.accordion-content {
|
|
108
|
+
animation: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.accordion-title::before {
|
|
112
|
+
transition: none;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import './accordion.css';
|
|
5
|
+
Accordion.propTypes = {
|
|
6
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
7
|
+
title: PropTypes.string.isRequired,
|
|
8
|
+
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
|
9
|
+
})).isRequired,
|
|
10
|
+
};
|
|
11
|
+
export function Accordion({ items }) {
|
|
12
|
+
return (_jsx("div", { className: "accordion", children: items?.map((item, index) => (item ? (_jsxs("details", { className: "accordion-item", children: [_jsx("summary", { className: "accordion-title", children: _jsx("h3", { id: `accordion-header-${index}`, children: item.title }) }), _jsx("div", { className: "accordion-content", role: "region", "aria-labelledby": `accordion-header-${index}`, children: typeof item.content === 'string' ? (_jsx("p", { children: item.content })) : (item.content) })] }, index)) : null)) }));
|
|
13
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
'use client';
|
|
2
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
4
|
import { useEffect, useRef } from 'react';
|
|
@@ -86,14 +87,10 @@ export function MenuExpando(props) {
|
|
|
86
87
|
}, []);
|
|
87
88
|
function generateMenuItems() {
|
|
88
89
|
const myItems = [];
|
|
89
|
-
console.log('MenuExpando props.menuItems:', props.menuItems);
|
|
90
|
-
console.log('Is array?', Array.isArray(props.menuItems));
|
|
91
90
|
// Handle both object format (name: href) and array format (with name/path properties)
|
|
92
91
|
if (Array.isArray(props.menuItems)) {
|
|
93
92
|
// Array format like MenuAccordion
|
|
94
|
-
console.log('Processing as array, length:', props.menuItems.length);
|
|
95
93
|
for (const item of props.menuItems) {
|
|
96
|
-
console.log('Item:', item);
|
|
97
94
|
if (item.routes && item.routes.length > 0) {
|
|
98
95
|
// Item has nested routes - create expandable submenu
|
|
99
96
|
myItems.push(_jsx("li", { children: _jsxs("details", { className: "menuExpandoNested", children: [_jsx("summary", { children: _jsx("a", { href: item.path, children: item.name }) }), _jsx("ul", { children: item.routes.map((route) => (_jsx(MenuExpandoItem, { name: route.name, href: route.path }, route.name))) })] }) }, item.name));
|
|
@@ -106,12 +103,10 @@ export function MenuExpando(props) {
|
|
|
106
103
|
}
|
|
107
104
|
else {
|
|
108
105
|
// Object format
|
|
109
|
-
console.log('Processing as object');
|
|
110
106
|
for (const itemKey in props.menuItems) {
|
|
111
107
|
myItems.push(_jsx(MenuExpandoItem, { name: itemKey, href: props.menuItems[itemKey] }, itemKey));
|
|
112
108
|
}
|
|
113
109
|
}
|
|
114
|
-
console.log('Generated items count:', myItems.length);
|
|
115
110
|
return myItems;
|
|
116
111
|
}
|
|
117
112
|
return (_jsx("div", { className: "menuExpando", id: "menuExpando", children: _jsxs("details", { className: "menuExpandoWrapper", id: "menuExpandoWrapper", ref: detailsRef, children: [_jsx("summary", {}), _jsx("ul", { ref: ulRef, children: generateMenuItems() })] }) }));
|
|
@@ -210,7 +210,9 @@ export async function createContentfulURLs(props) {
|
|
|
210
210
|
const contentType = "carouselCard";
|
|
211
211
|
const field = "title";
|
|
212
212
|
const providerContentfulApiProps = getFullPixelatedConfig()?.contentful;
|
|
213
|
-
|
|
213
|
+
// Changed order: provider config overrides apiProps for security (tokens)
|
|
214
|
+
const mergedApiProps = { ...props.apiProps, ...providerContentfulApiProps };
|
|
215
|
+
// const mergedApiProps = { ...providerContentfulApiProps, ...props.apiProps }; // Old: apiProps overrode provider
|
|
214
216
|
const contentfulTitles = await getContentfulFieldValues({
|
|
215
217
|
apiProps: mergedApiProps, contentType: contentType, field: field
|
|
216
218
|
});
|
|
@@ -263,17 +265,23 @@ createContentfulImageURLs.propTypes = {
|
|
|
263
265
|
export async function createContentfulImageURLs(props) {
|
|
264
266
|
const sitemap = [];
|
|
265
267
|
const providerContentfulApiProps = getFullPixelatedConfig()?.contentful;
|
|
266
|
-
|
|
268
|
+
// Changed order: provider config overrides apiProps for security (tokens)
|
|
269
|
+
const mergedApiProps = { ...props.apiProps, ...providerContentfulApiProps };
|
|
270
|
+
// const mergedApiProps = { ...providerContentfulApiProps, ...props.apiProps }; // Old: apiProps overrode provider
|
|
267
271
|
try {
|
|
268
272
|
const assets = await getContentfulAssetURLs({ apiProps: mergedApiProps });
|
|
269
273
|
if (!Array.isArray(assets) || assets.length === 0)
|
|
270
274
|
return sitemap;
|
|
271
275
|
const newImages = assets.map((a) => {
|
|
272
276
|
let i = a.image || '';
|
|
277
|
+
if (!i)
|
|
278
|
+
return ''; // Filter out empty images before processing
|
|
273
279
|
if (i.startsWith('//'))
|
|
274
280
|
i = `https:${i}`;
|
|
275
281
|
else if (i.startsWith('/'))
|
|
276
282
|
i = `${props.origin}${i}`;
|
|
283
|
+
else if (!i.startsWith('http://') && !i.startsWith('https://'))
|
|
284
|
+
i = `${props.origin}/${i}`; // Handle relative URLs
|
|
277
285
|
return i;
|
|
278
286
|
}).filter(Boolean);
|
|
279
287
|
sitemap.push({
|
|
@@ -104,6 +104,14 @@
|
|
|
104
104
|
height: auto;
|
|
105
105
|
margin: 5px auto;
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
.pixCartButton:hover,
|
|
109
|
+
.pixCart .pixCartButton:hover,
|
|
110
|
+
#pixCartButton.pixCartButton:hover {
|
|
111
|
+
color: #369;
|
|
112
|
+
text-decoration: underline;
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
#pixCartButton.pixCartButton {
|
|
108
116
|
width: 80px;
|
|
109
117
|
min-width: 80px;
|
|
@@ -115,6 +123,8 @@
|
|
|
115
123
|
|
|
116
124
|
|
|
117
125
|
|
|
126
|
+
|
|
127
|
+
|
|
118
128
|
input[readonly] {
|
|
119
129
|
border: none;
|
|
120
130
|
background-color: transparent;
|
|
@@ -104,6 +104,13 @@ export function RecipeBook(props) {
|
|
|
104
104
|
useEffect(() => {
|
|
105
105
|
setOutputElems(outputMyElems());
|
|
106
106
|
}, [showOnlyCat, showOnlyRecipe]);
|
|
107
|
+
// Deep linking: read URL hash on mount and select recipe if present
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const hash = window.location.hash.replace('#', '');
|
|
110
|
+
if (hash && hash.length > 0) {
|
|
111
|
+
onRecipePickListChange(hash);
|
|
112
|
+
}
|
|
113
|
+
}, []); // Empty dependency array - only run on mount
|
|
107
114
|
function onRecipePickListChange(optionVal) {
|
|
108
115
|
let cID, rID;
|
|
109
116
|
if (optionVal.includes('-')) {
|
|
@@ -33,7 +33,7 @@ Resume.propTypes = {
|
|
|
33
33
|
data: PropTypes.any.isRequired,
|
|
34
34
|
};
|
|
35
35
|
export function Resume(props) {
|
|
36
|
-
return (_jsx("section", { className: "p-resume", id: "resume-section", children: _jsx("div", { className: "section-container", children: _jsxs("div", { className: "row-12col", children: [_jsx("div", { className: "p-name grid-s1-e13", children: _jsx(ResumeName, { data: props.data
|
|
36
|
+
return (_jsx("section", { className: "p-resume", id: "resume-section", children: _jsx("div", { className: "section-container", children: _jsxs("div", { className: "row-12col", children: [_jsx("div", { className: "p-name grid-s1-e13", children: _jsx(ResumeName, { data: props.data?.items?.[0]?.properties?.name || '' }) }), _jsxs("div", { className: "divider grid-s1-e4", children: [_jsx("div", { className: "p-contact", children: _jsx(ResumeContact, { title: "Contact Information", data: props.data?.items?.[0]?.properties?.contact || [] }) }), _jsx("div", { className: "p-education", children: _jsx(ResumeEvents, { title: "Education", data: props.data?.items?.[0]?.properties?.education || [], dateFormat: "MM/yyyy", collapsible: false }) }), _jsx("div", { className: "p-skills", children: _jsx(ResumeSkills, { title: "Skills", data: props.data?.items?.[0]?.properties?.skills || [] }) })] }), _jsxs("div", { className: "grid-s4-e13", children: [_jsx("div", { className: "p-summary", children: _jsx(ResumeSummary, { title: "Professional Summary", data: props.data?.items?.[0]?.properties?.summary || '' }) }), _jsx("div", { className: "p-qualifications", children: _jsx(ResumeQualifications, { title: "Professional Qualifications", data: props.data?.items?.[0]?.properties?.qualifications || [] }) }), _jsx("div", { className: "p-experience", children: _jsx(ResumeEvents, { title: "Work History", data: props.data?.items?.[0]?.properties?.experience || [], dateFormat: "MM/yyyy", collapsible: false }) }), _jsx("div", { className: "p-projects", children: _jsx(ResumeProjects, { title: "Projects", data: props.data?.items?.[0]?.properties?.experience || [], collapsible: true }) }), _jsx("div", { className: "p-volunteer", children: _jsx(ResumeEvents, { title: "Volunteer Work", data: props.data?.items?.[0]?.properties?.volunteer || [], dateFormat: "MM/yyyy", collapsible: true }) }), _jsx("div", { className: "p-certifications", children: _jsx(ResumeEvents, { title: "Certifications", data: props.data?.items?.[0]?.properties?.certifications || [], dateFormat: "MM/yyyy", collapsible: true }) }), _jsx("div", { className: "p-awards", children: _jsx(ResumeEvents, { title: "Honors & Awards", data: props.data?.items?.[0]?.properties?.awards || [], dateFormat: "MM/yyyy", collapsible: true }) }), _jsx("div", { className: "p-training", children: _jsx(ResumeEvents, { title: "Training & Conferences", data: props.data?.items?.[0]?.properties?.training || [], dateFormat: "MM/dd/yyyy", collapsible: true }) }), _jsx("div", { className: "p-references", children: _jsx(ResumeReferences, { title: "References", data: props.data?.items?.[0]?.properties?.references || [], collapsible: true }) })] })] }) }) }));
|
|
37
37
|
}
|
|
38
38
|
ResumeName.propTypes = {
|
|
39
39
|
data: PropTypes.any.isRequired,
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export * from './components/config/config.server';
|
|
|
25
25
|
export * from './components/config/config';
|
|
26
26
|
export * from './components/config/config.types';
|
|
27
27
|
export * from './components/general/css';
|
|
28
|
+
export * from './components/general/accordion';
|
|
28
29
|
export * from './components/general/image';
|
|
29
30
|
export * from './components/general/loading';
|
|
30
31
|
export * from './components/general/microinteractions';
|
|
@@ -46,7 +46,7 @@ export declare function CalloutButton({ title, url, target }: CalloutButtonType)
|
|
|
46
46
|
export declare namespace CalloutButton {
|
|
47
47
|
var propTypes: {
|
|
48
48
|
title: PropTypes.Validator<string>;
|
|
49
|
-
url: PropTypes.
|
|
49
|
+
url: PropTypes.Validator<string>;
|
|
50
50
|
target: PropTypes.Requireable<string>;
|
|
51
51
|
};
|
|
52
52
|
}
|