@chiselandco/nexus 2.6.1 → 3.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 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:** 2.6.1
5
+ **Version:** 3.1.0
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 { FilteredPortfolio } from "@chiselandco/nexus"
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
- <FilteredPortfolio
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
- | `side` | `"architectural" \| "speedrail"` | No | — | Filter projects by website side. When omitted all projects are shown. See [Filtering by side](#filtering-by-side). |
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
- Used internally by `FilteredPortfolio` — only import it standalone if you need to build a custom layout around it.
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"
@@ -403,7 +356,7 @@ Use `variant="card"` to render baseball-card style instead of the default list s
403
356
  | `font` | `string` | No | System font stack | Font family string |
404
357
  | `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds |
405
358
  | `noCache` | `boolean` | No | `false` | Sets `cache: "no-store"` — useful during development |
406
- | `side` | `"architectural" \| "speedrail"` | No | — | Filter projects by website side. When omitted all projects are shown. |
359
+ | `filterBy` | `{ field: string; value: string }` | No | — | Pre-filter projects by any custom field value. See [Filtering by field](#filtering-by-field). |
407
360
 
408
361
  ---
409
362
 
@@ -536,7 +489,7 @@ export function Nav() {
536
489
 
537
490
  ### `ProjectPortfolio`
538
491
 
539
- Server component. Fetches all projects and renders a responsive baseball card grid (1 col mobile / 2 col tablet / 3 col desktop). Supports URL-driven filtering via `searchParams`. Prefer `FilteredPortfolio` for new projects — it includes the full filter sidebar and is the recommended default.
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 filteringplace it wherever suits your layout.
540
493
 
541
494
  ```tsx
542
495
  // app/projects/page.tsx
@@ -568,7 +521,7 @@ export default async function ProjectsPage({
568
521
  | `searchParams` | `Record<string, string \| string[] \| undefined>` | No | `{}` | Filter params — pass Next.js `searchParams` directly |
569
522
  | `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds |
570
523
  | `noCache` | `boolean` | No | `false` | Sets `cache: "no-store"` — useful during development |
571
- | `side` | `"architectural" \| "speedrail"` | No | — | Filter projects by website side. When omitted all projects are shown. |
524
+ | `filterBy` | `{ field: string; value: string }` | No | — | Pre-filter projects by any custom field value. See [Filtering by field](#filtering-by-field). |
572
525
 
573
526
  ---
574
527
 
@@ -611,39 +564,69 @@ export default function ProjectsPage() {
611
564
  | `filters` | `Record<string, string>` | No | `{}` | Active filters — filtering is instant, no API call on change |
612
565
  | `columns` | `2 \| 3` | No | `3` | Number of grid columns |
613
566
  | `font` | `string` | No | System font stack | Font family string |
614
- | `side` | `"architectural" \| "speedrail"` | No | — | Filter projects by website side. When omitted all projects are shown. |
567
+ | `filterBy` | `{ field: string; value: string }` | No | — | Pre-filter projects by any custom field value. See [Filtering by field](#filtering-by-field). |
615
568
 
616
569
  ---
617
570
 
618
- ## Filtering by side
619
-
620
- Every project returned by the API includes a `side` field: `"architectural"`, `"speedrail"`, or `"both"`. This reflects which section of the client's website a project belongs to.
571
+ ## Filtering by field
621
572
 
622
- The following components accept an optional `side` prop: `FilteredPortfolio`, `ProjectPortfolio`, `ProjectPortfolioClient`, and `SimilarProjects`.
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.
623
574
 
624
575
  ```tsx
625
- // Architectural side of the site only shows architectural + both projects
626
- <FilteredPortfolio
576
+ // Only show projects where custom field "side" equals "architectural" or "both"
577
+ <ProjectPortfolio
627
578
  clientSlug="hollaender"
628
579
  apiBase="https://your-api.com"
629
580
  apiKey={process.env.HOLLAENDER_API_KEY!}
630
581
  basePath="/architectural/projects"
631
582
  searchParams={searchParams}
632
- side="architectural"
583
+ filterBy={{ field: "side", value: "architectural" }}
633
584
  />
634
585
 
635
- // Speed-Rail side of the site only shows speedrail + both projects
636
- <FilteredPortfolio
586
+ // Only show projects where custom field "side" equals "speedrail" or "both"
587
+ <ProjectPortfolio
637
588
  clientSlug="hollaender"
638
589
  apiBase="https://your-api.com"
639
590
  apiKey={process.env.HOLLAENDER_API_KEY!}
640
591
  basePath="/speedrail/projects"
641
592
  searchParams={searchParams}
642
- side="speedrail"
593
+ filterBy={{ field: "side", value: "speedrail" }}
594
+ />
595
+
596
+ // Works with any field — not just "side"
597
+ <ProjectPortfolio
598
+ clientSlug="acme"
599
+ apiBase="https://your-api.com"
600
+ apiKey={process.env.ACME_API_KEY!}
601
+ filterBy={{ field: "region", value: "northeast" }}
643
602
  />
644
603
  ```
645
604
 
646
- When `side` is omitted the component behaves exactly as before all projects are shown. The client controls which value to pass based on the current route or context. No extra API calls are made — the filtering happens in memory after the standard fetch.
605
+ The `"both"` fallback is built in if a project's field value is `"both"` it matches any `filterBy.value`. When `filterBy` is omitted all projects are shown. No extra API calls are made — filtering happens in memory after the standard fetch.
606
+
607
+ ### Migration from v2 `side` prop
608
+
609
+ ```tsx
610
+ // v2
611
+ <ProjectPortfolio side="architectural" />
612
+
613
+ // v3
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} />
629
+ ```
647
630
 
648
631
  ---
649
632
 
@@ -651,10 +634,9 @@ When `side` is omitted the component behaves exactly as before — all projects
651
634
 
652
635
  | Component | Type | Notes |
653
636
  |---|---|---|
654
- | `FilteredPortfolio` | Server | Recommended default for project grids |
655
- | `FilterSidebar` | Client | Used internally by `FilteredPortfolio` |
656
- | `ProjectPortfolio` | Server | Simpler grid without sidebar |
657
- | `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 |
658
640
  | `ProjectDetail` | Server | Full project detail page |
659
641
  | `GalleryCarousel` | Client | Used internally by `ProjectDetail` |
660
642
  | `SimilarProjects` | Server | After `ProjectDetail` on detail pages |
@@ -669,7 +651,6 @@ All server components must be rendered in a server context. If your parent compo
669
651
 
670
652
  | Component | Server cache | Client cache |
671
653
  |---|---|---|
672
- | `FilteredPortfolio` | 24h via `next.revalidate` | — |
673
654
  | `ProjectPortfolio` | 24h via `next.revalidate` | — |
674
655
  | `ProjectDetail` | 24h via `next.revalidate` | — |
675
656
  | `SimilarProjects` | 24h via `next.revalidate` | — |
@@ -20,12 +20,16 @@ export interface FilteredPortfolioProps {
20
20
  noCache?: boolean;
21
21
  font?: string;
22
22
  /**
23
- * Filter projects by website side.
24
- * - "architectural" shows projects with side "architectural" or "both"
25
- * - "speedrail" — shows projects with side "speedrail" or "both"
26
- * - omit — shows all projects regardless of side
23
+ * Pre-filter projects by any custom field value before rendering.
24
+ * Useful for splitting a portfolio by a routing field (e.g. site section).
25
+ * e.g. filterBy={{ field: "side", value: "architectural" }}
26
+ * Projects where the field value equals the given value OR "both" are shown.
27
+ * When omitted all projects are shown.
27
28
  */
28
- side?: "architectural" | "speedrail";
29
+ filterBy?: {
30
+ field: string;
31
+ value: string;
32
+ };
29
33
  }
30
34
  /**
31
35
  * FilteredPortfolio — server component.
@@ -48,5 +52,5 @@ export interface FilteredPortfolioProps {
48
52
  * )
49
53
  * }
50
54
  */
51
- export declare function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchParams, filterKeys, basePath, revalidate, noCache, font, side, }: 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>;
52
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;;;;;OAKG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,WAAW,CAAA;CACrC;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,IAAI,GACL,EAAE,sBAAsB,wCAqGxB"}
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"}
@@ -74,7 +74,7 @@ async function fetchSchema(apiBase, clientSlug, apiKey, fetchOpts) {
74
74
  * )
75
75
  * }
