project-portfolio 2.0.0 → 2.1.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 +349 -94
- package/dist/GalleryCarousel.d.ts +3 -2
- package/dist/GalleryCarousel.d.ts.map +1 -1
- package/dist/GalleryCarousel.js +1 -1
- package/dist/ProjectDetail.js +1 -1
- package/dist/ProjectMenu.d.ts +11 -3
- package/dist/ProjectMenu.d.ts.map +1 -1
- package/dist/ProjectMenu.js +57 -30
- package/dist/ProjectMenuClient.d.ts +6 -1
- package/dist/ProjectMenuClient.d.ts.map +1 -1
- package/dist/ProjectMenuClient.js +167 -103
- package/dist/ProjectPortfolioClient.d.ts.map +1 -1
- package/dist/ProjectPortfolioClient.js +6 -1
- package/dist/SimilarProjects.d.ts +13 -1
- package/dist/SimilarProjects.d.ts.map +1 -1
- package/dist/SimilarProjects.js +70 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ const ACCENT = "oklch(0.78 0.16 85)";
|
|
|
17
17
|
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
18
18
|
const DEFAULT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
19
19
|
const menuDataCache = new Map();
|
|
20
|
-
export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp = "Project Type", fieldOptionsMap: fieldOptionsMapProp = {}, subtitle, basePath, viewAllPath, font = DEFAULT_FONT, maxProjects = 6, }) {
|
|
20
|
+
export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp = "Project Type", fieldOptionsMap: fieldOptionsMapProp = {}, subtitle, basePath, viewAllPath, font = DEFAULT_FONT, maxProjects = 6, }) {
|
|
21
21
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
22
22
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
23
23
|
const [hoveredCard, setHoveredCard] = useState(null);
|
|
@@ -30,9 +30,9 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
30
30
|
if (!hasDataUrl && !hasDirectFetch)
|
|
31
31
|
return;
|
|
32
32
|
let cancelled = false;
|
|
33
|
-
const cacheKey = dataUrl !== null && dataUrl !== void 0 ? dataUrl : `${clientSlug}:${apiBase}`;
|
|
33
|
+
const cacheKey = dataUrl !== null && dataUrl !== void 0 ? dataUrl : `${clientSlug}:${apiBase}:${menuId !== null && menuId !== void 0 ? menuId : "all"}`;
|
|
34
34
|
async function fetchAndCache() {
|
|
35
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
35
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
|
|
36
36
|
// dataUrl mode: fetch from local API route (server-cached, no API key exposed)
|
|
37
37
|
if (dataUrl) {
|
|
38
38
|
const res = await fetch(dataUrl);
|
|
@@ -46,19 +46,62 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
46
46
|
fieldOptionsMap: (_f = json.fieldOptionsMap) !== null && _f !== void 0 ? _f : {},
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
//
|
|
49
|
+
// Menu endpoint mode: fetch from /menus/{menuId} when menuId is provided
|
|
50
|
+
// Also fetch /projects to get schema for filters (menu endpoint may not return it)
|
|
51
|
+
if (menuId) {
|
|
52
|
+
const [menuRes, projectsRes, fieldsRes] = await Promise.all([
|
|
53
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/menus/${menuId}?api_key=${API_KEY}`),
|
|
54
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`),
|
|
55
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`),
|
|
56
|
+
]);
|
|
57
|
+
const menuJson = menuRes.ok ? await menuRes.json() : {};
|
|
58
|
+
const projectsJson = projectsRes.ok ? await projectsRes.json() : {};
|
|
59
|
+
const projects = (_h = (_g = menuJson === null || menuJson === void 0 ? void 0 : menuJson.projects) !== null && _g !== void 0 ? _g : menuJson === null || menuJson === void 0 ? void 0 : menuJson.data) !== null && _h !== void 0 ? _h : [];
|
|
60
|
+
// Get schema from menu response first, fallback to projects response
|
|
61
|
+
const schema = (_o = (_l = (_k = (_j = menuJson === null || menuJson === void 0 ? void 0 : menuJson.client) === null || _j === void 0 ? void 0 : _j.custom_fields_schema) !== null && _k !== void 0 ? _k : menuJson === null || menuJson === void 0 ? void 0 : menuJson.schema) !== null && _l !== void 0 ? _l : (_m = projectsJson === null || projectsJson === void 0 ? void 0 : projectsJson.client) === null || _m === void 0 ? void 0 : _m.custom_fields_schema) !== null && _o !== void 0 ? _o : [];
|
|
62
|
+
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
63
|
+
const fieldOptionsMap = {};
|
|
64
|
+
for (const field of ((_p = fieldsJson.fields) !== null && _p !== void 0 ? _p : [])) {
|
|
65
|
+
const map = {};
|
|
66
|
+
for (const opt of ((_q = field.options) !== null && _q !== void 0 ? _q : [])) {
|
|
67
|
+
if (typeof opt === "object" && opt.id && opt.label) {
|
|
68
|
+
map[opt.id] = opt.label;
|
|
69
|
+
map[opt.label] = opt.label;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
fieldOptionsMap[field.key] = map;
|
|
73
|
+
}
|
|
74
|
+
const filterField = (_r = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _r !== void 0 ? _r : null;
|
|
75
|
+
const filterOptions = filterField
|
|
76
|
+
? ((_s = filterField.options) !== null && _s !== void 0 ? _s : []).map((opt) => {
|
|
77
|
+
var _a, _b;
|
|
78
|
+
if (typeof opt === "string")
|
|
79
|
+
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
80
|
+
return { id: (_a = opt.id) !== null && _a !== void 0 ? _a : "", label: (_b = opt.label) !== null && _b !== void 0 ? _b : "" };
|
|
81
|
+
})
|
|
82
|
+
: [];
|
|
83
|
+
return {
|
|
84
|
+
projects,
|
|
85
|
+
schema,
|
|
86
|
+
filterOptions,
|
|
87
|
+
filterFieldKey: (_t = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _t !== void 0 ? _t : null,
|
|
88
|
+
filterFieldName: (_u = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _u !== void 0 ? _u : "Project Type",
|
|
89
|
+
fieldOptionsMap,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Direct fetch mode: fetch from the upstream API directly (all projects)
|
|
50
93
|
const [projectsRes, fieldsRes] = await Promise.all([
|
|
51
94
|
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`),
|
|
52
95
|
fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`),
|
|
53
96
|
]);
|
|
54
97
|
const json = projectsRes.ok ? await projectsRes.json() : {};
|
|
55
|
-
const projects = (
|
|
56
|
-
const schema = (
|
|
98
|
+
const projects = (_v = json === null || json === void 0 ? void 0 : json.data) !== null && _v !== void 0 ? _v : [];
|
|
99
|
+
const schema = (_x = (_w = json === null || json === void 0 ? void 0 : json.client) === null || _w === void 0 ? void 0 : _w.custom_fields_schema) !== null && _x !== void 0 ? _x : [];
|
|
57
100
|
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
58
101
|
const fieldOptionsMap = {};
|
|
59
|
-
for (const field of ((
|
|
102
|
+
for (const field of ((_y = fieldsJson.fields) !== null && _y !== void 0 ? _y : [])) {
|
|
60
103
|
const map = {};
|
|
61
|
-
for (const opt of ((
|
|
104
|
+
for (const opt of ((_z = field.options) !== null && _z !== void 0 ? _z : [])) {
|
|
62
105
|
if (typeof opt === "object" && opt.id && opt.label) {
|
|
63
106
|
map[opt.id] = opt.label;
|
|
64
107
|
map[opt.label] = opt.label;
|
|
@@ -66,9 +109,9 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
66
109
|
}
|
|
67
110
|
fieldOptionsMap[field.key] = map;
|
|
68
111
|
}
|
|
69
|
-
const filterField = (
|
|
112
|
+
const filterField = (_0 = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _0 !== void 0 ? _0 : null;
|
|
70
113
|
const filterOptions = filterField
|
|
71
|
-
? ((
|
|
114
|
+
? ((_1 = filterField.options) !== null && _1 !== void 0 ? _1 : []).map((opt) => {
|
|
72
115
|
var _a, _b;
|
|
73
116
|
if (typeof opt === "string")
|
|
74
117
|
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
@@ -79,8 +122,8 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
79
122
|
projects,
|
|
80
123
|
schema,
|
|
81
124
|
filterOptions,
|
|
82
|
-
filterFieldKey: (
|
|
83
|
-
filterFieldName: (
|
|
125
|
+
filterFieldKey: (_2 = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _2 !== void 0 ? _2 : null,
|
|
126
|
+
filterFieldName: (_3 = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _3 !== void 0 ? _3 : "Project Type",
|
|
84
127
|
fieldOptionsMap,
|
|
85
128
|
};
|
|
86
129
|
}
|
|
@@ -95,7 +138,7 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
95
138
|
// silently fail — render nothing
|
|
96
139
|
});
|
|
97
140
|
return () => { cancelled = true; };
|
|
98
|
-
}, [dataUrl, clientSlug, apiBase]);
|
|
141
|
+
}, [dataUrl, clientSlug, apiBase, menuId]);
|
|
99
142
|
// Resolve data: prefer self-fetched, fall back to props
|
|
100
143
|
const projects = (_b = (_a = fetched === null || fetched === void 0 ? void 0 : fetched.projects) !== null && _a !== void 0 ? _a : projectsProp) !== null && _b !== void 0 ? _b : [];
|
|
101
144
|
const schema = (_d = (_c = fetched === null || fetched === void 0 ? void 0 : fetched.schema) !== null && _c !== void 0 ? _c : schemaProp) !== null && _d !== void 0 ? _d : [];
|
|
@@ -118,7 +161,7 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
118
161
|
font-family: ${font};
|
|
119
162
|
width: 100%;
|
|
120
163
|
box-sizing: border-box;
|
|
121
|
-
padding: 1.
|
|
164
|
+
padding: 1.25rem 1rem;
|
|
122
165
|
}
|
|
123
166
|
@media (min-width: 768px) {
|
|
124
167
|
.chisel-menu-outer {
|
|
@@ -127,19 +170,23 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
127
170
|
}
|
|
128
171
|
}
|
|
129
172
|
|
|
173
|
+
/* ── Left column ── */
|
|
130
174
|
.chisel-menu-left {
|
|
131
175
|
flex: 1;
|
|
132
176
|
min-width: 0;
|
|
133
177
|
padding-right: 0;
|
|
134
|
-
padding-bottom: 1.
|
|
178
|
+
padding-bottom: 1.25rem;
|
|
179
|
+
border-bottom: 1px solid #e4e4e7;
|
|
135
180
|
}
|
|
136
181
|
@media (min-width: 768px) {
|
|
137
182
|
.chisel-menu-left {
|
|
138
183
|
padding-right: 2.5rem;
|
|
139
184
|
padding-bottom: 0;
|
|
185
|
+
border-bottom: none;
|
|
140
186
|
}
|
|
141
187
|
}
|
|
142
188
|
|
|
189
|
+
/* ── Vertical divider — desktop only ── */
|
|
143
190
|
.chisel-menu-divider-v {
|
|
144
191
|
display: none;
|
|
145
192
|
}
|
|
@@ -153,79 +200,128 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
153
200
|
}
|
|
154
201
|
}
|
|
155
202
|
|
|
203
|
+
/* ── Right column ── */
|
|
156
204
|
.chisel-menu-right {
|
|
157
205
|
width: 100%;
|
|
158
206
|
padding-left: 0;
|
|
159
|
-
padding-top: 1.
|
|
160
|
-
border-top: 1px solid #e4e4e7;
|
|
207
|
+
padding-top: 1.25rem;
|
|
161
208
|
}
|
|
162
209
|
@media (min-width: 768px) {
|
|
163
210
|
.chisel-menu-right {
|
|
164
|
-
width:
|
|
211
|
+
width: 220px;
|
|
165
212
|
flex-shrink: 0;
|
|
166
213
|
padding-left: 2.5rem;
|
|
167
214
|
padding-top: 0;
|
|
168
|
-
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
@media (min-width: 1024px) {
|
|
218
|
+
.chisel-menu-right {
|
|
219
|
+
width: 280px;
|
|
169
220
|
}
|
|
170
221
|
}
|
|
171
222
|
|
|
223
|
+
/* ── Project card grid ──
|
|
224
|
+
Mobile: 1 column — cards get full width, titles don't wrap
|
|
225
|
+
Desktop: 2 columns */
|
|
172
226
|
.chisel-menu-card-grid {
|
|
173
227
|
display: grid;
|
|
174
228
|
grid-template-columns: 1fr;
|
|
175
|
-
gap:
|
|
229
|
+
gap: 6px;
|
|
176
230
|
}
|
|
177
|
-
@media (min-width:
|
|
231
|
+
@media (min-width: 768px) {
|
|
178
232
|
.chisel-menu-card-grid {
|
|
179
233
|
grid-template-columns: repeat(2, 1fr);
|
|
180
|
-
gap:
|
|
234
|
+
gap: 10px;
|
|
181
235
|
}
|
|
182
236
|
}
|
|
183
237
|
|
|
184
|
-
|
|
238
|
+
/* Hide cards beyond the 3rd on mobile */
|
|
239
|
+
@media (max-width: 767px) {
|
|
240
|
+
.chisel-menu-card:nth-child(n+4) {
|
|
241
|
+
display: none;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Card — always horizontal side-by-side */
|
|
246
|
+
.chisel-menu-card {
|
|
185
247
|
display: flex;
|
|
248
|
+
flex-direction: row;
|
|
186
249
|
align-items: center;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
border:
|
|
190
|
-
|
|
250
|
+
gap: 12px;
|
|
251
|
+
padding: 10px 12px;
|
|
252
|
+
border: 1px solid #e4e4e7;
|
|
253
|
+
text-decoration: none;
|
|
254
|
+
color: inherit;
|
|
255
|
+
box-sizing: border-box;
|
|
256
|
+
transition: border-color 0.2s;
|
|
191
257
|
cursor: pointer;
|
|
192
|
-
|
|
193
|
-
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
}
|
|
260
|
+
.chisel-menu-card:hover {
|
|
261
|
+
border-color: ${ACCENT};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.chisel-menu-card-thumb {
|
|
265
|
+
width: 72px;
|
|
266
|
+
height: 54px;
|
|
267
|
+
overflow: hidden;
|
|
268
|
+
background-color: #f4f4f5;
|
|
269
|
+
flex-shrink: 0;
|
|
194
270
|
}
|
|
195
271
|
@media (min-width: 768px) {
|
|
196
|
-
.chisel-menu-
|
|
197
|
-
|
|
272
|
+
.chisel-menu-card-thumb {
|
|
273
|
+
width: 72px;
|
|
274
|
+
height: 54px;
|
|
198
275
|
}
|
|
199
276
|
}
|
|
200
277
|
|
|
201
|
-
.chisel-menu-
|
|
202
|
-
display: none;
|
|
203
|
-
}
|
|
204
|
-
.chisel-menu-filters-list.open {
|
|
278
|
+
.chisel-menu-card-body {
|
|
205
279
|
display: flex;
|
|
206
280
|
flex-direction: column;
|
|
207
281
|
gap: 2px;
|
|
282
|
+
flex: 1;
|
|
283
|
+
min-width: 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ── Subtitle — hidden on mobile, shown on desktop ── */
|
|
287
|
+
.chisel-menu-subtitle {
|
|
288
|
+
display: none !important;
|
|
208
289
|
}
|
|
209
290
|
@media (min-width: 768px) {
|
|
210
|
-
.chisel-menu-
|
|
211
|
-
display:
|
|
212
|
-
flex-direction: column;
|
|
213
|
-
gap: 2px;
|
|
291
|
+
.chisel-menu-subtitle {
|
|
292
|
+
display: block !important;
|
|
214
293
|
}
|
|
215
294
|
}
|
|
295
|
+
|
|
296
|
+
/* ── Filter links — always visible, no toggle needed ── */
|
|
297
|
+
.chisel-menu-filter-link {
|
|
298
|
+
all: unset;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
display: block;
|
|
301
|
+
font-size: 14px;
|
|
302
|
+
color: #52525b;
|
|
303
|
+
font-weight: 400;
|
|
304
|
+
padding: 7px 0;
|
|
305
|
+
text-decoration: none;
|
|
306
|
+
transition: color 0.15s;
|
|
307
|
+
}
|
|
308
|
+
.chisel-menu-filter-link:hover {
|
|
309
|
+
color: #18181b;
|
|
310
|
+
}
|
|
216
311
|
` }), _jsxs("div", { className: "chisel-menu-outer", children: [_jsxs("div", { className: "chisel-menu-left", children: [_jsx("p", { style: {
|
|
217
|
-
fontSize: "
|
|
312
|
+
fontSize: "11px",
|
|
218
313
|
fontWeight: 700,
|
|
219
314
|
textTransform: "uppercase",
|
|
220
315
|
letterSpacing: "0.12em",
|
|
221
316
|
color: "#71717a",
|
|
222
|
-
margin: "0 0
|
|
317
|
+
margin: "0 0 12px 0",
|
|
223
318
|
}, children: "Featured Projects" }), subtitle && (_jsx("p", { style: {
|
|
224
|
-
fontSize: "
|
|
319
|
+
fontSize: "14px",
|
|
225
320
|
color: "#52525b",
|
|
226
|
-
lineHeight: 1.
|
|
227
|
-
margin: "0 0
|
|
228
|
-
|
|
321
|
+
lineHeight: 1.5,
|
|
322
|
+
margin: "0 0 16px 0",
|
|
323
|
+
display: "none",
|
|
324
|
+
}, className: "chisel-menu-subtitle", children: subtitle })), displayed.length === 0 ? (_jsx("p", { style: { fontSize: "14px", color: "#a1a1aa", margin: 0 }, children: "No projects found." })) : (_jsx("div", { className: "chisel-menu-card-grid", children: displayed.map((project) => {
|
|
229
325
|
var _a, _b, _c, _d, _e, _f;
|
|
230
326
|
const imageUrl = (_d = (_a = project.image_url) !== null && _a !== void 0 ? _a : (_c = (_b = project.media) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : null;
|
|
231
327
|
const badgeRaw = badgeField ? parseSingleValue(project.custom_field_values[badgeField.key]) : null;
|
|
@@ -234,78 +330,46 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
234
330
|
const tags = tagsField ? parseMultiValue(project.custom_field_values[tagsField.key]) : [];
|
|
235
331
|
const href = `${basePath}/${project.slug}`;
|
|
236
332
|
const isHovered = hoveredCard === project.id;
|
|
237
|
-
return (_jsxs("a", { href: href, onMouseEnter: () => setHoveredCard(project.id), onMouseLeave: () => setHoveredCard(null), style: {
|
|
238
|
-
all: "unset",
|
|
239
|
-
cursor: "pointer",
|
|
240
|
-
display: "flex",
|
|
241
|
-
alignItems: "flex-start",
|
|
242
|
-
gap: "12px",
|
|
243
|
-
padding: "12px",
|
|
244
|
-
border: `1px solid ${isHovered ? ACCENT : "#e4e4e7"}`,
|
|
245
|
-
textDecoration: "none",
|
|
246
|
-
boxSizing: "border-box",
|
|
247
|
-
transition: "border-color 0.2s",
|
|
248
|
-
}, children: [_jsx("div", { style: {
|
|
249
|
-
width: "80px",
|
|
250
|
-
height: "64px",
|
|
251
|
-
flexShrink: 0,
|
|
252
|
-
overflow: "hidden",
|
|
253
|
-
backgroundColor: "#f4f4f5",
|
|
254
|
-
}, children: imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: {
|
|
333
|
+
return (_jsxs("a", { href: href, className: "chisel-menu-card", onMouseEnter: () => setHoveredCard(project.id), onMouseLeave: () => setHoveredCard(null), children: [_jsx("div", { className: "chisel-menu-card-thumb", children: imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: {
|
|
255
334
|
width: "100%",
|
|
256
335
|
height: "100%",
|
|
257
336
|
objectFit: "cover",
|
|
258
337
|
display: "block",
|
|
259
|
-
transform: isHovered ? "scale(1.
|
|
338
|
+
transform: isHovered ? "scale(1.05)" : "scale(1)",
|
|
260
339
|
transition: "transform 0.3s ease",
|
|
261
|
-
} })) : (_jsx("div", { style: { width: "100%", height: "100%", backgroundColor: "#e4e4e7" } })) }), _jsxs("div", {
|
|
262
|
-
fontSize: "
|
|
340
|
+
} })) : (_jsx("div", { style: { width: "100%", height: "100%", backgroundColor: "#e4e4e7" } })) }), _jsxs("div", { className: "chisel-menu-card-body", children: [_jsx("p", { style: {
|
|
341
|
+
fontSize: "13px",
|
|
263
342
|
fontWeight: 700,
|
|
264
343
|
color: isHovered ? ACCENT : "#18181b",
|
|
265
344
|
margin: 0,
|
|
266
345
|
lineHeight: 1.3,
|
|
267
346
|
fontFamily: font,
|
|
268
347
|
transition: "color 0.2s",
|
|
269
|
-
}, children: project.title }), badge && (_jsx("p", { style: { fontSize: "
|
|
348
|
+
}, children: project.title }), badge && (_jsx("p", { style: { fontSize: "12px", color: "#71717a", margin: 0, fontFamily: font }, children: badge })), tags.length > 0 && (() => {
|
|
270
349
|
var _a;
|
|
271
350
|
const optMap = tagsField ? ((_a = fieldOptionsMap[tagsField.key]) !== null && _a !== void 0 ? _a : {}) : {};
|
|
272
|
-
const tagLabels = tags.map((t) => { var _a; return (_a = optMap[t]) !== null && _a !== void 0 ? _a : t; });
|
|
273
|
-
|
|
274
|
-
const display = tagLabels.length > 3
|
|
275
|
-
? shown.join(" · ") + " · ..."
|
|
276
|
-
: shown.join(" · ");
|
|
277
|
-
return (_jsx("p", { style: { fontSize: "12px", color: ACCENT, margin: 0, fontFamily: font, lineHeight: 1.4 }, children: display }));
|
|
351
|
+
const tagLabels = tags.map((t) => { var _a; return (_a = optMap[t]) !== null && _a !== void 0 ? _a : t; }).slice(0, 2);
|
|
352
|
+
return (_jsx("p", { style: { fontSize: "11px", color: ACCENT, margin: 0, fontFamily: font, lineHeight: 1.4 }, children: tagLabels.join(" · ") }));
|
|
278
353
|
})()] })] }, project.id));
|
|
279
|
-
}) }))] }), _jsx("div", { className: "chisel-menu-divider-v" }),
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
fontFamily: font,
|
|
292
|
-
}, children: filterFieldName }), _jsx("button", { className: "chisel-menu-filters-mobile-toggle", onClick: () => setFiltersOpen((o) => !o), style: { marginTop: "-10px" }, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#18181b", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", style: { transform: filtersOpen ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.2s", flexShrink: 0 }, children: _jsx("path", { d: "M6 9l6 6 6-6" }) }) }), _jsx("div", { className: `chisel-menu-filters-list${filtersOpen ? " open" : ""}`, children: filterOptions.map((opt) => {
|
|
293
|
-
const href = filterFieldKey
|
|
294
|
-
? `${basePath}?filter[${filterFieldKey}]=${encodeURIComponent(opt.id)}`
|
|
295
|
-
: basePath;
|
|
296
|
-
return (_jsx("a", { href: href, style: {
|
|
297
|
-
all: "unset",
|
|
298
|
-
cursor: "pointer",
|
|
299
|
-
fontSize: "14px",
|
|
300
|
-
color: "#52525b",
|
|
301
|
-
fontWeight: 400,
|
|
302
|
-
padding: "5px 0",
|
|
303
|
-
textDecoration: "none",
|
|
354
|
+
}) }))] }), _jsx("div", { className: "chisel-menu-divider-v" }), _jsxs("div", { className: "chisel-menu-right", children: [filterOptions.length > 0 && (_jsxs(_Fragment, { children: [_jsx("p", { style: {
|
|
355
|
+
fontSize: "11px",
|
|
356
|
+
fontWeight: 700,
|
|
357
|
+
textTransform: "uppercase",
|
|
358
|
+
letterSpacing: "0.12em",
|
|
359
|
+
color: "#71717a",
|
|
360
|
+
margin: "0 0 10px 0",
|
|
361
|
+
}, children: "Browse By" }), _jsx("p", { style: {
|
|
362
|
+
fontSize: "15px",
|
|
363
|
+
fontWeight: 700,
|
|
364
|
+
color: "#18181b",
|
|
365
|
+
margin: "0 0 4px 0",
|
|
304
366
|
fontFamily: font,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
367
|
+
}, children: filterFieldName }), _jsx("div", { style: { display: "flex", flexDirection: "column", marginBottom: "20px" }, children: filterOptions.map((opt) => {
|
|
368
|
+
const href = filterFieldKey
|
|
369
|
+
? `${basePath}?filter[${filterFieldKey}]=${encodeURIComponent(opt.id)}`
|
|
370
|
+
: basePath;
|
|
371
|
+
return (_jsx("a", { href: href, className: "chisel-menu-filter-link", style: { fontFamily: font }, children: opt.label }, opt.id));
|
|
372
|
+
}) }), _jsx("div", { style: { height: "1px", backgroundColor: "#e4e4e7", marginBottom: "20px" } })] })), _jsxs("a", { href: viewAllPath, style: {
|
|
309
373
|
all: "unset",
|
|
310
374
|
cursor: "pointer",
|
|
311
375
|
display: "inline-flex",
|
|
@@ -316,5 +380,5 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, projects: proj
|
|
|
316
380
|
color: ACCENT,
|
|
317
381
|
textDecoration: "none",
|
|
318
382
|
fontFamily: font,
|
|
319
|
-
}, children: ["View All Projects", _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M5 12h14M12 5l7 7-7 7" }) })] })] })
|
|
383
|
+
}, children: ["View All Projects", _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M5 12h14M12 5l7 7-7 7" }) })] })] })] })] }));
|
|
320
384
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectPortfolioClient.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolioClient.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,2BAA2B;IAC1C,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CAChB;AA0DD,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,OAAY,EACZ,IAAmB,EACnB,OAAW,GACZ,EAAE,2BAA2B,
|
|
1
|
+
{"version":3,"file":"ProjectPortfolioClient.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolioClient.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,2BAA2B;IAC1C,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CAChB;AA0DD,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,OAAY,EACZ,IAAmB,EACnB,OAAW,GACZ,EAAE,2BAA2B,2CAoJ7B"}
|
|
@@ -108,7 +108,12 @@ export function ProjectPortfolioClient({ clientSlug, apiBase, basePath = "/proje
|
|
|
108
108
|
margin: "0 auto",
|
|
109
109
|
padding: "2rem 1rem",
|
|
110
110
|
fontFamily: font,
|
|
111
|
-
}, children: [_jsx("
|
|
111
|
+
}, children: [_jsx("style", { children: `
|
|
112
|
+
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.5 } }
|
|
113
|
+
.chisel-skeleton-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
|
|
114
|
+
@media (min-width: 640px) { .chisel-skeleton-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
115
|
+
@media (min-width: 1024px) { .chisel-skeleton-grid { grid-template-columns: ${gridCols}; gap: 2rem; } }
|
|
116
|
+
` }), _jsx("div", { className: "chisel-skeleton-grid", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { style: { backgroundColor: "#f4f4f5", borderRadius: 2, height: 320, animation: "pulse 1.5s ease-in-out infinite" } }, i))) })] }));
|
|
112
117
|
}
|
|
113
118
|
return (_jsxs("div", { style: {
|
|
114
119
|
width: "100%",
|
|
@@ -20,6 +20,18 @@ export interface SimilarProjectsProps {
|
|
|
20
20
|
maxItems?: number;
|
|
21
21
|
/** Seconds to cache. Defaults to 60 */
|
|
22
22
|
revalidate?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Visual layout for the project cards.
|
|
25
|
+
* - "list" (default) — image + text with border-bottom separator
|
|
26
|
+
* - "card" — full baseball-card style matching the ProjectPortfolio grid
|
|
27
|
+
*/
|
|
28
|
+
variant?: "list" | "card";
|
|
29
|
+
/**
|
|
30
|
+
* Explicit list of project slugs to display, in order.
|
|
31
|
+
* When provided, overrides `filters` entirely — only these projects are shown.
|
|
32
|
+
* e.g. projectSlugs={["jacob-javits", "tillamook-bay-community-college"]}
|
|
33
|
+
*/
|
|
34
|
+
projectSlugs?: string[];
|
|
23
35
|
}
|
|
24
|
-
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
36
|
+
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, variant, projectSlugs, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
25
37
|
//# sourceMappingURL=SimilarProjects.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AA6DD,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,QAAY,EACZ,UAAe,EACf,OAAgB,EAChB,YAAY,GACb,EAAE,oBAAoB,2DA8JtB"}
|