@chiselandco/nexus 3.0.0 → 3.1.1
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 +42 -63
- package/dist/FilteredPortfolio.d.ts +1 -1
- package/dist/FilteredPortfolio.d.ts.map +1 -1
- package/dist/GalleryCarousel.d.ts.map +1 -1
- package/dist/GalleryCarousel.js +5 -4
- package/dist/ProjectMenuClient.d.ts.map +1 -1
- package/dist/ProjectMenuClient.js +5 -4
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +21 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Self-contained project portfolio components for Next.js App Router. Pass a `clientSlug`, `apiBase`, and `apiKey` — each component fetches, caches, and renders everything it needs with no client-side waterfall requests.
|
|
4
4
|
|
|
5
|
-
**Version:** 3.
|
|
5
|
+
**Version:** 3.1.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ The most common full setup — a filterable projects grid, a detail page with si
|
|
|
29
29
|
|
|
30
30
|
```tsx
|
|
31
31
|
// app/projects/page.tsx
|
|
32
|
-
import {
|
|
32
|
+
import { ProjectPortfolio } from "@chiselandco/nexus"
|
|
33
33
|
|
|
34
34
|
export default async function ProjectsPage({
|
|
35
35
|
searchParams,
|
|
@@ -37,7 +37,7 @@ export default async function ProjectsPage({
|
|
|
37
37
|
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
38
38
|
}) {
|
|
39
39
|
return (
|
|
40
|
-
<
|
|
40
|
+
<ProjectPortfolio
|
|
41
41
|
clientSlug="your-client-slug"
|
|
42
42
|
apiBase="https://your-api.com"
|
|
43
43
|
apiKey={process.env.YOUR_CLIENT_API_KEY!}
|
|
@@ -115,58 +115,11 @@ export function Nav() {
|
|
|
115
115
|
|
|
116
116
|
## Components
|
|
117
117
|
|
|
118
|
-
### `FilteredPortfolio`
|
|
119
|
-
|
|
120
|
-
Server component. The recommended primary projects grid. Fetches all projects once, reads `filter[key]=` URL params server-side to narrow results using AND-across-fields / OR-within-field logic, then renders a project count toolbar with a `FilterSidebar` trigger above a responsive card grid.
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
import { FilteredPortfolio } from "@chiselandco/nexus"
|
|
124
|
-
|
|
125
|
-
export default async function ProjectsPage({
|
|
126
|
-
searchParams,
|
|
127
|
-
}: {
|
|
128
|
-
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
129
|
-
}) {
|
|
130
|
-
return (
|
|
131
|
-
<FilteredPortfolio
|
|
132
|
-
clientSlug="your-client-slug"
|
|
133
|
-
apiBase="https://your-api.com"
|
|
134
|
-
apiKey={process.env.YOUR_CLIENT_API_KEY!}
|
|
135
|
-
basePath="/projects"
|
|
136
|
-
searchParams={await searchParams}
|
|
137
|
-
/>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
| Prop | Type | Required | Default | Description |
|
|
143
|
-
|---|---|---|---|---|
|
|
144
|
-
| `clientSlug` | `string` | Yes | — | Client identifier passed to the API |
|
|
145
|
-
| `apiBase` | `string` | Yes | — | Base URL of the Chisel API |
|
|
146
|
-
| `apiKey` | `string` | Yes | — | Client API key — always pass via environment variable, never hardcode |
|
|
147
|
-
| `searchParams` | `Record<string, string \| string[] \| undefined>` | Yes | — | Resolved Next.js `searchParams` — await it before passing in Next.js 16+ |
|
|
148
|
-
| `basePath` | `string` | No | `"/projects"` | Base path for project detail card links |
|
|
149
|
-
| `filterKeys` | `string[]` | No | All eligible fields | Ordered list of field keys to expose in the filter drawer |
|
|
150
|
-
| `font` | `string` | No | System font | Font family string |
|
|
151
|
-
| `noCache` | `boolean` | No | `false` | Sets `cache: "no-store"` — useful during development |
|
|
152
|
-
| `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds |
|
|
153
|
-
| `filterBy` | `{ field: string; value: string }` | No | — | Pre-filter projects by any custom field value. See [Filtering by field](#filtering-by-field). |
|
|
154
|
-
|
|
155
|
-
#### URL param format
|
|
156
|
-
|
|
157
|
-
Filters are written to and read from URL params in the form `filter[fieldKey]=slug1,slug2`. Multiple values within a field are OR'd; multiple fields are AND'd.
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
/projects?filter[application]=hospitality,education&filter[systems]=spacematic
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
118
|
### `FilterSidebar`
|
|
166
119
|
|
|
167
120
|
Client component (`"use client"`). Renders an "Advanced Filters" trigger that opens a right-side drawer with one section per filterable field. Pills are solid black when active and outlined when inactive. Filter state is written to URL params so filtered views are shareable and survive page refresh.
|
|
168
121
|
|
|
169
|
-
|
|
122
|
+
Use alongside `ProjectPortfolio` when you want user-driven filtering — place it wherever suits your layout and pass the same `searchParams` to `ProjectPortfolio`.
|
|
170
123
|
|
|
171
124
|
```tsx
|
|
172
125
|
import { FilterSidebar } from "@chiselandco/nexus"
|
|
@@ -521,7 +474,7 @@ export function Nav() {
|
|
|
521
474
|
| `dataUrl` | `string` | No* | — | URL of a local API route created with `createMenuHandler()` — recommended for production |
|
|
522
475
|
| `clientSlug` | `string` | No* | — | Client slug for direct fetch mode |
|
|
523
476
|
| `apiBase` | `string` | No* | — | API base URL for direct fetch mode |
|
|
524
|
-
| `apiKey` | `string` | No* |
|
|
477
|
+
| `apiKey` | `string` | No* | ��� | Client API key for direct fetch mode |
|
|
525
478
|
| `menuId` | `string` | No | — | Slug of a curated menu |
|
|
526
479
|
| `basePath` | `string` | Yes | — | Base path for project detail links |
|
|
527
480
|
| `viewAllPath` | `string` | Yes | — | Path for the "View All Projects" link |
|
|
@@ -536,7 +489,7 @@ export function Nav() {
|
|
|
536
489
|
|
|
537
490
|
### `ProjectPortfolio`
|
|
538
491
|
|
|
539
|
-
Server component. Fetches all projects and renders a responsive
|
|
492
|
+
Server component. The primary projects grid. Fetches all projects, reads `filter[key]=` URL params server-side to narrow results, and renders a responsive card grid (1 col mobile / 2 col tablet / 3 col desktop). Pair with `FilterSidebar` when you want user-driven filtering — place it wherever suits your layout.
|
|
540
493
|
|
|
541
494
|
```tsx
|
|
542
495
|
// app/projects/page.tsx
|
|
@@ -617,11 +570,11 @@ export default function ProjectsPage() {
|
|
|
617
570
|
|
|
618
571
|
## Filtering by field
|
|
619
572
|
|
|
620
|
-
`
|
|
573
|
+
`ProjectPortfolio`, `ProjectPortfolioClient`, and `SimilarProjects` all accept an optional `filterBy` prop. It pre-filters the project list by any custom field value before any user-driven filters are applied.
|
|
621
574
|
|
|
622
575
|
```tsx
|
|
623
576
|
// Only show projects where custom field "side" equals "architectural" or "both"
|
|
624
|
-
<
|
|
577
|
+
<ProjectPortfolio
|
|
625
578
|
clientSlug="hollaender"
|
|
626
579
|
apiBase="https://your-api.com"
|
|
627
580
|
apiKey={process.env.HOLLAENDER_API_KEY!}
|
|
@@ -631,7 +584,7 @@ export default function ProjectsPage() {
|
|
|
631
584
|
/>
|
|
632
585
|
|
|
633
586
|
// Only show projects where custom field "side" equals "speedrail" or "both"
|
|
634
|
-
<
|
|
587
|
+
<ProjectPortfolio
|
|
635
588
|
clientSlug="hollaender"
|
|
636
589
|
apiBase="https://your-api.com"
|
|
637
590
|
apiKey={process.env.HOLLAENDER_API_KEY!}
|
|
@@ -655,10 +608,24 @@ The `"both"` fallback is built in — if a project's field value is `"both"` it
|
|
|
655
608
|
|
|
656
609
|
```tsx
|
|
657
610
|
// v2
|
|
658
|
-
<
|
|
611
|
+
<ProjectPortfolio side="architectural" />
|
|
659
612
|
|
|
660
613
|
// v3
|
|
661
|
-
<
|
|
614
|
+
<ProjectPortfolio filterBy={{ field: "side", value: "architectural" }} />
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Migration from v3.0 `FilteredPortfolio`
|
|
618
|
+
|
|
619
|
+
`FilteredPortfolio` was removed in v3.1. Use `ProjectPortfolio` directly — it has the same props. Pair with `FilterSidebar` if you want a filter drawer.
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
// v3.0
|
|
623
|
+
import { FilteredPortfolio } from "@chiselandco/nexus"
|
|
624
|
+
<FilteredPortfolio clientSlug="..." apiBase="..." apiKey={...} searchParams={searchParams} />
|
|
625
|
+
|
|
626
|
+
// v3.1
|
|
627
|
+
import { ProjectPortfolio } from "@chiselandco/nexus"
|
|
628
|
+
<ProjectPortfolio clientSlug="..." apiBase="..." apiKey={...} searchParams={searchParams} />
|
|
662
629
|
```
|
|
663
630
|
|
|
664
631
|
---
|
|
@@ -667,10 +634,9 @@ The `"both"` fallback is built in — if a project's field value is `"both"` it
|
|
|
667
634
|
|
|
668
635
|
| Component | Type | Notes |
|
|
669
636
|
|---|---|---|
|
|
670
|
-
| `
|
|
671
|
-
| `FilterSidebar` | Client |
|
|
672
|
-
| `
|
|
673
|
-
| `ProjectPortfolioClient` | Client | For use inside client trees |
|
|
637
|
+
| `ProjectPortfolio` | Server | Primary projects grid |
|
|
638
|
+
| `FilterSidebar` | Client | Optional filter drawer — pair with `ProjectPortfolio` |
|
|
639
|
+
| `ProjectPortfolioClient` | Client | For use inside client component trees |
|
|
674
640
|
| `ProjectDetail` | Server | Full project detail page |
|
|
675
641
|
| `GalleryCarousel` | Client | Used internally by `ProjectDetail` |
|
|
676
642
|
| `SimilarProjects` | Server | After `ProjectDetail` on detail pages |
|
|
@@ -685,7 +651,6 @@ All server components must be rendered in a server context. If your parent compo
|
|
|
685
651
|
|
|
686
652
|
| Component | Server cache | Client cache |
|
|
687
653
|
|---|---|---|
|
|
688
|
-
| `FilteredPortfolio` | 24h via `next.revalidate` | — |
|
|
689
654
|
| `ProjectPortfolio` | 24h via `next.revalidate` | — |
|
|
690
655
|
| `ProjectDetail` | 24h via `next.revalidate` | — |
|
|
691
656
|
| `SimilarProjects` | 24h via `next.revalidate` | — |
|
|
@@ -705,6 +670,20 @@ revalidateTag("chisel-menu-your-client-slug-main-nav")
|
|
|
705
670
|
|
|
706
671
|
---
|
|
707
672
|
|
|
673
|
+
## Image optimisation
|
|
674
|
+
|
|
675
|
+
All components that render images use the built-in `thumbUrl()` helper to request appropriately-sized variants from Vercel Blob instead of loading full-resolution originals. The helper appends `?w=<width>&q=<quality>` query params which Vercel Blob handles on-the-fly. For non-Blob image sources the URL is returned unchanged.
|
|
676
|
+
|
|
677
|
+
| Location | Display size | Requested size | Quality |
|
|
678
|
+
|---|---|---|---|
|
|
679
|
+
| `ProjectMenuClient` card thumbnails | 72×54px | 144px wide (2× retina) | 60% |
|
|
680
|
+
| `GalleryCarousel` thumbnail strip | 72px wide | 160px wide (2× retina) | 60% |
|
|
681
|
+
| `GalleryCarousel` main image | Full width | 1600px wide | 80% |
|
|
682
|
+
|
|
683
|
+
If you are hosting images outside Vercel Blob (e.g. Cloudinary, imgix, your own CDN), you can override image rendering by passing pre-transformed URLs in your media objects — `thumbUrl()` will pass them through untouched.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
708
687
|
## Publishing
|
|
709
688
|
|
|
710
689
|
```bash
|
|
@@ -52,5 +52,5 @@ export interface FilteredPortfolioProps {
|
|
|
52
52
|
* )
|
|
53
53
|
* }
|
|
54
54
|
*/
|
|
55
|
-
export declare function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchParams, filterKeys, basePath, revalidate, noCache, font, filterBy, }: FilteredPortfolioProps): Promise<import("react").JSX.Element>;
|
|
55
|
+
export declare function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchParams, filterKeys, basePath, revalidate, noCache, font, filterBy, }: FilteredPortfolioProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
56
56
|
//# sourceMappingURL=FilteredPortfolio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilteredPortfolio.d.ts","sourceRoot":"","sources":["../src/FilteredPortfolio.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IAC5D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;AA8ED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,OAAO,EACP,MAAM,EACN,YAAiB,EACjB,UAAU,EACV,QAAsB,EACtB,UAAe,EACf,OAAe,EACf,IAAI,EACJ,QAAQ,GACT,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"FilteredPortfolio.d.ts","sourceRoot":"","sources":["../src/FilteredPortfolio.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IAC5D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;AA8ED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,OAAO,EACP,MAAM,EACN,YAAiB,EACjB,UAAU,EACV,QAAsB,EACtB,UAAe,EACf,OAAe,EACf,IAAI,EACJ,QAAQ,GACT,EAAE,sBAAsB,oDAqGxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GalleryCarousel.d.ts","sourceRoot":"","sources":["../src/GalleryCarousel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAe,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"GalleryCarousel.d.ts","sourceRoot":"","sources":["../src/GalleryCarousel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAe,MAAM,SAAS,CAAA;AAUpE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAgOD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAW,EAAE,EAAE,oBAAoB,4BA2V1F"}
|
package/dist/GalleryCarousel.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
4
4
|
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
5
|
+
import { thumbUrl } from "./types";
|
|
5
6
|
const FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
6
7
|
const ACCENT = "#C8872A";
|
|
7
8
|
const INK = "#0f0f0f";
|
|
@@ -168,7 +169,7 @@ function FilterDropdown({ field, values, activeSlug, onSelect, }) {
|
|
|
168
169
|
}
|
|
169
170
|
// ─── Main component ────────────────────────────────────────────────────────
|
|
170
171
|
export function GalleryCarousel({ images, projectTitle, schema = [] }) {
|
|
171
|
-
var _a;
|
|
172
|
+
var _a, _b;
|
|
172
173
|
const router = useRouter();
|
|
173
174
|
const pathname = usePathname();
|
|
174
175
|
const searchParams = useSearchParams();
|
|
@@ -286,7 +287,7 @@ export function GalleryCarousel({ images, projectTitle, schema = [] }) {
|
|
|
286
287
|
cursor: "pointer",
|
|
287
288
|
letterSpacing: "0.04em",
|
|
288
289
|
textDecoration: "underline",
|
|
289
|
-
}, children: "Clear filters" })] })), active && (_jsxs("div", { style: { position: "relative", width: "100%", aspectRatio: "16/9", backgroundColor: "#1a1916", overflow: "hidden" }, children: [_jsx("img", { src: active.url, alt: active.alt || projectTitle, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" } }), _jsx("div", { style: {
|
|
290
|
+
}, children: "Clear filters" })] })), active && (_jsxs("div", { style: { position: "relative", width: "100%", aspectRatio: "16/9", backgroundColor: "#1a1916", overflow: "hidden" }, children: [_jsx("img", { src: (_b = thumbUrl(active.url, 1600, 80)) !== null && _b !== void 0 ? _b : active.url, alt: active.alt || projectTitle, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" } }), _jsx("div", { style: {
|
|
290
291
|
position: "absolute",
|
|
291
292
|
inset: "50% 0 0 0",
|
|
292
293
|
background: "linear-gradient(to bottom, transparent, rgba(0,0,0,0.52))",
|
|
@@ -344,7 +345,7 @@ export function GalleryCarousel({ images, projectTitle, schema = [] }) {
|
|
|
344
345
|
marginTop: "6px",
|
|
345
346
|
paddingBottom: "2px",
|
|
346
347
|
}, children: filteredImages.map((img, i) => {
|
|
347
|
-
var _a;
|
|
348
|
+
var _a, _b;
|
|
348
349
|
const isActive = i === safeIndex;
|
|
349
350
|
return (_jsx("button", { onClick: () => setCurrent(i), "aria-label": `View image ${i + 1}`, style: {
|
|
350
351
|
position: "relative",
|
|
@@ -361,7 +362,7 @@ export function GalleryCarousel({ images, projectTitle, schema = [] }) {
|
|
|
361
362
|
outlineOffset: isActive ? "1px" : "0",
|
|
362
363
|
transition: "outline 0.12s, opacity 0.12s",
|
|
363
364
|
opacity: isActive ? 1 : 0.55,
|
|
364
|
-
}, children: _jsx("img", { src: img.url, alt: img.alt || `Image ${i + 1}`, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }, (
|
|
365
|
+
}, children: _jsx("img", { src: (_a = thumbUrl(img.url, 160, 60)) !== null && _a !== void 0 ? _a : img.url, alt: img.alt || `Image ${i + 1}`, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }, (_b = img.id) !== null && _b !== void 0 ? _b : i));
|
|
365
366
|
}) }))] }));
|
|
366
367
|
}
|
|
367
368
|
function arrowBtn(side) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMenuClient.d.ts","sourceRoot":"","sources":["../src/ProjectMenuClient.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ProjectMenuClient.d.ts","sourceRoot":"","sources":["../src/ProjectMenuClient.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAA;AAc3E,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;IACpB,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC5B,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC/C,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAkBD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,UAAU,EACV,OAAO,EACP,MAAM,EACN,MAAM,EACN,OAAe,EACf,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,UAAU,EAClB,aAAa,EAAE,iBAAiB,EAChC,cAAc,EAAE,kBAAkB,EAClC,eAAe,EAAE,mBAAoC,EACrD,eAAe,EAAE,mBAAwB,EACzC,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,IAAmB,EACnB,WAAe,GAChB,EAAE,sBAAsB,qBAqfxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
+
import { thumbUrl } from "./types";
|
|
4
5
|
function parseSingleValue(raw) {
|
|
5
6
|
if (typeof raw === "string")
|
|
6
7
|
return raw.replace(/`/g, "").trim();
|
|
@@ -318,11 +319,11 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, apiKey, menuId
|
|
|
318
319
|
margin: "0 0 16px 0",
|
|
319
320
|
display: "none",
|
|
320
321
|
}, 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) => {
|
|
321
|
-
var _a, _b, _c, _d, _e
|
|
322
|
-
const imageUrl = (
|
|
322
|
+
var _a, _b, _c, _d, _e;
|
|
323
|
+
const imageUrl = thumbUrl((_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, 144, 60);
|
|
323
324
|
const badgeRaw = badgeField ? parseSingleValue(project.custom_field_values[badgeField.key]) : null;
|
|
324
|
-
const badgeOptMap = badgeField ? ((
|
|
325
|
-
const badge = badgeRaw ? ((
|
|
325
|
+
const badgeOptMap = badgeField ? ((_d = fieldOptionsMap[badgeField.key]) !== null && _d !== void 0 ? _d : {}) : {};
|
|
326
|
+
const badge = badgeRaw ? ((_e = badgeOptMap[badgeRaw]) !== null && _e !== void 0 ? _e : badgeRaw) : null;
|
|
326
327
|
const tags = tagsField ? parseMultiValue(project.custom_field_values[tagsField.key]) : [];
|
|
327
328
|
const href = `${basePath}/${project.slug}`;
|
|
328
329
|
const isHovered = hoveredCard === project.id;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,5 @@ export { ProjectCard } from "./ProjectCard";
|
|
|
16
16
|
export type { CardVariant } from "./ProjectCard";
|
|
17
17
|
export { FilterSidebar } from "./FilterSidebar";
|
|
18
18
|
export type { FilterSidebarProps } from "./FilterSidebar";
|
|
19
|
-
export { FilteredPortfolio } from "./FilteredPortfolio";
|
|
20
|
-
export type { FilteredPortfolioProps } from "./FilteredPortfolio";
|
|
21
19
|
export type { Project, CustomFieldSchema, CustomFieldValue, LocationValue, Media, } from "./types";
|
|
22
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,
|
|
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,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,KAAK,GACN,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,4 +7,3 @@ export { ProjectMenu, fetchProjectMenuData, createMenuHandler } from "./ProjectM
|
|
|
7
7
|
export { ProjectMenuClient } from "./ProjectMenuClient";
|
|
8
8
|
export { ProjectCard } from "./ProjectCard";
|
|
9
9
|
export { FilterSidebar } from "./FilterSidebar";
|
|
10
|
-
export { FilteredPortfolio } from "./FilteredPortfolio";
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface LocationValue {
|
|
|
30
30
|
state?: string;
|
|
31
31
|
}
|
|
32
32
|
export type CustomFieldValue = string | number | string[] | LocationValue | null;
|
|
33
|
+
/**
|
|
34
|
+
* Returns a resized image URL for Vercel Blob-hosted images.
|
|
35
|
+
* Appends ?w=<width>&q=<quality> for on-the-fly resizing.
|
|
36
|
+
* Falls back to the original URL unchanged for non-Blob sources.
|
|
37
|
+
*/
|
|
38
|
+
export declare function thumbUrl(url: string | null | undefined, width: number, quality?: number): string | null;
|
|
33
39
|
export interface Project {
|
|
34
40
|
id: string;
|
|
35
41
|
title: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +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;IAClB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;CAC/D;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"}
|
|
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;IAClB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;CAC/D;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;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,GAAG,IAAI,CAanG;AAED,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
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Returns a resized image URL for Vercel Blob-hosted images.
|
|
3
|
+
* Appends ?w=<width>&q=<quality> for on-the-fly resizing.
|
|
4
|
+
* Falls back to the original URL unchanged for non-Blob sources.
|
|
5
|
+
*/
|
|
6
|
+
export function thumbUrl(url, width, quality = 75) {
|
|
7
|
+
if (!url)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const u = new URL(url);
|
|
11
|
+
if (u.hostname.endsWith(".public.blob.vercel-storage.com")) {
|
|
12
|
+
u.searchParams.set("w", String(width));
|
|
13
|
+
u.searchParams.set("q", String(quality));
|
|
14
|
+
return u.toString();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (_a) {
|
|
18
|
+
// not a valid absolute URL — return as-is
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chiselandco/nexus",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectPortfolioClient, ProjectDetail, SimilarProjects, ProjectMenu, ProjectMenuClient, GalleryCarousel,
|
|
3
|
+
"version": "3.1.1",
|
|
4
|
+
"description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectPortfolioClient, ProjectDetail, SimilarProjects, ProjectMenu, ProjectMenuClient, GalleryCarousel, and FilterSidebar. Pass a clientSlug and apiBase — done.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
7
7
|
"react",
|