@chiselandco/nexus 2.2.6
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 +676 -0
- package/dist/GalleryCarousel.d.ts +7 -0
- package/dist/GalleryCarousel.d.ts.map +1 -0
- package/dist/GalleryCarousel.js +84 -0
- package/dist/ProjectCard.d.ts +15 -0
- package/dist/ProjectCard.d.ts.map +1 -0
- package/dist/ProjectCard.js +159 -0
- package/dist/ProjectDetail.d.ts +25 -0
- package/dist/ProjectDetail.d.ts.map +1 -0
- package/dist/ProjectDetail.js +179 -0
- package/dist/ProjectFilters.d.ts +11 -0
- package/dist/ProjectFilters.d.ts.map +1 -0
- package/dist/ProjectFilters.js +49 -0
- package/dist/ProjectGrid.d.ts +10 -0
- package/dist/ProjectGrid.d.ts.map +1 -0
- package/dist/ProjectGrid.js +8 -0
- package/dist/ProjectMenu.d.ts +79 -0
- package/dist/ProjectMenu.d.ts.map +1 -0
- package/dist/ProjectMenu.js +170 -0
- package/dist/ProjectMenuClient.d.ts +44 -0
- package/dist/ProjectMenuClient.d.ts.map +1 -0
- package/dist/ProjectMenuClient.js +386 -0
- package/dist/ProjectPortfolio.d.ts +42 -0
- package/dist/ProjectPortfolio.d.ts.map +1 -0
- package/dist/ProjectPortfolio.js +153 -0
- package/dist/ProjectPortfolioClient.d.ts +21 -0
- package/dist/ProjectPortfolioClient.d.ts.map +1 -0
- package/dist/ProjectPortfolioClient.js +141 -0
- package/dist/SimilarProjects.d.ts +46 -0
- package/dist/SimilarProjects.d.ts.map +1 -0
- package/dist/SimilarProjects.js +125 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useMemo } from "react";
|
|
4
|
+
import { ProjectCard } from "./ProjectCard";
|
|
5
|
+
const portfolioDataCache = new Map();
|
|
6
|
+
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
7
|
+
const DEFAULT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
8
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
9
|
+
function parseMultiValue(raw) {
|
|
10
|
+
if (Array.isArray(raw))
|
|
11
|
+
return raw.map(String).map((s) => s.replace(/`/g, "").trim()).filter(Boolean);
|
|
12
|
+
if (typeof raw === "string")
|
|
13
|
+
return raw.replace(/`/g, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
function matchesFilters(project, filters, schema, fieldOptionsMap) {
|
|
17
|
+
return Object.entries(filters).every(([key, value]) => {
|
|
18
|
+
var _a;
|
|
19
|
+
if (!value)
|
|
20
|
+
return true;
|
|
21
|
+
const field = schema.find((f) => f.key === key);
|
|
22
|
+
if (!field)
|
|
23
|
+
return true;
|
|
24
|
+
const raw = project.custom_field_values[key];
|
|
25
|
+
if (field.type === "location") {
|
|
26
|
+
const loc = raw;
|
|
27
|
+
if (!loc)
|
|
28
|
+
return false;
|
|
29
|
+
const locStr = [loc.city, loc.state].filter(Boolean).join(", ").toLowerCase();
|
|
30
|
+
return locStr.includes(value.toLowerCase());
|
|
31
|
+
}
|
|
32
|
+
const values = parseMultiValue(raw);
|
|
33
|
+
const optMap = (_a = fieldOptionsMap[key]) !== null && _a !== void 0 ? _a : {};
|
|
34
|
+
return values.some((v) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const normalizedV = ((_a = optMap[v]) !== null && _a !== void 0 ? _a : v).toLowerCase();
|
|
37
|
+
const normalizedFilter = value.toLowerCase();
|
|
38
|
+
return normalizedV === normalizedFilter || normalizedV.includes(normalizedFilter);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
43
|
+
export function ProjectPortfolioClient({ clientSlug, apiBase, basePath = "/projects", filters = {}, font = DEFAULT_FONT, columns = 3, }) {
|
|
44
|
+
const [data, setData] = useState(null);
|
|
45
|
+
// Self-fetch on mount — uses module-level cache so the API is only called once per page load
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const cacheKey = `${clientSlug}:${apiBase}`;
|
|
48
|
+
async function fetchAndCache() {
|
|
49
|
+
var _a, _b, _c, _d, _e;
|
|
50
|
+
const [projectsRes, fieldsRes] = await Promise.all([
|
|
51
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`),
|
|
52
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`),
|
|
53
|
+
]);
|
|
54
|
+
const json = projectsRes.ok ? await projectsRes.json() : {};
|
|
55
|
+
const projects = ((_a = json === null || json === void 0 ? void 0 : json.data) !== null && _a !== void 0 ? _a : []).filter((p) => p.is_published !== false);
|
|
56
|
+
// Deduplicate schema keys
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const schema = ((_c = (_b = json === null || json === void 0 ? void 0 : json.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : []).filter((f) => {
|
|
59
|
+
if (seen.has(f.key))
|
|
60
|
+
return false;
|
|
61
|
+
seen.add(f.key);
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
65
|
+
const fieldOptionsMap = {};
|
|
66
|
+
for (const field of ((_d = fieldsJson.fields) !== null && _d !== void 0 ? _d : [])) {
|
|
67
|
+
const map = {};
|
|
68
|
+
for (const opt of ((_e = field.options) !== null && _e !== void 0 ? _e : [])) {
|
|
69
|
+
if (typeof opt === "object" && opt.id && opt.label) {
|
|
70
|
+
map[opt.id] = opt.label;
|
|
71
|
+
map[opt.label] = opt.label;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
fieldOptionsMap[field.key] = map;
|
|
75
|
+
}
|
|
76
|
+
return { projects, schema, fieldOptionsMap };
|
|
77
|
+
}
|
|
78
|
+
if (!portfolioDataCache.has(cacheKey)) {
|
|
79
|
+
portfolioDataCache.set(cacheKey, fetchAndCache());
|
|
80
|
+
}
|
|
81
|
+
let cancelled = false;
|
|
82
|
+
portfolioDataCache.get(cacheKey).then((result) => {
|
|
83
|
+
if (!cancelled)
|
|
84
|
+
setData(result);
|
|
85
|
+
}).catch(() => {
|
|
86
|
+
if (!cancelled)
|
|
87
|
+
setData({ projects: [], schema: [], fieldOptionsMap: {} });
|
|
88
|
+
});
|
|
89
|
+
return () => { cancelled = true; };
|
|
90
|
+
}, [clientSlug, apiBase]);
|
|
91
|
+
// Filter projects locally — instant, no API call on filter change
|
|
92
|
+
const filteredProjects = useMemo(() => {
|
|
93
|
+
if (!data)
|
|
94
|
+
return [];
|
|
95
|
+
const hasActiveFilters = Object.values(filters).some(Boolean);
|
|
96
|
+
if (!hasActiveFilters)
|
|
97
|
+
return data.projects;
|
|
98
|
+
return data.projects.filter((p) => matchesFilters(p, filters, data.schema, data.fieldOptionsMap));
|
|
99
|
+
}, [data, filters]);
|
|
100
|
+
const gridCols = columns === 2
|
|
101
|
+
? "repeat(2, 1fr)"
|
|
102
|
+
: "repeat(3, 1fr)";
|
|
103
|
+
// Loading state
|
|
104
|
+
if (!data) {
|
|
105
|
+
return (_jsxs("div", { style: {
|
|
106
|
+
width: "100%",
|
|
107
|
+
maxWidth: "1280px",
|
|
108
|
+
margin: "0 auto",
|
|
109
|
+
padding: "2rem 1rem",
|
|
110
|
+
fontFamily: font,
|
|
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))) })] }));
|
|
117
|
+
}
|
|
118
|
+
return (_jsxs("div", { style: {
|
|
119
|
+
width: "100%",
|
|
120
|
+
maxWidth: "1280px",
|
|
121
|
+
margin: "0 auto",
|
|
122
|
+
padding: "2rem 1rem",
|
|
123
|
+
boxSizing: "border-box",
|
|
124
|
+
fontFamily: font,
|
|
125
|
+
}, children: [filteredProjects.length === 0 && (_jsx("div", { style: { textAlign: "center", padding: "4rem 0" }, children: _jsx("p", { style: { color: "#71717a", fontFamily: font }, children: "No projects found." }) })), filteredProjects.length > 0 && (_jsxs(_Fragment, { children: [_jsx("style", { children: `
|
|
126
|
+
.chisel-portfolio-grid {
|
|
127
|
+
display: grid;
|
|
128
|
+
grid-template-columns: 1fr;
|
|
129
|
+
gap: 1.5rem;
|
|
130
|
+
}
|
|
131
|
+
@media (min-width: 640px) {
|
|
132
|
+
.chisel-portfolio-grid { grid-template-columns: repeat(2, 1fr); }
|
|
133
|
+
}
|
|
134
|
+
@media (min-width: 1024px) {
|
|
135
|
+
.chisel-portfolio-grid { grid-template-columns: ${gridCols}; gap: 2rem; }
|
|
136
|
+
}
|
|
137
|
+
.chisel-project-card-img { height: 180px; }
|
|
138
|
+
@media (min-width: 640px) { .chisel-project-card-img { height: 200px; } }
|
|
139
|
+
@media (min-width: 1024px) { .chisel-project-card-img { height: 220px; } }
|
|
140
|
+
` }), _jsx("div", { className: "chisel-portfolio-grid", children: filteredProjects.map((project, index) => (_jsx(ProjectCard, { project: project, schema: data.schema, fieldOptionsMap: data.fieldOptionsMap, basePath: basePath, priority: index === 0 }, project.id))) })] }))] }));
|
|
141
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface SimilarProjectsProps {
|
|
2
|
+
/**
|
|
3
|
+
* Key/value pairs to filter projects by custom field values.
|
|
4
|
+
* e.g. { type: "commercial" } or { type: "educational-facilities" }
|
|
5
|
+
* All filters must match (AND logic).
|
|
6
|
+
*/
|
|
7
|
+
filters?: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Slug of a project to exclude from results (e.g. the currently viewed project).
|
|
10
|
+
* Optional — omit if you don't need to exclude any project.
|
|
11
|
+
*/
|
|
12
|
+
excludeSlug?: string;
|
|
13
|
+
/** The client slug identifying which client owns these projects */
|
|
14
|
+
clientSlug: string;
|
|
15
|
+
/** Base URL of the projects API */
|
|
16
|
+
apiBase: string;
|
|
17
|
+
/** Base path used to build individual project detail URLs e.g. "/projects" */
|
|
18
|
+
basePath?: string;
|
|
19
|
+
/** Maximum number of projects to show. Defaults to 3 */
|
|
20
|
+
maxItems?: number;
|
|
21
|
+
/** Seconds to cache. Defaults to 60 */
|
|
22
|
+
revalidate?: number;
|
|
23
|
+
/**
|
|
24
|
+
* When true, bypasses the Next.js Data Cache and always fetches fresh data.
|
|
25
|
+
* Sets fetch cache to "no-store". Useful during development or for frequently updated projects.
|
|
26
|
+
*/
|
|
27
|
+
noCache?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Visual layout for the project cards.
|
|
30
|
+
* - "list" (default) — image + text with border-bottom separator
|
|
31
|
+
* - "card" — full baseball-card style matching the ProjectPortfolio grid
|
|
32
|
+
*/
|
|
33
|
+
variant?: "list" | "card";
|
|
34
|
+
/**
|
|
35
|
+
* Explicit list of project slugs to display, in order.
|
|
36
|
+
* When provided, overrides `filters` entirely — only these projects are shown.
|
|
37
|
+
* e.g. projectSlugs={["jacob-javits", "tillamook-bay-community-college"]}
|
|
38
|
+
*/
|
|
39
|
+
projectSlugs?: string[];
|
|
40
|
+
/** Main heading for the section. Defaults to "Similar Projects" */
|
|
41
|
+
title?: string;
|
|
42
|
+
/** Small label above the heading. Defaults to "More Work" */
|
|
43
|
+
subtitle?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, noCache, variant, projectSlugs, title, subtitle, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
46
|
+
//# sourceMappingURL=SimilarProjects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAYA,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;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA+DD,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,QAAY,EACZ,UAAe,EACf,OAAe,EACf,OAAgB,EAChB,YAAY,EACZ,KAA0B,EAC1B,QAAsB,GACvB,EAAE,oBAAoB,2DAiKtB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cache } from "react";
|
|
3
|
+
import { ProjectCard } from "./ProjectCard";
|
|
4
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
5
|
+
function parseMultiValue(raw) {
|
|
6
|
+
if (Array.isArray(raw))
|
|
7
|
+
return raw.map(String).map((s) => s.replace(/`/g, "").trim()).filter(Boolean);
|
|
8
|
+
if (typeof raw === "string")
|
|
9
|
+
return raw.replace(/`/g, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
function dedupeByKey(arr) {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
return arr.filter((f) => {
|
|
15
|
+
if (seen.has(f.key))
|
|
16
|
+
return false;
|
|
17
|
+
seen.add(f.key);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// ─── Data fetching ────────────────────────────────────────────────────────────
|
|
22
|
+
const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate, noCache) => {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
const fetchOpts = noCache
|
|
25
|
+
? { cache: "no-store" }
|
|
26
|
+
: revalidate > 0
|
|
27
|
+
? { next: { revalidate } }
|
|
28
|
+
: {};
|
|
29
|
+
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
30
|
+
try {
|
|
31
|
+
// Single call — /projects returns projects AND client.custom_fields_schema with full options.
|
|
32
|
+
const res = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`, fetchOpts);
|
|
33
|
+
const json = res.ok ? await res.json() : null;
|
|
34
|
+
const allProjects = ((_a = json === null || json === void 0 ? void 0 : json.data) !== null && _a !== void 0 ? _a : []).filter((p) => p.is_published !== false);
|
|
35
|
+
const schema = dedupeByKey((_c = (_b = json === null || json === void 0 ? void 0 : json.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : []);
|
|
36
|
+
const fieldOptionsMap = {};
|
|
37
|
+
for (const field of schema) {
|
|
38
|
+
const map = {};
|
|
39
|
+
for (const opt of ((_d = field.options) !== null && _d !== void 0 ? _d : [])) {
|
|
40
|
+
if (typeof opt === "object" && opt.id && opt.label) {
|
|
41
|
+
map[opt.id] = opt.label;
|
|
42
|
+
map[opt.label] = opt.label;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
fieldOptionsMap[field.key] = map;
|
|
46
|
+
}
|
|
47
|
+
return { allProjects, schema, fieldOptionsMap };
|
|
48
|
+
}
|
|
49
|
+
catch (_e) {
|
|
50
|
+
return { allProjects: [], schema: [], fieldOptionsMap: {} };
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
54
|
+
export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, noCache = false, variant = "list", projectSlugs, title = "Similar Projects", subtitle = "More Work", }) {
|
|
55
|
+
const { allProjects, schema, fieldOptionsMap } = await fetchSimilarData(apiBase, clientSlug, revalidate, noCache);
|
|
56
|
+
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
57
|
+
const locationField = schema.find((f) => f.type === "location");
|
|
58
|
+
const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
59
|
+
let similar;
|
|
60
|
+
if (projectSlugs && projectSlugs.length > 0) {
|
|
61
|
+
// Manual mode — show exactly these slugs in the order provided
|
|
62
|
+
const projectMap = new Map(allProjects.map((p) => [p.slug, p]));
|
|
63
|
+
similar = projectSlugs
|
|
64
|
+
.filter((slug) => slug !== excludeSlug)
|
|
65
|
+
.map((slug) => projectMap.get(slug))
|
|
66
|
+
.filter((p) => p !== undefined)
|
|
67
|
+
.slice(0, maxItems);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Filter mode — match by custom field values
|
|
71
|
+
const filterEntries = Object.entries(filters);
|
|
72
|
+
similar = allProjects
|
|
73
|
+
.filter((p) => {
|
|
74
|
+
if (excludeSlug && p.slug === excludeSlug)
|
|
75
|
+
return false;
|
|
76
|
+
return filterEntries.every(([key, value]) => {
|
|
77
|
+
const fieldValues = parseMultiValue(p.custom_field_values[key]);
|
|
78
|
+
return fieldValues.some((v) => v.toLowerCase() === value.toLowerCase());
|
|
79
|
+
});
|
|
80
|
+
})
|
|
81
|
+
.slice(0, maxItems);
|
|
82
|
+
}
|
|
83
|
+
if (similar.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
const header = (_jsxs("div", { className: "chisel-similar-header", children: [_jsxs("div", { children: [_jsx("p", { style: { fontSize: "11px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: "oklch(0.78 0.16 85)", margin: "0 0 6px 0" }, children: subtitle }), _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "clamp(20px, 4vw, 28px)", margin: 0, fontFamily: font }, children: title })] }), _jsxs("a", { href: basePath, style: { color: "#18181b", fontWeight: 600, fontSize: "15px", textDecoration: "none", display: "inline-flex", alignItems: "center", gap: "6px", fontFamily: font, flexShrink: 0 }, children: ["View All", _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9 18l6-6-6-6" }) })] })] }));
|
|
86
|
+
// Shared styles injected regardless of variant so .chisel-project-card-img is always defined
|
|
87
|
+
const sharedStyles = (_jsx("style", { children: `
|
|
88
|
+
.chisel-similar-header { display: flex; flex-direction: column; gap: 12px; margin-bottom: 2rem; }
|
|
89
|
+
@media (min-width: 640px) { .chisel-similar-header { flex-direction: row; align-items: flex-end; justify-content: space-between; } }
|
|
90
|
+
.chisel-similar-card-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
|
|
91
|
+
@media (min-width: 640px) { .chisel-similar-card-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
92
|
+
@media (min-width: 1024px) { .chisel-similar-card-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
93
|
+
.chisel-project-card-img { height: 180px; }
|
|
94
|
+
@media (min-width: 640px) { .chisel-project-card-img { height: 200px; } }
|
|
95
|
+
@media (min-width: 1024px) { .chisel-project-card-img { height: 220px; } }
|
|
96
|
+
.chisel-similar-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
|
|
97
|
+
@media (min-width: 640px) { .chisel-similar-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
98
|
+
@media (min-width: 1024px) { .chisel-similar-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
99
|
+
.chisel-similar-img { height: 56vw; min-height: 160px; max-height: 220px; }
|
|
100
|
+
` }));
|
|
101
|
+
// ── Card variant ─────────────────────────────────────────────────────────────
|
|
102
|
+
if (variant === "card") {
|
|
103
|
+
return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [sharedStyles, header, _jsx("div", { className: "chisel-similar-card-grid", children: similar.map((p, i) => (_jsx(ProjectCard, { project: p, schema: schema, fieldOptionsMap: fieldOptionsMap, basePath: basePath, priority: i === 0, variant: "card" }, p.id))) })] }));
|
|
104
|
+
}
|
|
105
|
+
// ── List variant (default) ────────────────────────────────────────────────────
|
|
106
|
+
return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [sharedStyles, header, _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
|
|
107
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
108
|
+
const imgUrl = (_d = (_a = p.image_url) !== null && _a !== void 0 ? _a : (_c = (_b = p.media) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : null;
|
|
109
|
+
const badgeRaw = badgeField
|
|
110
|
+
? ((_e = parseMultiValue(p.custom_field_values[badgeField.key])[0]) !== null && _e !== void 0 ? _e : null)
|
|
111
|
+
: null;
|
|
112
|
+
const badgeOptMap = badgeField ? ((_f = fieldOptionsMap[badgeField.key]) !== null && _f !== void 0 ? _f : {}) : {};
|
|
113
|
+
const badge = badgeRaw ? ((_g = badgeOptMap[badgeRaw]) !== null && _g !== void 0 ? _g : badgeRaw) : null;
|
|
114
|
+
const loc = locationField
|
|
115
|
+
? p.custom_field_values[locationField.key]
|
|
116
|
+
: null;
|
|
117
|
+
const locStr = loc ? [loc.city, loc.state].filter(Boolean).join(", ") : null;
|
|
118
|
+
return (_jsxs("a", { href: `${basePath}/${p.slug}`, style: { textDecoration: "none", color: "inherit", display: "block", borderBottom: "1px solid #e4e4e7", fontFamily: font }, children: [_jsxs("div", { className: "chisel-similar-img", style: { position: "relative", width: "100%", overflow: "hidden", backgroundColor: "#f4f4f5", marginBottom: "1rem" }, children: [imgUrl && (_jsx("img", { src: imgUrl, alt: p.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })), badge && (_jsx("span", { style: {
|
|
119
|
+
position: "absolute", top: "12px", left: "12px",
|
|
120
|
+
backgroundColor: "oklch(0.78 0.16 85)", color: "#fff",
|
|
121
|
+
fontSize: "10px", fontWeight: 700, textTransform: "uppercase",
|
|
122
|
+
letterSpacing: "0.1em", padding: "4px 10px",
|
|
123
|
+
}, children: badge }))] }), _jsxs("div", { style: { paddingBottom: "1.25rem" }, children: [_jsx("h3", { style: { color: "#18181b", fontWeight: 700, fontSize: "clamp(15px, 2.5vw, 17px)", lineHeight: 1.3, margin: "0 0 8px 0", fontFamily: font }, children: p.title }), locStr && (_jsxs("p", { style: { display: "flex", alignItems: "center", gap: "5px", color: "#71717a", fontSize: "14px", margin: 0 }, children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [_jsx("path", { d: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" }), _jsx("circle", { cx: "12", cy: "10", r: "3" })] }), locStr] }))] })] }, p.id));
|
|
124
|
+
}) })] }));
|
|
125
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { ProjectPortfolio } from "./ProjectPortfolio";
|
|
2
|
+
export type { ProjectPortfolioProps } from "./ProjectPortfolio";
|
|
3
|
+
export { ProjectPortfolioClient } from "./ProjectPortfolioClient";
|
|
4
|
+
export type { ProjectPortfolioClientProps } from "./ProjectPortfolioClient";
|
|
5
|
+
export { ProjectDetail } from "./ProjectDetail";
|
|
6
|
+
export type { ProjectDetailProps } from "./ProjectDetail";
|
|
7
|
+
export { SimilarProjects } from "./SimilarProjects";
|
|
8
|
+
export type { SimilarProjectsProps } from "./SimilarProjects";
|
|
9
|
+
export { GalleryCarousel } from "./GalleryCarousel";
|
|
10
|
+
export type { GalleryCarouselProps } from "./GalleryCarousel";
|
|
11
|
+
export { ProjectMenu, fetchProjectMenuData, createMenuHandler } from "./ProjectMenu";
|
|
12
|
+
export type { ProjectMenuProps } from "./ProjectMenu";
|
|
13
|
+
export { ProjectMenuClient } from "./ProjectMenuClient";
|
|
14
|
+
export type { ProjectMenuClientProps } from "./ProjectMenuClient";
|
|
15
|
+
export { ProjectCard } from "./ProjectCard";
|
|
16
|
+
export type { CardVariant } from "./ProjectCard";
|
|
17
|
+
export type { Project, CustomFieldSchema, CustomFieldValue, LocationValue, Media, } from "./types";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACpF,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,KAAK,GACN,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ProjectPortfolio } from "./ProjectPortfolio";
|
|
2
|
+
export { ProjectPortfolioClient } from "./ProjectPortfolioClient";
|
|
3
|
+
export { ProjectDetail } from "./ProjectDetail";
|
|
4
|
+
export { SimilarProjects } from "./SimilarProjects";
|
|
5
|
+
export { GalleryCarousel } from "./GalleryCarousel";
|
|
6
|
+
export { ProjectMenu, fetchProjectMenuData, createMenuHandler } from "./ProjectMenu";
|
|
7
|
+
export { ProjectMenuClient } from "./ProjectMenuClient";
|
|
8
|
+
export { ProjectCard } from "./ProjectCard";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface FieldOption {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
url?: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface CustomFieldSchema {
|
|
7
|
+
key: string;
|
|
8
|
+
name: string;
|
|
9
|
+
type: "text" | "number" | "select" | "multi-select" | "location";
|
|
10
|
+
options: string[] | FieldOption[];
|
|
11
|
+
option_urls?: Record<string, string>;
|
|
12
|
+
is_filterable: boolean;
|
|
13
|
+
sort_order: number;
|
|
14
|
+
display_position?: "badge_overlay" | "tags" | "metadata" | "hidden";
|
|
15
|
+
}
|
|
16
|
+
export interface Media {
|
|
17
|
+
id: string;
|
|
18
|
+
project_id: string;
|
|
19
|
+
url: string;
|
|
20
|
+
alt: string;
|
|
21
|
+
caption?: string | null;
|
|
22
|
+
description?: string | null;
|
|
23
|
+
is_primary: boolean;
|
|
24
|
+
sort_order: number;
|
|
25
|
+
}
|
|
26
|
+
export interface LocationValue {
|
|
27
|
+
city?: string;
|
|
28
|
+
state?: string;
|
|
29
|
+
}
|
|
30
|
+
export type CustomFieldValue = string | number | string[] | LocationValue | null;
|
|
31
|
+
export interface Project {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
slug: string;
|
|
35
|
+
blurb: string;
|
|
36
|
+
description: string;
|
|
37
|
+
image_url: string | null;
|
|
38
|
+
is_featured: boolean;
|
|
39
|
+
is_published?: boolean;
|
|
40
|
+
custom_field_values: Record<string, CustomFieldValue>;
|
|
41
|
+
created_at: string;
|
|
42
|
+
updated_at: string;
|
|
43
|
+
media: Media[];
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,UAAU,CAAA;IAChE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,aAAa,EAAE,OAAO,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;CACpE;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,aAAa,GAAG,IAAI,CAAA;AAEhF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,KAAK,EAAE,CAAA;CACf"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chiselandco/nexus",
|
|
3
|
+
"version": "2.2.6",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectPortfolioClient (with built-in filtering), ProjectDetail, SimilarProjects, ProjectMenu, ProjectMenuClient, and GalleryCarousel. Pass a clientSlug and apiBase — done.",
|
|
8
|
+
"keywords": ["nextjs", "react", "portfolio", "projects", "megamenu", "gallery", "filtering"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"next": ">=13.0.0",
|
|
27
|
+
"react": ">=18.0.0",
|
|
28
|
+
"react-dom": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/react": "^18.0.0",
|
|
32
|
+
"@types/react-dom": "^18.0.0",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|