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.
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cache } from "react";
3
+ import { ProjectCard } from "./ProjectCard";
3
4
  // ─── Helpers ─────────────────────────────────────────────────────────────────
4
5
  function parseMultiValue(raw) {
5
6
  if (Array.isArray(raw))
@@ -19,45 +20,90 @@ function dedupeByKey(arr) {
19
20
  }
20
21
  // ─── Data fetching ────────────────────────────────────────────────────────────
21
22
  const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate) => {
22
- var _a, _b, _c;
23
+ var _a, _b, _c, _d, _e;
23
24
  const fetchOpts = revalidate > 0 ? { next: { revalidate } } : {};
25
+ const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
24
26
  try {
25
- const res = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts);
27
+ const [res, fieldsRes] = await Promise.all([
28
+ fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`, fetchOpts),
29
+ fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`, fetchOpts),
30
+ ]);
26
31
  const json = res.ok ? await res.json() : null;
27
32
  const allProjects = (_a = json === null || json === void 0 ? void 0 : json.data) !== null && _a !== void 0 ? _a : [];
28
33
  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 : []);
29
- return { allProjects, schema };
34
+ const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
35
+ const fieldOptionsMap = {};
36
+ for (const field of ((_d = fieldsJson.fields) !== null && _d !== void 0 ? _d : [])) {
37
+ const map = {};
38
+ for (const opt of ((_e = field.options) !== null && _e !== void 0 ? _e : [])) {
39
+ if (typeof opt === "object" && opt.id && opt.label) {
40
+ map[opt.id] = opt.label;
41
+ map[opt.label] = opt.label;
42
+ }
43
+ }
44
+ fieldOptionsMap[field.key] = map;
45
+ }
46
+ return { allProjects, schema, fieldOptionsMap };
30
47
  }
31
- catch (_d) {
32
- return { allProjects: [], schema: [] };
48
+ catch (_f) {
49
+ return { allProjects: [], schema: [], fieldOptionsMap: {} };
33
50
  }
34
51
  });
35
52
  // ─── Component ────────────────────────────────────────────────────────────────
