@carlonicora/nextjs-jsonapi 0.0.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 +889 -0
- package/dist/AbstractService-BKlpJA61.d.mts +109 -0
- package/dist/AbstractService-D9eSVKNa.d.ts +109 -0
- package/dist/ApiData-DPKNfY-9.d.mts +10 -0
- package/dist/ApiData-DPKNfY-9.d.ts +10 -0
- package/dist/ApiDataInterface-DPP8s46n.d.mts +21 -0
- package/dist/ApiDataInterface-DPP8s46n.d.ts +21 -0
- package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.mts +20 -0
- package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.ts +20 -0
- package/dist/ApiResponseInterface-BHN5D9r5.d.mts +16 -0
- package/dist/ApiResponseInterface-DDI7QQPR.d.ts +16 -0
- package/dist/BlockNoteEditor-UVO3VZZE.mjs +396 -0
- package/dist/BlockNoteEditor-UVO3VZZE.mjs.map +1 -0
- package/dist/BlockNoteEditor-VFWG6LXI.js +396 -0
- package/dist/BlockNoteEditor-VFWG6LXI.js.map +1 -0
- package/dist/JsonApiRequest-S3ICLM7B.mjs +20 -0
- package/dist/JsonApiRequest-S3ICLM7B.mjs.map +1 -0
- package/dist/JsonApiRequest-ZZLSP26T.js +20 -0
- package/dist/JsonApiRequest-ZZLSP26T.js.map +1 -0
- package/dist/atoms/index.d.mts +12 -0
- package/dist/atoms/index.d.ts +12 -0
- package/dist/atoms/index.js +9 -0
- package/dist/atoms/index.js.map +1 -0
- package/dist/atoms/index.mjs +9 -0
- package/dist/atoms/index.mjs.map +1 -0
- package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.mts +6 -0
- package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.ts +6 -0
- package/dist/chunk-2K3Q24UF.js +89 -0
- package/dist/chunk-2K3Q24UF.js.map +1 -0
- package/dist/chunk-2LM6LCJW.mjs +1091 -0
- package/dist/chunk-2LM6LCJW.mjs.map +1 -0
- package/dist/chunk-366S2JCC.mjs +31 -0
- package/dist/chunk-366S2JCC.mjs.map +1 -0
- package/dist/chunk-3FBCC4G3.js +8 -0
- package/dist/chunk-3FBCC4G3.js.map +1 -0
- package/dist/chunk-4HCRAOS5.js +28 -0
- package/dist/chunk-4HCRAOS5.js.map +1 -0
- package/dist/chunk-5W6AKZE6.mjs +131 -0
- package/dist/chunk-5W6AKZE6.mjs.map +1 -0
- package/dist/chunk-6GKHCVF6.js +98 -0
- package/dist/chunk-6GKHCVF6.js.map +1 -0
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/chunk-7QVYU63E.js.map +1 -0
- package/dist/chunk-A3J3AAYM.mjs +97 -0
- package/dist/chunk-A3J3AAYM.mjs.map +1 -0
- package/dist/chunk-A5DDIABK.js +4209 -0
- package/dist/chunk-A5DDIABK.js.map +1 -0
- package/dist/chunk-AUXK7QSA.mjs +15 -0
- package/dist/chunk-AUXK7QSA.mjs.map +1 -0
- package/dist/chunk-AWONBQQP.js +97 -0
- package/dist/chunk-AWONBQQP.js.map +1 -0
- package/dist/chunk-BLWVZK6J.mjs +28 -0
- package/dist/chunk-BLWVZK6J.mjs.map +1 -0
- package/dist/chunk-C7C7VY4F.mjs +77 -0
- package/dist/chunk-C7C7VY4F.mjs.map +1 -0
- package/dist/chunk-CXQOWQSY.js +55 -0
- package/dist/chunk-CXQOWQSY.js.map +1 -0
- package/dist/chunk-DD3KISNB.mjs +98 -0
- package/dist/chunk-DD3KISNB.mjs.map +1 -0
- package/dist/chunk-DKKMWBP4.mjs +1 -0
- package/dist/chunk-DKKMWBP4.mjs.map +1 -0
- package/dist/chunk-DO2HLAZO.js +48 -0
- package/dist/chunk-DO2HLAZO.js.map +1 -0
- package/dist/chunk-DZXDB3K2.mjs +17 -0
- package/dist/chunk-DZXDB3K2.mjs.map +1 -0
- package/dist/chunk-ECDTZBYO.mjs +230 -0
- package/dist/chunk-ECDTZBYO.mjs.map +1 -0
- package/dist/chunk-EFJEWLRL.js +16 -0
- package/dist/chunk-EFJEWLRL.js.map +1 -0
- package/dist/chunk-FY4SXJGU.js +806 -0
- package/dist/chunk-FY4SXJGU.js.map +1 -0
- package/dist/chunk-GYWPEPOH.mjs +1354 -0
- package/dist/chunk-GYWPEPOH.mjs.map +1 -0
- package/dist/chunk-H6FMOA6B.js +1 -0
- package/dist/chunk-H6FMOA6B.js.map +1 -0
- package/dist/chunk-HR4H2FP7.mjs +89 -0
- package/dist/chunk-HR4H2FP7.mjs.map +1 -0
- package/dist/chunk-I2REI7OA.js +462 -0
- package/dist/chunk-I2REI7OA.js.map +1 -0
- package/dist/chunk-IBS6NI7D.js +77 -0
- package/dist/chunk-IBS6NI7D.js.map +1 -0
- package/dist/chunk-IWFGEPAA.mjs +4209 -0
- package/dist/chunk-IWFGEPAA.mjs.map +1 -0
- package/dist/chunk-J4Q36PMP.js +31 -0
- package/dist/chunk-J4Q36PMP.js.map +1 -0
- package/dist/chunk-JC3WJK65.js +1091 -0
- package/dist/chunk-JC3WJK65.js.map +1 -0
- package/dist/chunk-L6EQEAXU.mjs +462 -0
- package/dist/chunk-L6EQEAXU.mjs.map +1 -0
- package/dist/chunk-LXKSUWAV.js +15 -0
- package/dist/chunk-LXKSUWAV.js.map +1 -0
- package/dist/chunk-MFO27OHB.mjs +48 -0
- package/dist/chunk-MFO27OHB.mjs.map +1 -0
- package/dist/chunk-PAWJFY3S.mjs +7 -0
- package/dist/chunk-PAWJFY3S.mjs.map +1 -0
- package/dist/chunk-Q2N6SQYW.mjs +8 -0
- package/dist/chunk-Q2N6SQYW.mjs.map +1 -0
- package/dist/chunk-RAF7PNLG.js +131 -0
- package/dist/chunk-RAF7PNLG.js.map +1 -0
- package/dist/chunk-RUR22SVM.js +17 -0
- package/dist/chunk-RUR22SVM.js.map +1 -0
- package/dist/chunk-TEGF6ZWG.js +109 -0
- package/dist/chunk-TEGF6ZWG.js.map +1 -0
- package/dist/chunk-TMVHSY3Y.js +230 -0
- package/dist/chunk-TMVHSY3Y.js.map +1 -0
- package/dist/chunk-V2JJPI7N.js +1354 -0
- package/dist/chunk-V2JJPI7N.js.map +1 -0
- package/dist/chunk-WWWMJZEF.mjs +806 -0
- package/dist/chunk-WWWMJZEF.mjs.map +1 -0
- package/dist/chunk-X4BIHJ2B.mjs +55 -0
- package/dist/chunk-X4BIHJ2B.mjs.map +1 -0
- package/dist/chunk-YDVTFM7X.mjs +109 -0
- package/dist/chunk-YDVTFM7X.mjs.map +1 -0
- package/dist/chunk-YF5XQZDR.mjs +16 -0
- package/dist/chunk-YF5XQZDR.mjs.map +1 -0
- package/dist/client/index.d.mts +252 -0
- package/dist/client/index.d.ts +252 -0
- package/dist/client/index.js +275 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +274 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/components/index.d.mts +441 -0
- package/dist/components/index.d.ts +441 -0
- package/dist/components/index.js +2474 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +2474 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config-hXufftVS.d.mts +34 -0
- package/dist/config-hXufftVS.d.ts +34 -0
- package/dist/content.interface-BhyAiOFq.d.ts +35 -0
- package/dist/content.interface-Dg2lt_An.d.mts +35 -0
- package/dist/contexts/index.d.mts +56 -0
- package/dist/contexts/index.d.ts +56 -0
- package/dist/contexts/index.js +21 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/contexts/index.mjs +21 -0
- package/dist/contexts/index.mjs.map +1 -0
- package/dist/core/index.d.mts +152 -0
- package/dist/core/index.d.ts +152 -0
- package/dist/core/index.js +47 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +47 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/d3.link.interface-QMdB22bC.d.mts +20 -0
- package/dist/d3.link.interface-QMdB22bC.d.ts +20 -0
- package/dist/features/index.d.mts +553 -0
- package/dist/features/index.d.ts +553 -0
- package/dist/features/index.js +94 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/index.mjs +94 -0
- package/dist/features/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +94 -0
- package/dist/hooks/index.d.ts +94 -0
- package/dist/hooks/index.js +43 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +43 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +84 -0
- package/dist/index.mjs.map +1 -0
- package/dist/interfaces/index.d.mts +3 -0
- package/dist/interfaces/index.d.ts +3 -0
- package/dist/interfaces/index.js +2 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/index.mjs +2 -0
- package/dist/interfaces/index.mjs.map +1 -0
- package/dist/permissions/index.d.mts +41 -0
- package/dist/permissions/index.d.ts +41 -0
- package/dist/permissions/index.js +14 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/index.mjs +14 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/request-7FE3LJLV.mjs +9 -0
- package/dist/request-7FE3LJLV.mjs.map +1 -0
- package/dist/request-FYMQK5CX.mjs +9 -0
- package/dist/request-FYMQK5CX.mjs.map +1 -0
- package/dist/request-QFS7NEIE.js +9 -0
- package/dist/request-QFS7NEIE.js.map +1 -0
- package/dist/request-ZYY6RI5X.js +9 -0
- package/dist/request-ZYY6RI5X.js.map +1 -0
- package/dist/roles/index.d.mts +33 -0
- package/dist/roles/index.d.ts +33 -0
- package/dist/roles/index.js +12 -0
- package/dist/roles/index.js.map +1 -0
- package/dist/roles/index.mjs +12 -0
- package/dist/roles/index.mjs.map +1 -0
- package/dist/server/index.d.mts +44 -0
- package/dist/server/index.d.ts +44 -0
- package/dist/server/index.js +29 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +29 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/shadcnui/index.d.mts +698 -0
- package/dist/shadcnui/index.d.ts +698 -0
- package/dist/shadcnui/index.js +466 -0
- package/dist/shadcnui/index.js.map +1 -0
- package/dist/shadcnui/index.mjs +465 -0
- package/dist/shadcnui/index.mjs.map +1 -0
- package/dist/token-IJSPOMW6.mjs +9 -0
- package/dist/token-IJSPOMW6.mjs.map +1 -0
- package/dist/token-MJMC26ON.js +9 -0
- package/dist/token-MJMC26ON.js.map +1 -0
- package/dist/token-UADJQ7VC.mjs +9 -0
- package/dist/token-UADJQ7VC.mjs.map +1 -0
- package/dist/token-UYE7CV6X.js +9 -0
- package/dist/token-UYE7CV6X.js.map +1 -0
- package/dist/types-DluCaP1I.d.ts +95 -0
- package/dist/types-lQVA8d_P.d.mts +95 -0
- package/dist/useDataListRetriever-futhx3OP.d.mts +32 -0
- package/dist/useDataListRetriever-futhx3OP.d.ts +32 -0
- package/dist/user.interface-CAsTIbuQ.d.mts +85 -0
- package/dist/user.interface-CbWqMaaU.d.ts +85 -0
- package/dist/utils/index.d.mts +201 -0
- package/dist/utils/index.d.ts +201 -0
- package/dist/utils/index.js +32 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +32 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +205 -0
package/README.md
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
# @carlonicora/nextjs-jsonapi
|
|
2
|
+
|
|
3
|
+
A comprehensive Next.js package providing JSON:API compliant client with unified server/client support, automatic caching, and a complete shadcn/ui component library.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Architecture](#architecture)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Environment Variables](#environment-variables)
|
|
12
|
+
- [Entry Points](#entry-points)
|
|
13
|
+
- [Unified API](#unified-api)
|
|
14
|
+
- [Client Hooks](#client-hooks)
|
|
15
|
+
- [Server Requests](#server-requests)
|
|
16
|
+
- [Permissions](#permissions)
|
|
17
|
+
- [shadcn/ui Components](#shadcnui-components)
|
|
18
|
+
- [Tailwind CSS Configuration](#tailwind-css-configuration)
|
|
19
|
+
- [CSS Variables](#css-variables)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Unified API**: Auto-detects environment (server/client) and uses the appropriate request method
|
|
25
|
+
- **JSON:API Compliance**: Full JSON:API specification support with deserialization and pagination
|
|
26
|
+
- **Next.js 16+ Caching**: Built-in support for `cacheLife()` and `cacheTag()` via cache profiles
|
|
27
|
+
- **React Hooks**: `useJsonApiGet` and `useJsonApiMutation` for client-side data fetching
|
|
28
|
+
- **Server Components**: Direct server-side data fetching with automatic token handling
|
|
29
|
+
- **Multi-Tenant Support**: Built-in company ID handling for B2B applications
|
|
30
|
+
- **File Uploads**: Seamless file upload support with multipart requests
|
|
31
|
+
- **shadcn/ui Components**: 44 pre-built UI components (41 standard + 3 custom)
|
|
32
|
+
- **Utility Functions**: `cn` class merger, mobile detection, and ref composition
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
The library is organized into eight entry points:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
@carlonicora/nextjs-jsonapi
|
|
40
|
+
├── (main) # Unified API (auto-detects environment)
|
|
41
|
+
├── /core # Interfaces, factories, registries, and utilities
|
|
42
|
+
├── /client # React hooks and client-side utilities
|
|
43
|
+
├── /server # Server-side requests and caching
|
|
44
|
+
├── /permissions # Permission checking utilities
|
|
45
|
+
├── /features # Built-in feature modules (S3, etc.)
|
|
46
|
+
├── /utils # Utility functions (cn, useIsMobile, etc.)
|
|
47
|
+
└── /shadcnui # 44 shadcn/ui components
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add @carlonicora/nextjs-jsonapi
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Git Submodule Setup (Alternative)
|
|
57
|
+
|
|
58
|
+
If you want to use the package as a git submodule (for development or before npm release):
|
|
59
|
+
|
|
60
|
+
**1. Add the submodule**
|
|
61
|
+
```bash
|
|
62
|
+
cd /path/to/your-project
|
|
63
|
+
git submodule add https://github.com/carlonicora/nextjs-jsonapi packages/nextjs-jsonapi
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**2. Verify it worked**
|
|
67
|
+
```bash
|
|
68
|
+
git submodule status
|
|
69
|
+
# Should show: <commit-sha> packages/nextjs-jsonapi (heads/master)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**3. Commit the submodule**
|
|
73
|
+
```bash
|
|
74
|
+
git add .gitmodules packages/nextjs-jsonapi
|
|
75
|
+
git commit -m "Add nextjs-jsonapi as submodule"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**4. Update your `package.json`** (e.g., `apps/web/package.json`)
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@carlonicora/nextjs-jsonapi": "workspace:*"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**5. Ensure `pnpm-workspace.yaml` includes packages**
|
|
88
|
+
```yaml
|
|
89
|
+
packages:
|
|
90
|
+
- "apps/*"
|
|
91
|
+
- "packages/*"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**6. Install and build**
|
|
95
|
+
```bash
|
|
96
|
+
pnpm install
|
|
97
|
+
cd packages/nextjs-jsonapi && pnpm build && cd ../..
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**For CI/CD (GitHub Actions)**, add `submodules: recursive` to your checkout step:
|
|
101
|
+
```yaml
|
|
102
|
+
- uses: actions/checkout@v4
|
|
103
|
+
with:
|
|
104
|
+
submodules: recursive
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Cloning a project with submodules:**
|
|
108
|
+
```bash
|
|
109
|
+
# When cloning fresh
|
|
110
|
+
git clone --recurse-submodules https://github.com/your/repo.git
|
|
111
|
+
|
|
112
|
+
# If already cloned
|
|
113
|
+
git submodule update --init --recursive
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Peer Dependencies
|
|
117
|
+
|
|
118
|
+
| Package | Version | Required | Purpose |
|
|
119
|
+
| ----------------- | --------- | -------- | -------------------------------- |
|
|
120
|
+
| `next` | >=14.0.0 | Yes | Next.js framework |
|
|
121
|
+
| `react` | >=18.0.0 | Yes | React library |
|
|
122
|
+
| `react-dom` | >=18.0.0 | Yes | React DOM |
|
|
123
|
+
| `react-hook-form` | >=7.0.0 | Optional | Form handling (for form components) |
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
### 1. Configure the API Client
|
|
128
|
+
|
|
129
|
+
Configure the JSON:API client in your environment setup file:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// src/config/env.ts
|
|
133
|
+
import { configureJsonApi } from "@carlonicora/nextjs-jsonapi";
|
|
134
|
+
import { bootstrap } from "@/config/Bootstrapper";
|
|
135
|
+
|
|
136
|
+
configureJsonApi({
|
|
137
|
+
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
138
|
+
bootstrapper: bootstrap,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2. Define Your Modules and Bootstrapper
|
|
143
|
+
|
|
144
|
+
Create a bootstrapper that registers all modules for both the ModuleRegistry (for `Modules.X` access) and DataClassRegistry (for JSON:API response translation):
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// src/config/Bootstrapper.ts
|
|
148
|
+
import { DataClassRegistry, FieldSelector, ModuleRegistry } from "@carlonicora/nextjs-jsonapi/core";
|
|
149
|
+
import { ModuleWithPermissions } from "@carlonicora/nextjs-jsonapi/permissions";
|
|
150
|
+
import { S3Module } from "@carlonicora/nextjs-jsonapi/features";
|
|
151
|
+
|
|
152
|
+
// Import your module definitions
|
|
153
|
+
import { ArticleModule } from "@/features/article/ArticleModule";
|
|
154
|
+
import { UserModule } from "@/features/user/UserModule";
|
|
155
|
+
import { Article } from "@/features/article/data/Article";
|
|
156
|
+
import { User } from "@/features/user/data/User";
|
|
157
|
+
|
|
158
|
+
// Module factory helper
|
|
159
|
+
const moduleFactory = (params: {
|
|
160
|
+
pageUrl?: string;
|
|
161
|
+
name: string;
|
|
162
|
+
cache?: string;
|
|
163
|
+
model: any;
|
|
164
|
+
feature?: string;
|
|
165
|
+
moduleId?: string;
|
|
166
|
+
inclusions?: Record<string, { types?: string[]; fields?: FieldSelector<any>[] }>;
|
|
167
|
+
}): ModuleWithPermissions => ({
|
|
168
|
+
pageUrl: params.pageUrl,
|
|
169
|
+
name: params.name,
|
|
170
|
+
model: params.model,
|
|
171
|
+
feature: params.feature,
|
|
172
|
+
moduleId: params.moduleId,
|
|
173
|
+
cache: params.cache,
|
|
174
|
+
inclusions: params.inclusions ?? {},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Example module definition file (e.g., ArticleModule.ts)
|
|
178
|
+
// export const ArticleModule = (factory: ModuleFactory) =>
|
|
179
|
+
// factory({ name: "articles", model: Article, pageUrl: "/articles" });
|
|
180
|
+
|
|
181
|
+
// Single source of truth for all modules
|
|
182
|
+
const allModules = {
|
|
183
|
+
Article: ArticleModule(moduleFactory),
|
|
184
|
+
User: UserModule(moduleFactory),
|
|
185
|
+
S3: S3Module(moduleFactory), // Built-in S3 module from library
|
|
186
|
+
} satisfies Record<string, ModuleWithPermissions>;
|
|
187
|
+
|
|
188
|
+
// Export type for TypeScript autocompletion
|
|
189
|
+
export type AllModuleDefinitions = typeof allModules;
|
|
190
|
+
|
|
191
|
+
let bootstrapped = false;
|
|
192
|
+
|
|
193
|
+
export function bootstrap(): void {
|
|
194
|
+
if (bootstrapped) return;
|
|
195
|
+
|
|
196
|
+
// Register modules for Modules.X access
|
|
197
|
+
Object.entries(allModules).forEach(([name, module]) => {
|
|
198
|
+
ModuleRegistry.register(name, module);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Register model classes for JSON:API response translation
|
|
202
|
+
DataClassRegistry.bootstrap(allModules);
|
|
203
|
+
|
|
204
|
+
bootstrapped = true;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 3. Fetch Data in Server Components
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// src/app/articles/page.tsx
|
|
212
|
+
import { JsonApiGet } from "@carlonicora/nextjs-jsonapi";
|
|
213
|
+
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
214
|
+
|
|
215
|
+
export default async function ArticlesPage() {
|
|
216
|
+
const response = await JsonApiGet({
|
|
217
|
+
classKey: Modules.Article,
|
|
218
|
+
endpoint: "/articles",
|
|
219
|
+
language: "en",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
return <div>Error: {response.error}</div>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<ul>
|
|
228
|
+
{response.data.map((article) => (
|
|
229
|
+
<li key={article.id}>{article.title}</li>
|
|
230
|
+
))}
|
|
231
|
+
</ul>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 4. Use Hooks in Client Components
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
"use client";
|
|
240
|
+
|
|
241
|
+
import { useJsonApiGet, useJsonApiMutation } from "@carlonicora/nextjs-jsonapi/client";
|
|
242
|
+
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
243
|
+
|
|
244
|
+
export function ArticleList() {
|
|
245
|
+
const { data, loading, error, refetch } = useJsonApiGet({
|
|
246
|
+
classKey: Modules.Article,
|
|
247
|
+
endpoint: "/articles",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const { mutate, loading: creating } = useJsonApiMutation({
|
|
251
|
+
method: "POST",
|
|
252
|
+
classKey: Modules.Article,
|
|
253
|
+
onSuccess: () => refetch(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (loading) return <div>Loading...</div>;
|
|
257
|
+
if (error) return <div>Error: {error}</div>;
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<button
|
|
262
|
+
onClick={() => mutate({
|
|
263
|
+
endpoint: "/articles",
|
|
264
|
+
body: { title: "New Article" }
|
|
265
|
+
})}
|
|
266
|
+
disabled={creating}
|
|
267
|
+
>
|
|
268
|
+
Create Article
|
|
269
|
+
</button>
|
|
270
|
+
<ul>
|
|
271
|
+
{data.map((article) => (
|
|
272
|
+
<li key={article.id}>{article.title}</li>
|
|
273
|
+
))}
|
|
274
|
+
</ul>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Environment Variables
|
|
281
|
+
|
|
282
|
+
```env
|
|
283
|
+
# Required
|
|
284
|
+
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
285
|
+
|
|
286
|
+
# Optional - Token cookie name (default: "token")
|
|
287
|
+
# Set this if your API uses a different cookie name for JWT tokens
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Entry Points
|
|
291
|
+
|
|
292
|
+
### Main Export (`.`)
|
|
293
|
+
|
|
294
|
+
The default export provides the unified API that auto-detects the environment:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import {
|
|
298
|
+
JsonApiGet,
|
|
299
|
+
JsonApiPost,
|
|
300
|
+
JsonApiPut,
|
|
301
|
+
JsonApiPatch,
|
|
302
|
+
JsonApiDelete,
|
|
303
|
+
configureJsonApi,
|
|
304
|
+
} from "@carlonicora/nextjs-jsonapi";
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Core (`/core`)
|
|
308
|
+
|
|
309
|
+
Core interfaces, factories, registries, and utilities:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import {
|
|
313
|
+
// Interfaces
|
|
314
|
+
ApiDataInterface,
|
|
315
|
+
ApiRequestDataTypeInterface,
|
|
316
|
+
ApiResponseInterface,
|
|
317
|
+
|
|
318
|
+
// Factories
|
|
319
|
+
JsonApiDataFactory,
|
|
320
|
+
|
|
321
|
+
// Registries
|
|
322
|
+
ModuleRegistry, // Register modules during bootstrap
|
|
323
|
+
DataClassRegistry, // Register model classes for JSON:API translation
|
|
324
|
+
Modules, // Access registered modules (e.g., Modules.Article)
|
|
325
|
+
|
|
326
|
+
// Endpoint builder
|
|
327
|
+
EndpointBuilder,
|
|
328
|
+
|
|
329
|
+
// Field selectors
|
|
330
|
+
FieldSelector,
|
|
331
|
+
|
|
332
|
+
// Utilities
|
|
333
|
+
translateResponse,
|
|
334
|
+
} from "@carlonicora/nextjs-jsonapi/core";
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Client (`/client`)
|
|
338
|
+
|
|
339
|
+
React hooks and client-side utilities (requires `"use client"`):
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import {
|
|
343
|
+
// Hooks
|
|
344
|
+
useJsonApiGet,
|
|
345
|
+
useJsonApiMutation,
|
|
346
|
+
useRehydration,
|
|
347
|
+
|
|
348
|
+
// Context
|
|
349
|
+
JsonApiProvider,
|
|
350
|
+
useJsonApiContext,
|
|
351
|
+
|
|
352
|
+
// Request utilities
|
|
353
|
+
directFetch,
|
|
354
|
+
getClientToken,
|
|
355
|
+
} from "@carlonicora/nextjs-jsonapi/client";
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Server (`/server`)
|
|
359
|
+
|
|
360
|
+
Server-side request utilities:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import {
|
|
364
|
+
serverRequest,
|
|
365
|
+
getServerToken,
|
|
366
|
+
getCacheProfile,
|
|
367
|
+
} from "@carlonicora/nextjs-jsonapi/server";
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Permissions (`/permissions`)
|
|
371
|
+
|
|
372
|
+
Permission checking utilities:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import {
|
|
376
|
+
checkPermission,
|
|
377
|
+
type PermissionCheck,
|
|
378
|
+
type ModuleWithPermissions,
|
|
379
|
+
type ModuleFactory,
|
|
380
|
+
} from "@carlonicora/nextjs-jsonapi/permissions";
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Features (`/features`)
|
|
384
|
+
|
|
385
|
+
Built-in feature modules that can be used directly in your application:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import {
|
|
389
|
+
// S3 Module (for file uploads via pre-signed URLs)
|
|
390
|
+
S3Module, // Module definition factory
|
|
391
|
+
S3Service, // Service with getPreSignedUrl, getSignedUrl, deleteFile
|
|
392
|
+
S3, // Data class
|
|
393
|
+
type S3Interface, // Response interface
|
|
394
|
+
type S3Input, // Input parameters
|
|
395
|
+
} from "@carlonicora/nextjs-jsonapi/features";
|
|
396
|
+
|
|
397
|
+
// Usage example:
|
|
398
|
+
const s3Response = await S3Service.getPreSignedUrl({
|
|
399
|
+
key: "companies/123/documents/file.pdf",
|
|
400
|
+
contentType: "application/pdf",
|
|
401
|
+
isPublic: true,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
await fetch(s3Response.url, {
|
|
405
|
+
method: "PUT",
|
|
406
|
+
headers: s3Response.headers,
|
|
407
|
+
body: file,
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Utils (`/utils`)
|
|
412
|
+
|
|
413
|
+
Utility functions:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import {
|
|
417
|
+
cn, // Class name merger (clsx + tailwind-merge)
|
|
418
|
+
composeRefs, // Compose multiple refs
|
|
419
|
+
useComposedRefs, // Hook for composing refs
|
|
420
|
+
useIsMobile, // Mobile detection hook
|
|
421
|
+
type ClassValue, // Type for cn function
|
|
422
|
+
} from "@carlonicora/nextjs-jsonapi/utils";
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### shadcn/ui (`/shadcnui`)
|
|
426
|
+
|
|
427
|
+
All shadcn/ui components (requires `"use client"`):
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import {
|
|
431
|
+
// UI Components (41)
|
|
432
|
+
Accordion, AccordionItem, AccordionTrigger, AccordionContent,
|
|
433
|
+
Alert, AlertTitle, AlertDescription,
|
|
434
|
+
AlertDialog, AlertDialogTrigger, AlertDialogContent, /* ... */
|
|
435
|
+
Avatar, AvatarImage, AvatarFallback,
|
|
436
|
+
Badge, badgeVariants,
|
|
437
|
+
Breadcrumb, BreadcrumbList, BreadcrumbItem, /* ... */
|
|
438
|
+
Button, buttonVariants,
|
|
439
|
+
Calendar,
|
|
440
|
+
Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
|
|
441
|
+
Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext,
|
|
442
|
+
ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, /* ... */
|
|
443
|
+
Checkbox,
|
|
444
|
+
Collapsible, CollapsibleTrigger, CollapsibleContent,
|
|
445
|
+
Command, CommandInput, CommandList, CommandItem, /* ... */
|
|
446
|
+
ContextMenu, ContextMenuTrigger, ContextMenuContent, /* ... */
|
|
447
|
+
Dialog, DialogTrigger, DialogContent, DialogHeader, /* ... */
|
|
448
|
+
Drawer, DrawerTrigger, DrawerContent, /* ... */
|
|
449
|
+
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, /* ... */
|
|
450
|
+
Form, FormField, FormItem, FormLabel, FormControl, /* ... */
|
|
451
|
+
HoverCard, HoverCardTrigger, HoverCardContent,
|
|
452
|
+
Input,
|
|
453
|
+
Label,
|
|
454
|
+
NavigationMenu, NavigationMenuList, NavigationMenuItem, /* ... */
|
|
455
|
+
Popover, PopoverTrigger, PopoverContent,
|
|
456
|
+
Progress,
|
|
457
|
+
RadioGroup, RadioGroupItem,
|
|
458
|
+
ScrollArea, ScrollBar,
|
|
459
|
+
Select, SelectTrigger, SelectValue, SelectContent, SelectItem, /* ... */
|
|
460
|
+
Separator,
|
|
461
|
+
Sheet, SheetTrigger, SheetContent, SheetHeader, /* ... */
|
|
462
|
+
Sidebar, SidebarProvider, SidebarContent, SidebarMenu, /* ... */
|
|
463
|
+
Skeleton,
|
|
464
|
+
Slider,
|
|
465
|
+
Sonner, Toaster, toast,
|
|
466
|
+
Switch,
|
|
467
|
+
Table, TableHeader, TableBody, TableRow, TableHead, TableCell, /* ... */
|
|
468
|
+
Tabs, TabsList, TabsTrigger, TabsContent,
|
|
469
|
+
Textarea,
|
|
470
|
+
Toggle, toggleVariants,
|
|
471
|
+
Tooltip, TooltipTrigger, TooltipContent, TooltipProvider,
|
|
472
|
+
|
|
473
|
+
// Custom Components (3)
|
|
474
|
+
Kanban, KanbanColumn, KanbanItem, // Drag-and-drop Kanban board
|
|
475
|
+
Link, // next-intl compatible Link
|
|
476
|
+
MultiSelect, // Multi-select dropdown
|
|
477
|
+
} from "@carlonicora/nextjs-jsonapi/shadcnui";
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Unified API
|
|
481
|
+
|
|
482
|
+
The unified API automatically detects whether code is running on the server or client and uses the appropriate request method.
|
|
483
|
+
|
|
484
|
+
### JsonApiGet
|
|
485
|
+
|
|
486
|
+
Fetch data from a JSON:API endpoint:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
const response = await JsonApiGet({
|
|
490
|
+
classKey: Modules.Article, // Module definition
|
|
491
|
+
endpoint: "/articles/123", // API endpoint
|
|
492
|
+
companyId: "company-uuid", // Optional: for multi-tenant apps
|
|
493
|
+
language: "en", // Required: for i18n
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
if (response.ok) {
|
|
497
|
+
console.log(response.data); // Deserialized data
|
|
498
|
+
console.log(response.pagination); // Pagination info
|
|
499
|
+
|
|
500
|
+
// Navigate pages
|
|
501
|
+
if (response.next) {
|
|
502
|
+
const nextPage = await response.nextPage();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### JsonApiPost
|
|
508
|
+
|
|
509
|
+
Create a new resource:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
const response = await JsonApiPost({
|
|
513
|
+
classKey: Modules.Article,
|
|
514
|
+
endpoint: "/articles",
|
|
515
|
+
body: { title: "New Article", content: "..." },
|
|
516
|
+
language: "en",
|
|
517
|
+
|
|
518
|
+
// Optional
|
|
519
|
+
files: { attachment: file }, // File uploads
|
|
520
|
+
overridesJsonApiCreation: false, // Use raw body instead of JSON:API format
|
|
521
|
+
responseType: Modules.OtherType, // If response type differs
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### JsonApiPut / JsonApiPatch
|
|
526
|
+
|
|
527
|
+
Update a resource:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
const response = await JsonApiPut({
|
|
531
|
+
classKey: Modules.Article,
|
|
532
|
+
endpoint: "/articles/123",
|
|
533
|
+
body: { title: "Updated Title" },
|
|
534
|
+
language: "en",
|
|
535
|
+
});
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### JsonApiDelete
|
|
539
|
+
|
|
540
|
+
Delete a resource:
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
const response = await JsonApiDelete({
|
|
544
|
+
classKey: Modules.Article,
|
|
545
|
+
endpoint: "/articles/123",
|
|
546
|
+
language: "en",
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Client Hooks
|
|
551
|
+
|
|
552
|
+
### useJsonApiGet
|
|
553
|
+
|
|
554
|
+
Hook for fetching data with automatic refetching:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
const {
|
|
558
|
+
data, // Fetched data or null
|
|
559
|
+
loading, // Loading state
|
|
560
|
+
error, // Error message or null
|
|
561
|
+
response, // Full API response
|
|
562
|
+
refetch, // Manual refetch function
|
|
563
|
+
hasNextPage, // Pagination: has next page
|
|
564
|
+
hasPreviousPage, // Pagination: has previous page
|
|
565
|
+
fetchNextPage, // Fetch next page
|
|
566
|
+
fetchPreviousPage, // Fetch previous page
|
|
567
|
+
} = useJsonApiGet<Article>({
|
|
568
|
+
classKey: Modules.Article,
|
|
569
|
+
endpoint: `/articles/${id}`,
|
|
570
|
+
companyId: companyId,
|
|
571
|
+
options: {
|
|
572
|
+
enabled: !!id, // Conditionally enable
|
|
573
|
+
deps: [someDependency], // Refetch when these change
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### useJsonApiMutation
|
|
579
|
+
|
|
580
|
+
Hook for mutations (POST, PUT, PATCH, DELETE):
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
const {
|
|
584
|
+
data, // Result data or null
|
|
585
|
+
loading, // Loading state
|
|
586
|
+
error, // Error message or null
|
|
587
|
+
response, // Full API response
|
|
588
|
+
mutate, // Execute the mutation
|
|
589
|
+
reset, // Reset state
|
|
590
|
+
} = useJsonApiMutation<Article>({
|
|
591
|
+
method: "POST",
|
|
592
|
+
classKey: Modules.Article,
|
|
593
|
+
onSuccess: (data) => console.log("Created:", data),
|
|
594
|
+
onError: (error) => console.error("Failed:", error),
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Execute mutation
|
|
598
|
+
const result = await mutate({
|
|
599
|
+
endpoint: "/articles",
|
|
600
|
+
body: { title: "New Article" },
|
|
601
|
+
files: { image: imageFile },
|
|
602
|
+
companyId: "company-uuid",
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
## Server Requests
|
|
607
|
+
|
|
608
|
+
For server components or API routes, use the server module directly:
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
import { serverRequest, getServerToken, getCacheProfile } from "@carlonicora/nextjs-jsonapi/server";
|
|
612
|
+
|
|
613
|
+
export async function getArticle(id: string) {
|
|
614
|
+
const token = await getServerToken();
|
|
615
|
+
|
|
616
|
+
const data = await serverRequest({
|
|
617
|
+
method: "GET",
|
|
618
|
+
url: `${process.env.NEXT_PUBLIC_API_URL}/articles/${id}`,
|
|
619
|
+
token,
|
|
620
|
+
cache: getCacheProfile("articles"), // Get cache settings
|
|
621
|
+
language: "en",
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
return data;
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Cache Profiles
|
|
629
|
+
|
|
630
|
+
The library supports Next.js 16+ caching via `cacheLife()` and `cacheTag()`:
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
// In your module definition
|
|
634
|
+
export const Modules = {
|
|
635
|
+
Article: {
|
|
636
|
+
type: "articles",
|
|
637
|
+
cache: "articles", // Profile name for caching
|
|
638
|
+
factory: (data: any) => data,
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// The cache profile is automatically applied when using JsonApiGet
|
|
643
|
+
// on the server side
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
## Permissions
|
|
647
|
+
|
|
648
|
+
Check user permissions for protected resources:
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
import { checkPermission } from "@carlonicora/nextjs-jsonapi/permissions";
|
|
652
|
+
|
|
653
|
+
// Check if user has permission
|
|
654
|
+
const canEdit = checkPermission({
|
|
655
|
+
user: currentUser,
|
|
656
|
+
action: "edit",
|
|
657
|
+
resource: "articles",
|
|
658
|
+
resourceId: article.id,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (!canEdit) {
|
|
662
|
+
return <div>Access denied</div>;
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## shadcn/ui Components
|
|
667
|
+
|
|
668
|
+
The package includes 44 pre-built shadcn/ui components:
|
|
669
|
+
|
|
670
|
+
### Standard UI Components (41)
|
|
671
|
+
|
|
672
|
+
| Component | Description |
|
|
673
|
+
|-----------|-------------|
|
|
674
|
+
| `Accordion` | Collapsible content sections |
|
|
675
|
+
| `Alert` | Callout for important messages |
|
|
676
|
+
| `AlertDialog` | Modal dialog for confirmations |
|
|
677
|
+
| `Avatar` | User profile images |
|
|
678
|
+
| `Badge` | Status indicators and labels |
|
|
679
|
+
| `Breadcrumb` | Navigation breadcrumbs |
|
|
680
|
+
| `Button` | Click actions with variants |
|
|
681
|
+
| `Calendar` | Date picker calendar |
|
|
682
|
+
| `Card` | Content container |
|
|
683
|
+
| `Carousel` | Sliding content panels |
|
|
684
|
+
| `Chart` | Data visualization (Recharts) |
|
|
685
|
+
| `Checkbox` | Toggle options |
|
|
686
|
+
| `Collapsible` | Expandable sections |
|
|
687
|
+
| `Command` | Command palette (cmdk) |
|
|
688
|
+
| `ContextMenu` | Right-click menus |
|
|
689
|
+
| `Dialog` | Modal windows |
|
|
690
|
+
| `Drawer` | Sliding side panels (Vaul) |
|
|
691
|
+
| `DropdownMenu` | Dropdown menus |
|
|
692
|
+
| `Form` | Form handling (react-hook-form) |
|
|
693
|
+
| `HoverCard` | Hover-triggered cards |
|
|
694
|
+
| `Input` | Text input fields |
|
|
695
|
+
| `Label` | Form labels |
|
|
696
|
+
| `NavigationMenu` | Navigation menus |
|
|
697
|
+
| `Popover` | Floating content |
|
|
698
|
+
| `Progress` | Progress indicators |
|
|
699
|
+
| `RadioGroup` | Radio button groups |
|
|
700
|
+
| `ScrollArea` | Custom scrollbars |
|
|
701
|
+
| `Select` | Dropdown selects |
|
|
702
|
+
| `Separator` | Visual dividers |
|
|
703
|
+
| `Sheet` | Side panels |
|
|
704
|
+
| `Sidebar` | Application sidebars |
|
|
705
|
+
| `Skeleton` | Loading placeholders |
|
|
706
|
+
| `Slider` | Range sliders |
|
|
707
|
+
| `Sonner` | Toast notifications |
|
|
708
|
+
| `Switch` | Toggle switches |
|
|
709
|
+
| `Table` | Data tables |
|
|
710
|
+
| `Tabs` | Tabbed interfaces |
|
|
711
|
+
| `Textarea` | Multi-line text input |
|
|
712
|
+
| `Toggle` | Toggle buttons |
|
|
713
|
+
| `Tooltip` | Hover tooltips |
|
|
714
|
+
|
|
715
|
+
### Custom Components (3)
|
|
716
|
+
|
|
717
|
+
| Component | Description |
|
|
718
|
+
|-----------|-------------|
|
|
719
|
+
| `Kanban` | Drag-and-drop Kanban board (dnd-kit) |
|
|
720
|
+
| `Link` | next-intl compatible link wrapper |
|
|
721
|
+
| `MultiSelect` | Multi-select dropdown with badges |
|
|
722
|
+
|
|
723
|
+
### Usage Example
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
"use client";
|
|
727
|
+
|
|
728
|
+
import {
|
|
729
|
+
Button,
|
|
730
|
+
Card,
|
|
731
|
+
CardHeader,
|
|
732
|
+
CardTitle,
|
|
733
|
+
CardContent,
|
|
734
|
+
Dialog,
|
|
735
|
+
DialogTrigger,
|
|
736
|
+
DialogContent,
|
|
737
|
+
DialogHeader,
|
|
738
|
+
DialogTitle,
|
|
739
|
+
} from "@carlonicora/nextjs-jsonapi/shadcnui";
|
|
740
|
+
import { cn } from "@carlonicora/nextjs-jsonapi/utils";
|
|
741
|
+
|
|
742
|
+
export function ArticleCard({ article, className }) {
|
|
743
|
+
return (
|
|
744
|
+
<Card className={cn("hover:shadow-lg transition-shadow", className)}>
|
|
745
|
+
<CardHeader>
|
|
746
|
+
<CardTitle>{article.title}</CardTitle>
|
|
747
|
+
</CardHeader>
|
|
748
|
+
<CardContent>
|
|
749
|
+
<p>{article.excerpt}</p>
|
|
750
|
+
|
|
751
|
+
<Dialog>
|
|
752
|
+
<DialogTrigger asChild>
|
|
753
|
+
<Button variant="outline">Read More</Button>
|
|
754
|
+
</DialogTrigger>
|
|
755
|
+
<DialogContent>
|
|
756
|
+
<DialogHeader>
|
|
757
|
+
<DialogTitle>{article.title}</DialogTitle>
|
|
758
|
+
</DialogHeader>
|
|
759
|
+
<p>{article.content}</p>
|
|
760
|
+
</DialogContent>
|
|
761
|
+
</Dialog>
|
|
762
|
+
</CardContent>
|
|
763
|
+
</Card>
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
## Tailwind CSS Configuration
|
|
769
|
+
|
|
770
|
+
**Important for Tailwind v4**: You must add the `@source` directive to your `globals.css` to ensure Tailwind scans the package's component files:
|
|
771
|
+
|
|
772
|
+
```css
|
|
773
|
+
/* apps/web/src/app/globals.css */
|
|
774
|
+
@import "tailwindcss";
|
|
775
|
+
@import "tw-animate-css";
|
|
776
|
+
|
|
777
|
+
/* Include package source files for Tailwind to scan */
|
|
778
|
+
@source "../../../../packages/nextjs-jsonapi/src/**/*.{ts,tsx}";
|
|
779
|
+
|
|
780
|
+
@custom-variant dark (&:is(.dark *));
|
|
781
|
+
|
|
782
|
+
@theme inline {
|
|
783
|
+
--color-background: var(--background);
|
|
784
|
+
--color-foreground: var(--foreground);
|
|
785
|
+
/* ... other theme variables */
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
The `@source` path should be relative from your `globals.css` to the package's `src` directory.
|
|
790
|
+
|
|
791
|
+
## CSS Variables
|
|
792
|
+
|
|
793
|
+
The shadcn/ui components require CSS variables to be defined in your application. Add these to your `globals.css`:
|
|
794
|
+
|
|
795
|
+
```css
|
|
796
|
+
:root {
|
|
797
|
+
/* Background & Foreground */
|
|
798
|
+
--background: oklch(1 0 0);
|
|
799
|
+
--foreground: oklch(0.145 0 0);
|
|
800
|
+
|
|
801
|
+
/* Primary */
|
|
802
|
+
--primary: oklch(0.205 0 0);
|
|
803
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
804
|
+
|
|
805
|
+
/* Secondary */
|
|
806
|
+
--secondary: oklch(0.97 0 0);
|
|
807
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
808
|
+
|
|
809
|
+
/* Muted */
|
|
810
|
+
--muted: oklch(0.97 0 0);
|
|
811
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
812
|
+
|
|
813
|
+
/* Accent */
|
|
814
|
+
--accent: oklch(0.97 0 0);
|
|
815
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
816
|
+
|
|
817
|
+
/* Destructive */
|
|
818
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
819
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
820
|
+
|
|
821
|
+
/* Border & Input */
|
|
822
|
+
--border: oklch(0.922 0 0);
|
|
823
|
+
--input: oklch(0.922 0 0);
|
|
824
|
+
--ring: oklch(0.708 0 0);
|
|
825
|
+
|
|
826
|
+
/* Card & Popover */
|
|
827
|
+
--card: oklch(1 0 0);
|
|
828
|
+
--card-foreground: oklch(0.145 0 0);
|
|
829
|
+
--popover: oklch(1 0 0);
|
|
830
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
831
|
+
|
|
832
|
+
/* Charts */
|
|
833
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
834
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
835
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
836
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
837
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
838
|
+
|
|
839
|
+
/* Sidebar */
|
|
840
|
+
--sidebar: oklch(0.985 0 0);
|
|
841
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
842
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
843
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
844
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
845
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
846
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
847
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
848
|
+
|
|
849
|
+
/* Warning */
|
|
850
|
+
--warning: oklch(0.84 0.16 84);
|
|
851
|
+
--warning-foreground: oklch(0.28 0.07 46);
|
|
852
|
+
|
|
853
|
+
/* Radius */
|
|
854
|
+
--radius: 0.625rem;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.dark {
|
|
858
|
+
--background: oklch(0.145 0 0);
|
|
859
|
+
--foreground: oklch(0.985 0 0);
|
|
860
|
+
--card: oklch(0.205 0 0);
|
|
861
|
+
--card-foreground: oklch(0.985 0 0);
|
|
862
|
+
--popover: oklch(0.269 0 0);
|
|
863
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
864
|
+
--primary: oklch(0.922 0 0);
|
|
865
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
866
|
+
--secondary: oklch(0.269 0 0);
|
|
867
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
868
|
+
--muted: oklch(0.269 0 0);
|
|
869
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
870
|
+
--accent: oklch(0.269 0 0);
|
|
871
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
872
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
873
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
874
|
+
--border: oklch(1 0 0 / 10%);
|
|
875
|
+
--input: oklch(1 0 0 / 15%);
|
|
876
|
+
--ring: oklch(0.556 0 0);
|
|
877
|
+
/* ... other dark mode values */
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
## License
|
|
882
|
+
|
|
883
|
+
This project is licensed under GPL v3 for open source use.
|
|
884
|
+
|
|
885
|
+
For commercial/closed-source licensing, contact: [@carlonicora](https://github.com/carlonicora)
|
|
886
|
+
|
|
887
|
+
## Author
|
|
888
|
+
|
|
889
|
+
Carlo Nicora - [@carlonicora](https://github.com/carlonicora)
|