project-portfolio 1.0.5 → 1.0.8

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 +1 @@
1
- {"version":3,"file":"ProjectCard.d.ts","sourceRoot":"","sources":["../src/ProjectCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAmC,MAAM,SAAS,CAAA;AAE1F,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,OAAgB,EAChB,QAAsB,GACvB,EAAE,gBAAgB,2CAkPlB"}
1
+ {"version":3,"file":"ProjectCard.d.ts","sourceRoot":"","sources":["../src/ProjectCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAmC,MAAM,SAAS,CAAA;AAE1F,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,OAAgB,EAChB,QAAsB,GACvB,EAAE,gBAAgB,2CAyPlB"}
@@ -45,6 +45,7 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
45
45
  const href = `${basePath}/${project.slug}`;
46
46
  if (variant === "compact") {
47
47
  return (_jsxs("a", { href: href, style: {
48
+ all: "revert",
48
49
  display: "flex",
49
50
  alignItems: "center",
50
51
  gap: "1rem",
@@ -54,6 +55,9 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
54
55
  borderRadius: "2px",
55
56
  textDecoration: "none",
56
57
  transition: "border-color 0.2s, box-shadow 0.2s",
58
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
59
+ boxSizing: "border-box",
60
+ color: "inherit",
57
61
  }, children: [_jsx("div", { style: {
58
62
  position: "relative",
59
63
  width: "80px",
@@ -65,6 +69,7 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
65
69
  }, children: imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: { width: "100%", height: "100%", objectFit: "cover" } })) : (_jsx("div", { style: { width: "100%", height: "100%", backgroundColor: "#e4e4e7" } })) }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px", minWidth: 0 }, children: [_jsx("p", { style: { fontWeight: 700, color: "#18181b", fontSize: "14px", margin: 0, lineHeight: 1.4, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: project.title }), badgeValue && (_jsx("p", { style: { fontSize: "12px", color: "#71717a", margin: 0 }, children: badgeValue })), compactTags.length > 0 && (_jsx("p", { style: { fontSize: "12px", color: "#f18a00", margin: 0, lineHeight: 1.4 }, children: compactTags.join(" · ") }))] })] }));
66
70
  }
67
71
  return (_jsxs("article", { style: {
72
+ all: "revert",
68
73
  backgroundColor: "#fff",
69
74
  borderRadius: "2px",
70
75
  boxShadow: "0 2px 8px 0 rgba(0,0,0,0.10), 0 0 0 1px rgba(0,0,0,0.07)",
@@ -72,6 +77,8 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
72
77
  display: "flex",
73
78
  flexDirection: "column",
74
79
  transition: "box-shadow 0.3s",
80
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
81
+ boxSizing: "border-box",
75
82
  }, children: [_jsxs("div", { style: { position: "relative", aspectRatio: "16/10", overflow: "hidden", flexShrink: 0 }, children: [imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, loading: priority ? "eager" : "lazy", style: { width: "100%", height: "100%", objectFit: "cover" } })) : (_jsx("div", { style: { position: "absolute", inset: 0, backgroundColor: "#f4f4f5" } })), _jsx("div", { style: {
76
83
  position: "absolute",
77
84
  inset: 0,
@@ -106,7 +113,7 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
106
113
  fontSize: "11px",
107
114
  fontWeight: 600,
108
115
  padding: "3px 10px",
109
- }, children: badgeValue })), _jsx("h3", { style: { color: "#fff", fontWeight: 700, fontSize: "20px", lineHeight: 1.3, margin: 0 }, children: project.title }), locationString && (_jsx("p", { style: { color: "rgba(255,255,255,0.8)", fontSize: "14px", margin: 0 }, children: locationString }))] })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", flex: 1, padding: "1.5rem" }, children: [(project.blurb || project.description) && (_jsx("p", { style: { fontSize: "14px", color: "#3f3f46", lineHeight: 1.6, margin: "0 0 16px 0" }, children: project.blurb || project.description })), tagFields.map((field) => {
116
+ }, children: badgeValue })), _jsx("h3", { style: { all: "revert", color: "#fff", fontWeight: 700, fontSize: "20px", lineHeight: 1.3, margin: 0, fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }, children: project.title }), locationString && (_jsx("p", { style: { color: "rgba(255,255,255,0.8)", fontSize: "14px", margin: 0 }, children: locationString }))] })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", flex: 1, padding: "1.5rem" }, children: [(project.blurb || project.description) && (_jsx("p", { style: { fontSize: "14px", color: "#3f3f46", lineHeight: 1.6, margin: "0 0 16px 0" }, children: project.blurb || project.description })), tagFields.map((field) => {
110
117
  const vals = parseMultiValue(project.custom_field_values[field.key]);
111
118
  if (vals.length === 0)