36
- export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, }) {
37
- const { allProjects, schema } = await fetchSimilarData(apiBase, clientSlug, revalidate);
53
+ export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, variant = "list", projectSlugs, }) {
54
+ const { allProjects, schema, fieldOptionsMap } = await fetchSimilarData(apiBase, clientSlug, revalidate);
38
55
  const badgeField = schema.find((f) => f.display_position === "badge_overlay");
39
56
  const locationField = schema.find((f) => f.type === "location");
40
57
  const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
41
- const filterEntries = Object.entries(filters);
42
- const similar = allProjects
43
- .filter((p) => {
44
- // Exclude the specified slug if provided
45
- if (excludeSlug && p.slug === excludeSlug)
46
- return false;
47
- // Every filter key/value must match the project's custom field values
48
- return filterEntries.every(([key, value]) => {
49
- const fieldValues = parseMultiValue(p.custom_field_values[key]);
50
- return fieldValues.some((v) => v.toLowerCase() === value.toLowerCase());
51
- });
52
- })
53
- .slice(0, maxItems);
58
+ let similar;
59
+ if (projectSlugs && projectSlugs.length > 0) {
60
+ // Manual mode — show exactly these slugs in the order provided
61
+ const projectMap = new Map(allProjects.map((p) => [p.slug, p]));
62
+ similar = projectSlugs
63
+ .filter((slug) => slug !== excludeSlug)
64
+ .map((slug) => projectMap.get(slug))
65
+ .filter((p) => p !== undefined)
66
+ .slice(0, maxItems);
67
+ }
68
+ else {
69
+ // Filter mode — match by custom field values
70
+ const filterEntries = Object.entries(filters);
71
+ similar = allProjects
72
+ .filter((p) => {
73
+ if (excludeSlug && p.slug === excludeSlug)
74
+ return false;
75
+ return filterEntries.every(([key, value]) => {
76
+ const fieldValues = parseMultiValue(p.custom_field_values[key]);
77
+ return fieldValues.some((v) => v.toLowerCase() === value.toLowerCase());
78
+ });
79
+ })
80
+ .slice(0, maxItems);
81
+ }
54
82
  if (similar.length === 0)
55
83
  return null;
56
- return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", paddingTop: "3rem", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [_jsx("style", { children: `
84
+ 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: "More Work" }), _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "clamp(20px, 4vw, 28px)", margin: 0, fontFamily: font }, children: "Similar Projects" })] }), _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" }) })] })] }));
85
+ // ── Card variant ─────────────────────────────────────────────────────────────
86
+ if (variant === "card") {
87
+ return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [_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
+ ` }), 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))) })] }));
97
+ }
98
+ // ── List variant (default) ────────────────────────────────────────────────────
99
+ return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [_jsx("style", { children: `
57
100
  .chisel-similar-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
58
101
  @media (min-width: 640px) { .chisel-similar-grid { grid-template-columns: repeat(2, 1fr); } }
59
102
  @media (min-width: 1024px) { .chisel-similar-grid { grid-template-columns: repeat(3, 1fr); } }
60
- ` }), _jsxs("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: "2rem" }, 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: "More Work" }), _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "28px", margin: 0, fontFamily: font }, children: "Similar Projects" })] }), _jsxs("a", { href: basePath, style: { color: "#18181b", fontWeight: 600, fontSize: "15px", textDecoration: "none", display: "flex", alignItems: "center", gap: "6px", fontFamily: font }, 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" }) })] })] }), _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
103
+ .chisel-similar-header { display: flex; flex-direction: column; gap: 12px; margin-bottom: 2rem; }
104
+ @media (min-width: 640px) { .chisel-similar-header { flex-direction: row; align-items: flex-end; justify-content: space-between; } }
105
+ .chisel-similar-img { height: 56vw; min-height: 160px; max-height: 220px; }
106
+ ` }), header, _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
61
107
  var _a, _b, _c, _d, _e;
62
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;
63
109
  const badge = badgeField
@@ -67,11 +113,11 @@ export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, a
67
113
  ? p.custom_field_values[locationField.key]
68
114
  : null;
69
115
  const locStr = loc ? [loc.city, loc.state].filter(Boolean).join(", ") : null;
70
- return (_jsxs("a", { href: `${basePath}/${p.slug}`, style: { textDecoration: "none", color: "inherit", display: "block", borderBottom: "1px solid #e4e4e7", fontFamily: font }, children: [_jsxs("div", { style: { position: "relative", width: "100%", height: "220px", 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: {
116
+ 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: {
71
117
  position: "absolute", top: "12px", left: "12px",
72
118
  backgroundColor: "oklch(0.78 0.16 85)", color: "#fff",
73
119
  fontSize: "10px", fontWeight: 700, textTransform: "uppercase",
74
120
  letterSpacing: "0.1em", padding: "4px 10px",
75
- }, children: badge }))] }), _jsxs("div", { style: { paddingBottom: "1.25rem" }, children: [_jsx("h3", { style: { color: "#18181b", fontWeight: 700, fontSize: "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));
121
+ }, 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));
76
122
  }) })] }));
77
123
  }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type { ProjectDetailProps } from "./ProjectDetail";
7
7
  export { SimilarProjects } from "./SimilarProjects";
8
8
  export type { SimilarProjectsProps } from "./SimilarProjects";
9
9
  export { GalleryCarousel } from "./GalleryCarousel";
10
+ export type { GalleryCarouselProps } from "./GalleryCarousel";
10
11
  export { ProjectMenu, fetchProjectMenuData, createMenuHandler } from "./ProjectMenu";
11
12
  export type { ProjectMenuProps } from "./ProjectMenu";
12
13
  export { ProjectMenuClient } from "./ProjectMenuClient";
@@ -1 +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,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"}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-portfolio",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "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.",
5
5
  "keywords": ["nextjs", "react", "portfolio", "projects", "megamenu", "gallery", "filtering"],
6
6
  "license": "MIT",