@conexus-x/next-view 1.0.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/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ .next/
3
+ out/
4
+ next-env.d.ts
5
+ *.tsbuildinfo
6
+ .env*.local
7
+ .vercel
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # Conexus View
2
+
3
+ A starter **custom view** for Conexus X: Next.js 16 + React 19, wired to `@conexus-x/sdk`.
4
+
5
+ Copy this folder, rename it, and you have an app. It lists the records in whichever collection the person is looking at, completes them in place, and stays live while colleagues edit the same board.
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev # http://localhost:5173
10
+ ```
11
+
12
+ Running it directly in a browser tab shows **"Not connected to Conexus X"** — that is correct. A view is a page that expects to be framed by the board, which is where its context and its data come from.
13
+
14
+ ### Working against an unpublished SDK
15
+
16
+ `npm install` resolves `@conexus-x/sdk` from the registry. If you are developing
17
+ the SDK at the same time, link it instead of adding a path to `package.json` —
18
+ a `file:` dependency committed here would break for everyone else:
19
+
20
+ ```bash
21
+ cd ../conexus-x-sdk && npm run build && npm link
22
+ cd - && npm link @conexus-x/sdk
23
+ ```
24
+
25
+ `npm unlink @conexus-x/sdk && npm install` puts it back on the published version.
26
+ Nothing in this repo records the link, which is the point.
27
+
28
+
29
+ ## Tailwind is already set up
30
+
31
+ Tailwind v4 is installed and configured. There is no `tailwind.config.js` to
32
+ create, no CLI to run, no PostCSS file to write — v4 does not use one. Add
33
+ classes to your markup and they work.
34
+
35
+ The theme lives in `app/globals.css`, as a small set of semantic colours:
36
+
37
+ | Utility | Meaning |
38
+ | --- | --- |
39
+ | `bg-bg` / `bg-card` | page and surface |
40
+ | `text-fg` / `text-muted` | body and secondary text |
41
+ | `border-line` | dividers and borders |
42
+ | `text-accent` / `bg-accent-soft` | the interactive colour |
43
+ | `text-danger` / `bg-danger-soft` | errors |
44
+
45
+ **These flip with the board's theme on their own**, so you almost never write
46
+ `dark:`. The values swap under `[data-theme="dark"]`, which `ThemeSync` sets
47
+ from the host — `bg-card text-fg` is already correct in both themes.
48
+
49
+ A `dark:` variant is wired up for the cases where a whole utility has to change
50
+ rather than a colour. It reads the host's `data-theme`, **not**
51
+ `prefers-color-scheme`: someone may run Conexus X in dark while their OS is in
52
+ light, and a view that follows the OS looks like a hole cut in the page.
53
+
54
+ To change a colour, edit the two blocks at the top of `app/globals.css`. To add one, add it
55
+ in both blocks and give it a name in `@theme inline`.
56
+
57
+ ## What is where
58
+
59
+ | File | What it shows you |
60
+ | --- | --- |
61
+ | [app/page.tsx](app/page.tsx) | The whole view: connection state, context, a scoped read, host chrome |
62
+ | [components/RecordRow.tsx](components/RecordRow.tsx) | A scoped write, and why it does not update local state on success |
63
+ | [components/ThemeSync.tsx](components/ThemeSync.tsx) | Following the board theme without a flash of the wrong one |
64
+ | [conexus.manifest.json](conexus.manifest.json) | What the app declares — views, settings, scopes, and why it wants them |
65
+ | [next.config.ts](next.config.ts) | `frame-ancestors`, the header that decides whether this can be embedded at all |
66
+
67
+ ## The whole SDK surface, in one screen
68
+
69
+ ```tsx
70
+ const { status, error } = useConnection(); // connecting | ready | error
71
+ const context = useViewContext(); // board, collection, user, theme, environment
72
+ const settings = useSettings<ViewSettings>(); // what the person configured for this placement
73
+ const canWrite = useScope("records:write"); // feature-detect, never assume
74
+ const commands = useCommands(); // notice, openRecord, confirm, navigate, copy
75
+
76
+ const { data, loading, error, refresh } = useRecords(context?.collectionId);
77
+
78
+ useAutoResize(); // grow the frame to fit
79
+ ```
80
+
81
+ `useRecords` refetches on its own when the board changes — a colleague editing a cell, an automation moving a row, your own write coming back. There is no socket in this app and no polling; the host forwards what belongs to this board and the hook redraws.
82
+
83
+ For anything the hooks do not cover:
84
+
85
+ ```tsx
86
+ const cx = useConexus();
87
+ await cx.api.values.forRecord(recordId);
88
+ await cx.request({ method: "GET", path: "/columns/" + context.moduleId });
89
+ ```
90
+
91
+ ## Notice what is missing
92
+
93
+ No `fetch` of the CRM API. No base URL. No token. No socket. The host owns all four and proxies every call with the signed-in user's own credentials, gated by the scopes your manifest asked for and an admin approved. That is why this app is a few hundred lines.
94
+
95
+ ## Deploying
96
+
97
+ Any static or Node host works. On Vercel:
98
+
99
+ ```bash
100
+ vercel --prod
101
+ ```
102
+
103
+ Then set `entry` in `conexus.manifest.json` to the deployed URL, and set `CONEXUS_ORIGINS` on the deployment to the Conexus X origins allowed to frame you (comma-separated). Getting `frame-ancestors` wrong is the most common reason a working view renders as a blank rectangle.
104
+
105
+ ## Settings
106
+
107
+ Fields declared in the manifest are rendered by the host, stored per placement, and delivered to `useSettings()`. Give every field a default in your code as well — a view mounted before anyone opened its settings pane receives an empty object.
@@ -0,0 +1,92 @@
1
+ @import "tailwindcss";
2
+
3
+ /**
4
+ * TAILWIND IS ALREADY SET UP. There is nothing for you to configure.
5
+ *
6
+ * Tailwind v4 has no `tailwind.config.js` — the theme lives here, in CSS. The
7
+ * only other piece is postcss.config.mjs, which is three lines and already
8
+ * written. Add classes to your markup and they work.
9
+ */
10
+
11
+ /**
12
+ * DARK MODE FOLLOWS THE BOARD, NOT THE OPERATING SYSTEM.
13
+ *
14
+ * Tailwind's stock `dark:` variant reads `prefers-color-scheme`, which is the
15
+ * wrong signal here: the person may run Conexus X in dark while their OS is in
16
+ * light, and a view that follows the OS looks like a hole cut in the page. The
17
+ * host tells us its theme and ThemeSync writes it to `<html data-theme>`, so
18
+ * the variant reads that instead.
19
+ *
20
+ * You rarely need `dark:` at all — the semantic colours below already flip.
21
+ * It is here for the cases where a whole utility has to change, not a colour.
22
+ */
23
+ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
24
+
25
+ /**
26
+ * The palette, as two sets of the same names.
27
+ *
28
+ * Swapping the VALUES under `[data-theme]` rather than writing `dark:` on every
29
+ * element is what keeps the markup readable: `bg-card text-fg` is correct in
30
+ * both themes and there is one place to change a colour.
31
+ */
32
+ @layer base {
33
+ :root {
34
+ --cx-bg: #ffffff;
35
+ --cx-card: #ffffff;
36
+ --cx-fg: #18181b;
37
+ --cx-muted: #71717a;
38
+ --cx-line: #e4e4e7;
39
+ --cx-accent: #2563eb;
40
+ --cx-accent-soft: #eff6ff;
41
+ --cx-danger: #b91c1c;
42
+ --cx-danger-soft: #fef2f2;
43
+
44
+ color-scheme: light;
45
+ }
46
+
47
+ [data-theme="dark"] {
48
+ --cx-bg: #18181b;
49
+ --cx-card: #1f1f23;
50
+ --cx-fg: #fafafa;
51
+ --cx-muted: #a1a1aa;
52
+ --cx-line: #3f3f46;
53
+ --cx-accent: #60a5fa;
54
+ --cx-accent-soft: #1e293b;
55
+ --cx-danger: #fca5a5;
56
+ --cx-danger-soft: #450a0a;
57
+
58
+ color-scheme: dark;
59
+ }
60
+
61
+ body {
62
+ background-color: var(--cx-bg);
63
+ color: var(--cx-fg);
64
+ font-family: var(--font-sans);
65
+ -webkit-font-smoothing: antialiased;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * `inline` matters.
71
+ *
72
+ * It makes each utility emit `var(--cx-…)` directly instead of copying the
73
+ * value into a second variable. That is what lets the theme flip at runtime:
74
+ * the utility resolves the variable at paint time, under whatever `data-theme`
75
+ * is on the element.
76
+ *
77
+ * Every name here becomes a full set of utilities — `bg-card`, `text-muted`,
78
+ * `border-line`, `ring-accent`, and so on.
79
+ */
80
+ @theme inline {
81
+ --color-bg: var(--cx-bg);
82
+ --color-card: var(--cx-card);
83
+ --color-fg: var(--cx-fg);
84
+ --color-muted: var(--cx-muted);
85
+ --color-line: var(--cx-line);
86
+ --color-accent: var(--cx-accent);
87
+ --color-accent-soft: var(--cx-accent-soft);
88
+ --color-danger: var(--cx-danger);
89
+ --color-danger-soft: var(--cx-danger-soft);
90
+
91
+ --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
92
+ }
package/app/layout.tsx ADDED
@@ -0,0 +1,28 @@
1
+ import type { Metadata } from "next";
2
+ import type { ReactNode } from "react";
3
+
4
+ import "./globals.css";
5
+
6
+ export const metadata: Metadata = {
7
+ title: "Conexus View",
8
+ description: "A custom view running inside a Conexus X board."
9
+ };
10
+
11
+ /**
12
+ * No fonts, no analytics, no chrome.
13
+ *
14
+ * This document is drawn INSIDE someone board, a few hundred pixels tall,
15
+ * behind whatever the customer is actually working on. Every kilobyte here is
16
+ * paid for by a person who did not ask for it, and anything that looks like
17
+ * page furniture reads as a bug in the host.
18
+ */
19
+ export default function RootLayout({ children }: { children: ReactNode }) {
20
+ return (
21
+ <html lang="en" suppressHydrationWarning>
22
+ {/* The theme is applied client-side from the host context — the
23
+ server cannot know it, and guessing produces a flash of the
24
+ wrong one on every mount. */}
25
+ <body>{children}</body>
26
+ </html>
27
+ );
28
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,143 @@
1
+ "use client";
2
+
3
+ /**
4
+ * The starter view.
5
+ *
6
+ * Lists the records in whichever collection the person is looking at, lets them
7
+ * tick one complete, and stays live while colleagues edit the same board. It is
8
+ * the smallest thing that exercises every part of the SDK worth knowing:
9
+ * context, a scoped read, a scoped write, host chrome, and realtime.
10
+ *
11
+ * Note what is NOT here: no fetch, no API base URL, no token, no socket. The
12
+ * host owns all four, which is why this file is short.
13
+ *
14
+ * Styling is Tailwind, already configured — see app/globals.css. The colour
15
+ * names (`bg-card`, `text-muted`, `border-line`) flip with the board's theme on
16
+ * their own, so almost nothing here needs a `dark:` variant.
17
+ */
18
+
19
+ import {
20
+ useAutoResize,
21
+ useCommands,
22
+ useConnection,
23
+ useRecords,
24
+ useScope,
25
+ useSettings,
26
+ useViewContext
27
+ } from "@conexus-x/sdk/react";
28
+
29
+ import { RecordRow } from "@/components/RecordRow";
30
+ import { ThemeSync } from "@/components/ThemeSync";
31
+
32
+ interface ViewSettings {
33
+ title?: string;
34
+ showCompleted?: boolean;
35
+ }
36
+
37
+ export default function View() {
38
+ const { status, error } = useConnection();
39
+ const context = useViewContext();
40
+ const commands = useCommands();
41
+ const canWrite = useScope("records:write");
42
+
43
+ /**
44
+ * What the person configured for THIS placement of the view.
45
+ *
46
+ * The manifest declares the fields, the host renders the pane and stores
47
+ * the answers, and this type is the contract between the two — so a default
48
+ * belongs here as well, because a view mounted before anyone opened its
49
+ * settings receives an empty object.
50
+ */
51
+ const settings = useSettings<ViewSettings>();
52
+ const title = settings.title ?? "Records";
53
+ const showCompleted = settings.showCompleted ?? true;
54
+
55
+ const {
56
+ data: records,
57
+ loading,
58
+ error: recordsError,
59
+ refresh
60
+ } = useRecords(context?.collectionId);
61
+
62
+ // Grow the frame to fit the content. The host resizes the slot it gave us.
63
+ useAutoResize();
64
+
65
+ if (status === "connecting") {
66
+ return (
67
+ <main className="p-4">
68
+ <div className="mb-3 h-3.5 w-2/5 animate-pulse rounded bg-line" />
69
+ <div className="mb-2 h-3.5 w-full animate-pulse rounded bg-line" />
70
+ <div className="h-3.5 w-4/5 animate-pulse rounded bg-line" />
71
+ </main>
72
+ );
73
+ }
74
+
75
+ if (status === "error") {
76
+ return (
77
+ <main className="p-4">
78
+ <div className="rounded-lg border border-danger bg-danger-soft px-3.5 py-3 text-danger">
79
+ <strong className="font-semibold">Not connected to Conexus X.</strong>
80
+ <p className="mt-1.5">
81
+ {error?.message ??
82
+ "This page expects to run inside a Conexus X board."}
83
+ </p>
84
+ </div>
85
+ </main>
86
+ );
87
+ }
88
+
89
+ return (
90
+ <main className="p-4">
91
+ <ThemeSync theme={context?.theme ?? "light"} />
92
+
93
+ <header className="mb-3 flex items-baseline justify-between gap-3">
94
+ <h1 className="text-[15px] font-semibold">{title}</h1>
95
+ <span className="text-[13px] text-muted">
96
+ {context?.environment === "test" ? "Test environment" : null}
97
+ </span>
98
+ </header>
99
+
100
+ {!context?.collectionId ? (
101
+ <p className="text-[13px] text-muted">Open a collection to see its records.</p>
102
+ ) : recordsError ? (
103
+ <div className="rounded-lg border border-danger bg-danger-soft px-3.5 py-3 text-danger">
104
+ {recordsError.code === "scope_denied"
105
+ ? "This app was not granted permission to read records."
106
+ : recordsError.message}
107
+ </div>
108
+ ) : loading && !records ? (
109
+ <div className="h-3.5 w-full animate-pulse rounded bg-line" />
110
+ ) : (
111
+ <ul>
112
+ {(records ?? [])
113
+ .filter((record) => showCompleted || !record.isCompleted)
114
+ .map((record) => (
115
+ <RecordRow
116
+ key={record._id}
117
+ record={record}
118
+ canWrite={canWrite}
119
+ onOpen={() =>
120
+ commands
121
+ .openRecord(record._id)
122
+ .catch(() =>
123
+ commands.notice(
124
+ "This board cannot open records from a view.",
125
+ "info"
126
+ )
127
+ )
128
+ }
129
+ onError={(message) => {
130
+ void commands.notice(message, "error");
131
+ refresh();
132
+ }}
133
+ />
134
+ ))}
135
+ </ul>
136
+ )}
137
+
138
+ {records?.length === 0 && context?.collectionId ? (
139
+ <p className="text-[13px] text-muted">No records in this collection yet.</p>
140
+ ) : null}
141
+ </main>
142
+ );
143
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ import { useConexus, type CxRecord } from "@conexus-x/sdk/react";
6
+
7
+ interface RecordRowProps {
8
+ record: CxRecord;
9
+ canWrite: boolean;
10
+ onOpen: () => void;
11
+ onError: (message: string) => void;
12
+ }
13
+
14
+ /** Shared button look. Extracted because two buttons is where copies start. */
15
+ const button =
16
+ "rounded-md border border-line px-2.5 py-[3px] text-[13px] text-accent " +
17
+ "transition-colors hover:bg-accent-soft disabled:cursor-not-allowed " +
18
+ "disabled:opacity-50 disabled:hover:bg-transparent";
19
+
20
+ /**
21
+ * One row.
22
+ *
23
+ * The complete/reopen button does NOT update local state on success. The write
24
+ * comes back as a realtime change, `useRecords` refetches, and the row redraws
25
+ * from the server — which is the same path a colleague edit takes. Keeping one
26
+ * path means the view cannot end up showing a local guess that the board never
27
+ * accepted.
28
+ */
29
+ export function RecordRow({ record, canWrite, onOpen, onError }: RecordRowProps) {
30
+ const cx = useConexus();
31
+ const [saving, setSaving] = useState(false);
32
+
33
+ const toggle = async () => {
34
+ setSaving(true);
35
+
36
+ try {
37
+ await cx.api.records.update(record._id, {
38
+ isCompleted: !record.isCompleted
39
+ });
40
+ } catch (error) {
41
+ onError(
42
+ error instanceof Error
43
+ ? error.message
44
+ : "Could not update that record."
45
+ );
46
+ } finally {
47
+ setSaving(false);
48
+ }
49
+ };
50
+
51
+ return (
52
+ <li className="flex items-center gap-2.5 border-b border-line py-2 last:border-b-0">
53
+ <span
54
+ className={
55
+ record.isCompleted
56
+ ? "min-w-0 flex-1 truncate text-muted line-through"
57
+ : "min-w-0 flex-1 truncate"
58
+ }
59
+ >
60
+ {record.name}
61
+ </span>
62
+
63
+ <button type="button" onClick={onOpen} className={button}>
64
+ Open
65
+ </button>
66
+
67
+ <button
68
+ type="button"
69
+ onClick={toggle}
70
+ // Disabled rather than hidden when the scope is missing: a
71
+ // button that vanishes reads as a bug, one that is greyed out
72
+ // reads as a permission.
73
+ disabled={!canWrite || saving}
74
+ title={canWrite ? undefined : "This app cannot change records."}
75
+ className={button}
76
+ >
77
+ {saving ? "Saving…" : record.isCompleted ? "Reopen" : "Complete"}
78
+ </button>
79
+ </li>
80
+ );
81
+ }
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ /**
6
+ * Mirror the host theme onto <html data-theme>, where globals.css reads it.
7
+ *
8
+ * Written to the DOM in an effect rather than rendered as an attribute because
9
+ * the server has no idea which theme the board is in — rendering a guess and
10
+ * correcting it on hydration is a visible flash of the wrong colours on every
11
+ * single mount, and this view remounts whenever the person opens the tab.
12
+ */
13
+ export function ThemeSync({ theme }: { theme: "light" | "dark" }) {
14
+ useEffect(() => {
15
+ document.documentElement.dataset.theme = theme;
16
+ }, [theme]);
17
+
18
+ return null;
19
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "conexus-next-view",
3
+ "name": "Conexus Next View",
4
+ "version": "1.0.0",
5
+ "description": "Starter custom view (Next.js) — lists the records in the current collection and completes them in place.",
6
+ "publisher": {
7
+ "name": "Conexus X",
8
+ "email": "apps@conexus-x.example",
9
+ "website": "https://conexus-x.example"
10
+ },
11
+ "entry": "https://conexus-next-view.vercel.app",
12
+ "views": [
13
+ {
14
+ "id": "records",
15
+ "name": "Records",
16
+ "surface": "module",
17
+ "defaultHeight": 480,
18
+ "settings": [
19
+ {
20
+ "key": "title",
21
+ "label": "Heading",
22
+ "type": "text",
23
+ "default": "Records"
24
+ },
25
+ {
26
+ "key": "showCompleted",
27
+ "label": "Include completed records",
28
+ "type": "boolean",
29
+ "default": true
30
+ }
31
+ ]
32
+ }
33
+ ],
34
+ "scopes": [
35
+ "records:read",
36
+ "records:write"
37
+ ],
38
+ "permissionsRationale": {
39
+ "records:read": "To list the records in the collection you are looking at.",
40
+ "records:write": "To tick a record complete from inside the view."
41
+ }
42
+ }
package/next.config.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ /**
4
+ * A view is meant to be framed. Say so explicitly.
5
+ *
6
+ * Most hosting defaults (and every security checklist) push you toward
7
+ * `X-Frame-Options: DENY`, which renders a custom view as a blank rectangle
8
+ * with an error only in the console — the single most common way an embedded
9
+ * app appears broken for a reason nobody can see. `frame-ancestors` is the
10
+ * modern control and it is an ALLOWLIST, not an opt-out: only the Conexus X
11
+ * origins named here may embed this app, so this is stricter than DENY plus a
12
+ * hole, not weaker.
13
+ *
14
+ * CONEXUS_ORIGINS is comma-separated so a deploy can add a preview domain
15
+ * without a code change.
16
+ */
17
+ const conexusOrigins =
18
+ process.env.CONEXUS_ORIGINS ??
19
+ "http://localhost:3000 https://conexus-x.vercel.app";
20
+
21
+ const nextConfig: NextConfig = {
22
+ /**
23
+ * NOT pinning `turbopack.root` here, deliberately.
24
+ *
25
+ * Turbopack warns when it infers a workspace root from a lockfile further
26
+ * up the tree, and the obvious fix is `turbopack: { root: import.meta.dirname }`.
27
+ * Do not add it while you are developing against a locally linked SDK
28
+ * (`npm link @conexus-x/sdk`): the link resolves to a folder OUTSIDE this
29
+ * one, and a pinned root refuses to look there, so the import stops
30
+ * resolving with an error that says nothing about linking.
31
+ *
32
+ * With `@conexus-x/sdk` installed normally from the registry, pinning the
33
+ * root is safe and silences the warning.
34
+ */
35
+ async headers() {
36
+ return [
37
+ {
38
+ source: "/:path*",
39
+ headers: [
40
+ {
41
+ key: "Content-Security-Policy",
42
+ value: `frame-ancestors 'self' ${conexusOrigins.split(",").join(" ")};`
43
+ },
44
+ // The host passes context in the query string; nothing here
45
+ // should be cached across two different mounts of the view.
46
+ { key: "Cache-Control", value: "no-store" }
47
+ ]
48
+ }
49
+ ];
50
+ }
51
+ };
52
+
53
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@conexus-x/next-view",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "description": "Custom view for Conexus X — Next.js starter, wired to @conexus-x/sdk.",
6
+ "scripts": {
7
+ "dev": "next dev --port 5173",
8
+ "build": "next build",
9
+ "start": "next start --port 5173",
10
+ "lint": "eslint"
11
+ },
12
+ "dependencies": {
13
+ "@conexus-x/sdk": "^0.1.0",
14
+ "next": "16.2.10",
15
+ "react": "19.2.4",
16
+ "react-dom": "19.2.4"
17
+ },
18
+ "devDependencies": {
19
+ "@tailwindcss/postcss": "^4.3.3",
20
+ "@types/node": "^20",
21
+ "@types/react": "^19",
22
+ "@types/react-dom": "^19",
23
+ "tailwindcss": "^4.3.3",
24
+ "typescript": "^5"
25
+ },
26
+ "publishConfig": {
27
+ "access": "restricted"
28
+ },
29
+ "files": [
30
+ "app",
31
+ "components",
32
+ "next.config.ts",
33
+ "postcss.config.mjs",
34
+ "tsconfig.json",
35
+ "conexus.manifest.json",
36
+ "README.md",
37
+ ".gitignore"
38
+ ]
39
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tailwind v4 needs exactly this and nothing else.
3
+ *
4
+ * No `tailwind.config.js`, no `autoprefixer`, no `postcss-import` — v4 handles
5
+ * all three itself. If you have set Tailwind up before and this file looks too
6
+ * short, it is not missing anything.
7
+ */
8
+ const config = {
9
+ plugins: {
10
+ "@tailwindcss/postcss": {}
11
+ }
12
+ };
13
+
14
+ export default config;
package/tsconfig.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./*"
28
+ ]
29
+ }
30
+ },
31
+ "include": [
32
+ "next-env.d.ts",
33
+ "**/*.ts",
34
+ "**/*.tsx",
35
+ ".next/types/**/*.ts",
36
+ ".next/dev/types/**/*.ts"
37
+ ],
38
+ "exclude": [
39
+ "node_modules"
40
+ ]
41
+ }