112
119
  return null;
@@ -124,5 +131,5 @@ export function ProjectCard({ project, schema, priority, variant = "card", baseP
124
131
  };
125
132
  return url ? (_jsx("a", { href: url, style: tagStyle, children: val }, `${project.id}-${field.key}-${i}`)) : (_jsx("span", { style: tagStyle, children: val }, `${project.id}-${field.key}-${i}`));
126
133
  }) })] }, `${project.id}-${field.key}`));
127
- }), _jsx("div", { style: { flex: 1 } }), _jsxs("div", { style: { marginTop: "16px", borderTop: "1px solid #e4e4e7", paddingTop: "12px", display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: "16px" }, children: [footerStat ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [_jsx("span", { style: { fontSize: "12px", color: "#a1a1aa" }, children: footerStat.label }), _jsx("span", { style: { color: "#18181b", fontWeight: 700, fontSize: "16px" }, children: footerStat.formatted })] })) : _jsx("div", {}), _jsx("a", { href: href, style: { color: "#f18a00", fontWeight: 600, fontSize: "14px", textDecoration: "none", flexShrink: 0 }, children: "Details \u2192" })] })] })] }));
134
+ }), _jsx("div", { style: { flex: 1 } }), _jsxs("div", { style: { marginTop: "16px", borderTop: "1px solid #e4e4e7", paddingTop: "12px", display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: "16px" }, children: [footerStat ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [_jsx("span", { style: { fontSize: "12px", color: "#a1a1aa" }, children: footerStat.label }), _jsx("span", { style: { color: "#18181b", fontWeight: 700, fontSize: "16px" }, children: footerStat.formatted })] })) : _jsx("div", {}), _jsx("a", { href: href, style: { all: "revert", color: "#f18a00", fontWeight: 600, fontSize: "14px", textDecoration: "none", flexShrink: 0, fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }, children: "Details \u2192" })] })] })] }));
128
135
  }
@@ -5,29 +5,19 @@ export interface ProjectPortfolioProps {
5
5
  apiBase: string;
6
6
  /** Base path for project detail links. Defaults to "/projects" */
7
7
  basePath?: string;
8
+ /**
9
+ * Seconds to cache via Next.js Data Cache on production deployments.
10
+ * React.cache() always deduplicates within a single render in all environments.
11
+ * Defaults to 60.
12
+ */
13
+ revalidate?: number;
8
14
  }
9
15
  /**
10
- * ProjectPortfolio — self-contained async server component.
16
+ * ProjectPortfolio — pure self-fetching card grid.
11
17
  *
12
- * Drop it into any Next.js App Router page:
13
- *
14
- * import { ProjectPortfolio } from "project-portfolio"
15
- *
16
- * export default function Page() {
17
- * return (
18
- * <ProjectPortfolio
19
- * clientSlug="my-client"
20
- * apiBase="https://your-api.com"
21
- * />
22
- * )
23
- * }
24
- *
25
- * Requirements: Next.js 13+ App Router, React 18+
26
- */
27
- /**
28
- * ProjectPortfolio — pure card grid, no chrome.
29
- *
30
- * Fetches client and project data directly from the API.
18
+ * Caching works in all environments:
19
+ * - Everywhere: React.cache() deduplicates fetches within a single render
20
+ * - Production: next.revalidate caches across requests for `revalidate` seconds
31
21
  *
32
22
  * Usage:
33
23
  * import { ProjectPortfolio } from "chisel-project-portfolio"
@@ -36,8 +26,6 @@ export interface ProjectPortfolioProps {
36
26
  * clientSlug="my-client"
37
27
  * apiBase="https://your-api.com"
38
28
  * />
39
- *
40
- * Requirements: Next.js 13+ App Router, React 18+
41
29
  */
42
- export declare function ProjectPortfolio({ clientSlug, apiBase, basePath, }: ProjectPortfolioProps): Promise<import("react/jsx-runtime").JSX.Element>;
30
+ export declare function ProjectPortfolio({ clientSlug, apiBase, basePath, revalidate, }: ProjectPortfolioProps): Promise<import("react/jsx-runtime").JSX.Element>;
43
31
  //# sourceMappingURL=ProjectPortfolio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectPortfolio.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolio.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAyDD;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,QAAsB,GACvB,EAAE,qBAAqB,oDA8BvB"}
1
+ {"version":3,"file":"ProjectPortfolio.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolio.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAuED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,UAAe,GAChB,EAAE,qBAAqB,oDA8BvB"}
@@ -1,10 +1,21 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cache } from "react";
2
3
  import { ProjectCard } from "./ProjectCard";