76
76
  */
77
- export async function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchParams = {}, filterKeys, basePath = "/projects", revalidate = 60, noCache = false, font, side, }) {
77
+ export async function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchParams = {}, filterKeys, basePath = "/projects", revalidate = 60, noCache = false, font, filterBy, }) {
78
78
  const fetchOpts = noCache
79
79
  ? { cache: "no-store" }
80
80
  : { next: { revalidate } };
@@ -95,11 +95,11 @@ export async function FilteredPortfolio({ clientSlug, apiBase, apiKey, searchPar
95
95
  fetchSchema(apiBase, clientSlug, apiKey, fetchOpts),
96
96
  ]);
97
97
  const activeCount = Object.values(activeFilters).reduce((n, ids) => n + ids.length, 0);
98
- const visible = side
98
+ const visible = filterBy
99
99
  ? projects.filter((p) => {
100
100
  var _a;
101
- const s = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a["side"];
102
- return s === side || s === "both";
101
+ const v = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a[filterBy.field];
102
+ return v === filterBy.value || v === "both";
103
103
  })
104
104
  : projects;
105
105
  return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
@@ -25,12 +25,15 @@ export interface ProjectPortfolioProps {
25
25
  */
26
26
  noCache?: boolean;
27
27
  /**
28
- * Filter projects by website side.
29
- * - "architectural" shows projects with side "architectural" or "both"
30
- * - "speedrail" — shows projects with side "speedrail" or "both"
31
- * - omit — shows all projects regardless of side
28
+ * Pre-filter projects by any custom field value before rendering.
29
+ * e.g. filterBy={{ field: "side", value: "architectural" }}
30
+ * Projects where the field value equals the given value OR "both" are shown.
31
+ * When omitted all projects are shown.
32
32
  */
33
- side?: "architectural" | "speedrail";
33
+ filterBy?: {
34
+ field: string;
35
+ value: string;
36
+ };
34
37
  }
35
38
  /**
36
39
  * ProjectPortfolio — pure self-fetching card grid.
@@ -47,5 +50,5 @@ export interface ProjectPortfolioProps {
47
50
  * apiBase="https://your-api.com"
48
51
  * />
49
52
  */
50
- export declare function ProjectPortfolio({ clientSlug, apiBase, apiKey, basePath, searchParams, revalidate, noCache, side, }: ProjectPortfolioProps): Promise<import("react").JSX.Element>;
53
+ export declare function ProjectPortfolio({ clientSlug, apiBase, apiKey, basePath, searchParams, revalidate, noCache, filterBy, }: ProjectPortfolioProps): Promise<import("react").JSX.Element>;
51
54
  //# sourceMappingURL=ProjectPortfolio.d.ts.map
@@ -1 +1 @@
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,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IAC5D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,WAAW,CAAA;CACrC;AA0ED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,YAAiB,EACjB,UAAe,EACf,OAAe,EACf,IAAI,GACL,EAAE,qBAAqB,wCAsIvB"}
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,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IAC5D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;AA0ED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,YAAiB,EACjB,UAAe,EACf,OAAe,EACf,QAAQ,GACT,EAAE,qBAAqB,wCAsIvB"}
@@ -65,7 +65,7 @@ const fetchPortfolioData = cache(async (apiBase, clientSlug, apiKey, revalidate,
65
65
  * apiBase="https://your-api.com"
66
66
  * />
67
67
  */
68
- export async function ProjectPortfolio({ clientSlug, apiBase, apiKey, basePath = "/projects", searchParams = {}, revalidate = 60, noCache = false, side, }) {
68
+ export async function ProjectPortfolio({ clientSlug, apiBase, apiKey, basePath = "/projects", searchParams = {}, revalidate = 60, noCache = false, filterBy, }) {
69
69
  // Parse filter[key]=value from searchParams into { key: value }
70
70
  const filters = {};
71
71
  Object.entries(searchParams).forEach(([key, val]) => {
@@ -77,11 +77,11 @@ export async function ProjectPortfolio({ clientSlug, apiBase, apiKey, basePath =
77
77
  });
78
78
  const filtersKey = JSON.stringify(filters);
79
79
  const { projects, schema, fieldOptionsMap } = await fetchPortfolioData(apiBase, clientSlug, apiKey, revalidate, filtersKey, noCache);
80
- const visible = side
80
+ const visible = filterBy
81
81
  ? projects.filter((p) => {
82
82
  var _a;
83
- const s = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a["side"];
84
- return s === side || s === "both";
83
+ const v = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a[filterBy.field];
84
+ return v === filterBy.value || v === "both";
85
85
  })
86
86
  : projects;
87
87
  const hasFilters = Object.keys(filters).length > 0;
@@ -20,12 +20,15 @@ export interface ProjectPortfolioClientProps {
20
20
  /** Max columns in the grid. 2 or 3. Defaults to 3 */
21
21
  columns?: 2 | 3;
22
22
  /**
23
- * Filter projects by website side.
24
- * - "architectural" shows projects with side "architectural" or "both"
25
- * - "speedrail" — shows projects with side "speedrail" or "both"
26
- * - omit — shows all projects regardless of side
23
+ * Pre-filter projects by any custom field value before rendering.
24
+ * e.g. filterBy={{ field: "side", value: "architectural" }}
25
+ * Projects where the field value equals the given value OR "both" are shown.
26
+ * When omitted all projects are shown.
27
27
  */
28
- side?: "architectural" | "speedrail";
28
+ filterBy?: {
29
+ field: string;
30
+ value: string;
31
+ };
29
32
  }
30
- export declare function ProjectPortfolioClient({ clientSlug, apiBase, apiKey, basePath, filters, font, columns, side, }: ProjectPortfolioClientProps): React.JSX.Element;
33
+ export declare function ProjectPortfolioClient({ clientSlug, apiBase, apiKey, basePath, filters, font, columns, filterBy, }: ProjectPortfolioClientProps): React.JSX.Element;
31
34
  //# sourceMappingURL=ProjectPortfolioClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectPortfolioClient.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolioClient.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAM3D,MAAM,WAAW,2BAA2B;IAC1C,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,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;IACf;;;;;OAKG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,WAAW,CAAA;CACrC;AAyDD,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,OAAY,EACZ,IAAmB,EACnB,OAAW,EACX,IAAI,GACL,EAAE,2BAA2B,qBAyJ7B"}
1
+ {"version":3,"file":"ProjectPortfolioClient.d.ts","sourceRoot":"","sources":["../src/ProjectPortfolioClient.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAM3D,MAAM,WAAW,2BAA2B;IAC1C,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,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;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;AAyDD,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,OAAY,EACZ,IAAmB,EACnB,OAAW,EACX,QAAQ,GACT,EAAE,2BAA2B,qBAyJ7B"}
@@ -39,7 +39,7 @@ function matchesFilters(project, filters, schema, fieldOptionsMap) {
39
39
  });
40
40
  }
41
41
  // ─── Component ───────────────────────────────────────────────────────────────
42
- export function ProjectPortfolioClient({ clientSlug, apiBase, apiKey, basePath = "/projects", filters = {}, font = DEFAULT_FONT, columns = 3, side, }) {
42
+ export function ProjectPortfolioClient({ clientSlug, apiBase, apiKey, basePath = "/projects", filters = {}, font = DEFAULT_FONT, columns = 3, filterBy, }) {
43
43
  const [data, setData] = useState(null);
44
44
  useEffect(() => {
45
45
  const cacheKey = `${clientSlug}:${apiBase}:${apiKey}`;
@@ -90,18 +90,18 @@ export function ProjectPortfolioClient({ clientSlug, apiBase, apiKey, basePath =
90
90
  const filteredProjects = useMemo(() => {
91
91
  if (!data)
92
92
  return [];
93
- const bySide = side
93
+ const byField = filterBy
94
94
  ? data.projects.filter((p) => {
95
95
  var _a;
96
- const s = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a["side"];
97
- return s === side || s === "both";
96
+ const v = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a[filterBy.field];
97
+ return v === filterBy.value || v === "both";
98
98
  })
99
99
  : data.projects;
100
100
  const hasActiveFilters = Object.values(filters).some(Boolean);
101
101
  if (!hasActiveFilters)
102
- return bySide;
103
- return bySide.filter((p) => matchesFilters(p, filters, data.schema, data.fieldOptionsMap));
104
- }, [data, filters, side]);
102
+ return byField;
103
+ return byField.filter((p) => matchesFilters(p, filters, data.schema, data.fieldOptionsMap));
104
+ }, [data, filters, filterBy]);
105
105
  const gridCols = columns === 2
106
106
  ? "repeat(2, 1fr)"
107
107
  : "repeat(3, 1fr)";
@@ -44,12 +44,15 @@ export interface SimilarProjectsProps {
44
44
  /** Small label above the heading. Defaults to "More Work" */
45
45
  subtitle?: string;
46
46
  /**
47
- * Filter projects by website side.
48
- * - "architectural" shows projects with side "architectural" or "both"
49
- * - "speedrail" — shows projects with side "speedrail" or "both"
50
- * - omit — shows all projects regardless of side
47
+ * Pre-filter projects by any custom field value before rendering.
48
+ * e.g. filterBy={{ field: "side", value: "architectural" }}
49
+ * Projects where the field value equals the given value OR "both" are shown.
50
+ * When omitted all projects are shown.
51
51
  */
52
- side?: "architectural" | "speedrail";
52
+ filterBy?: {
53
+ field: string;
54
+ value: string;
55
+ };
53
56
  }
54
- export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, apiKey, basePath, maxItems, revalidate, noCache, variant, projectSlugs, title, subtitle, side, }: SimilarProjectsProps): Promise<import("react").JSX.Element | null>;
57
+ export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, apiKey, basePath, maxItems, revalidate, noCache, variant, projectSlugs, title, subtitle, filterBy, }: SimilarProjectsProps): Promise<import("react").JSX.Element | null>;
55
58
  //# sourceMappingURL=SimilarProjects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAKA,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,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,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;IACjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,eAAe,GAAG,WAAW,CAAA;CACrC;AA6DD,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,QAAY,EACZ,UAAe,EACf,OAAe,EACf,OAAgB,EAChB,YAAY,EACZ,KAA0B,EAC1B,QAAsB,EACtB,IAAI,GACL,EAAE,oBAAoB,+CA4KtB"}
1
+ {"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAKA,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,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,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;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;AA6DD,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,QAAY,EACZ,UAAe,EACf,OAAe,EACf,OAAgB,EAChB,YAAY,EACZ,KAA0B,EAC1B,QAAsB,EACtB,QAAQ,GACT,EAAE,oBAAoB,+CA4KtB"}
@@ -49,14 +49,14 @@ async function fetchSimilarData(apiBase, clientSlug, apiKey, revalidate, noCache
49
49
  }
50
50
  }
51
51
  // ─── Component ────────────────────────────────────────────────────────────────
52
- export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, apiKey, basePath = "/projects", maxItems = 3, revalidate = 60, noCache = false, variant = "list", projectSlugs, title = "Similar Projects", subtitle = "More Work", side, }) {
52
+ export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, apiKey, basePath = "/projects", maxItems = 3, revalidate = 60, noCache = false, variant = "list", projectSlugs, title = "Similar Projects", subtitle = "More Work", filterBy, }) {
53
53
  const { allProjects: allProjectsRaw, schema, fieldOptionsMap } = await fetchSimilarData(apiBase, clientSlug, apiKey, revalidate, noCache);
54
- // Apply side filter before any other filtering
55
- const allProjects = side
54
+ // Apply filterBy before any other filtering
55
+ const allProjects = filterBy
56
56
  ? allProjectsRaw.filter((p) => {
57
57
  var _a;
58
- const s = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a["side"];
59
- return s === side || s === "both";
58
+ const v = (_a = p.custom_field_values) === null || _a === void 0 ? void 0 : _a[filterBy.field];
59
+ return v === filterBy.value || v === "both";
60
60
  })
61
61
  : allProjectsRaw;
62
62
  const badgeField = schema.find((f) => f.display_position === "badge_overlay");
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
@@ -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,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,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,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
@@ -39,8 +39,6 @@ export interface Project {
39
39
  image_url: string | null;
40
40
  is_featured: boolean;
41
41
  is_published?: boolean;
42
- /** Which website side this project belongs to. Omitted = show on all sides. */
43
- side?: "architectural" | "speedrail" | "both";
44
42
  custom_field_values: Record<string, CustomFieldValue>;
45
43
  created_at: string;
46
44
  updated_at: string;
@@ -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,+EAA+E;IAC/E,IAAI,CAAC,EAAE,eAAe,GAAG,WAAW,GAAG,MAAM,CAAA;IAC7C,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,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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chiselandco/nexus",
3
- "version": "2.6.1",
4
- "description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectPortfolioClient, ProjectDetail, SimilarProjects, ProjectMenu, ProjectMenuClient, GalleryCarousel, FilterSidebar, and FilteredPortfolio. Pass a clientSlug and apiBase — done.",
3
+ "version": "3.1.0",
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",