3
- const fetchPortfolioData = async (apiBase, clientSlug) => {
4
+ // Two-layer caching strategy:
5
+ // 1. React.cache() — deduplicates within a single render in ALL environments
6
+ // (preview, local, production). If this component appears twice on one page,
7
+ // the API is only hit once.
8
+ // 2. next: { revalidate } — Next.js Data Cache, caches across multiple requests
9
+ // on production deployments. Silently ignored in preview/local — no crash,
10
+ // just falls back to per-render dedup from React.cache().
11
+ const fetchPortfolioData = cache(async (apiBase, clientSlug, revalidate) => {
4
12
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
13
+ // next.revalidate is used where supported (production); silently ignored elsewhere.
14
+ // Either way React.cache() above ensures no duplicate fetches within one render.
15
+ const fetchOpts = { next: { revalidate } };
5
16
  const [clientRes, projectsRes] = await Promise.all([
6
- fetch(`${apiBase}/api/v1/clients/${clientSlug}`),
7
- fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects`),
17
+ fetch(`${apiBase}/api/v1/clients/${clientSlug}`, fetchOpts),
18
+ fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects`, fetchOpts),
8
19
  ]);
9
20
  const clientJson = clientRes.ok
10
21
  ? await clientRes.json()
@@ -26,29 +37,13 @@ const fetchPortfolioData = async (apiBase, clientSlug) => {
26
37
  projects: (_j = projectsJson.data) !== null && _j !== void 0 ? _j : [],
27
38
  schema,
28
39
  };
29
- };
40
+ });
30
41
  /**
31
- * ProjectPortfolio — self-contained async server component.
42
+ * ProjectPortfolio — pure self-fetching card grid.
32
43
  *
33
- * Drop it into any Next.js App Router page:
34
- *
35
- * import { ProjectPortfolio } from "project-portfolio"
36
- *
37
- * export default function Page() {
38
- * return (
39
- * <ProjectPortfolio
40
- * clientSlug="my-client"
41
- * apiBase="https://your-api.com"
42
- * />
43
- * )
44
- * }
45
- *
46
- * Requirements: Next.js 13+ App Router, React 18+
47
- */
48
- /**
49
- * ProjectPortfolio — pure card grid, no chrome.
50
- *
51
- * Fetches client and project data directly from the API.
44
+ * Caching works in all environments:
45
+ * - Everywhere: React.cache() deduplicates fetches within a single render
46
+ * - Production: next.revalidate caches across requests for `revalidate` seconds
52
47
  *
53
48
  * Usage:
54
49
  * import { ProjectPortfolio } from "chisel-project-portfolio"
@@ -57,11 +52,9 @@ const fetchPortfolioData = async (apiBase, clientSlug) => {
57
52
  * clientSlug="my-client"
58
53
  * apiBase="https://your-api.com"
59
54
  * />
60
- *
61
- * Requirements: Next.js 13+ App Router, React 18+
62
55
  */
63
- export async function ProjectPortfolio({ clientSlug, apiBase, basePath = "/projects", }) {
64
- const { projects, schema } = await fetchPortfolioData(apiBase, clientSlug);
56
+ export async function ProjectPortfolio({ clientSlug, apiBase, basePath = "/projects", revalidate = 60, }) {
57
+ const { projects, schema } = await fetchPortfolioData(apiBase, clientSlug, revalidate);
65
58
  if (projects.length === 0) {
66
59
  return (_jsx("div", { style: { textAlign: "center", padding: "4rem 0" }, children: _jsx("p", { style: { color: "#71717a" }, children: "No projects found." }) }));
67
60
  }
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "project-portfolio",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "description": "Self-contained project portfolio component for Next.js App Router. Drop in one component, pass a clientSlug and apiBase, done.",
5
- "keywords": [
6
- "nextjs",
7
- "react",
8
- "portfolio",
9
- "projects"
10
- ],
5
+ "keywords": ["nextjs", "react", "portfolio", "projects"],
11
6
  "license": "MIT",
12
7
  "type": "module",
13
8
  "main": "./dist/index.js",
@@ -19,9 +14,7 @@
19
14
  "types": "./dist/index.d.ts"
20
15
  }
21
16
  },
22
- "files": [
23
- "dist"
24
- ],
17
+ "files": ["dist"],
25
18
  "scripts": {
26
19
  "build": "tsc",
27
20
  "prepublishOnly": "npm run build"
@@ -32,10 +25,8 @@
32
25
  "react-dom": ">=18.0.0"
33
26
  },
34
27
  "devDependencies": {
35
- "@types/react": "^18.3.28",
36
- "@types/react-dom": "^18.3.7",
37
- "react": "^19.2.4",
38
- "react-dom": "^19.2.4",
28
+ "@types/react": "^18.0.0",
29
+ "@types/react-dom": "^18.0.0",
39
30
  "typescript": "^5.0.0"
40
31
  }
41
32
  }