create-questpie 2.0.1 → 2.0.3
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 +10 -6
- package/dist/index.mjs +139 -24
- package/package.json +5 -3
- package/skills/questpie/AGENTS.md +2670 -0
- package/skills/questpie/SKILL.md +260 -0
- package/skills/questpie/references/auth.md +121 -0
- package/skills/questpie/references/business-logic.md +550 -0
- package/skills/questpie/references/codegen-plugin-api.md +382 -0
- package/skills/questpie/references/crud-api.md +378 -0
- package/skills/questpie/references/data-modeling.md +493 -0
- package/skills/questpie/references/extend.md +557 -0
- package/skills/questpie/references/field-types.md +386 -0
- package/skills/questpie/references/infrastructure-adapters.md +545 -0
- package/skills/questpie/references/multi-tenancy.md +364 -0
- package/skills/questpie/references/production.md +475 -0
- package/skills/questpie/references/query-operators.md +125 -0
- package/skills/questpie/references/quickstart.md +564 -0
- package/skills/questpie/references/rules.md +389 -0
- package/skills/questpie/references/tanstack-query.md +520 -0
- package/skills/questpie-admin/AGENTS.md +1508 -0
- package/skills/questpie-admin/SKILL.md +436 -0
- package/skills/questpie-admin/references/blocks.md +331 -0
- package/skills/questpie-admin/references/custom-ui.md +305 -0
- package/skills/questpie-admin/references/views.md +449 -0
- package/templates/tanstack-start/AGENTS.md +17 -13
- package/templates/tanstack-start/CLAUDE.md +15 -12
- package/templates/tanstack-start/README.md +19 -13
- package/templates/tanstack-start/env.example +1 -1
- package/templates/tanstack-start/package.json +20 -6
- package/templates/tanstack-start/src/lib/env.ts +1 -1
- package/templates/tanstack-start/src/lib/query-client.ts +10 -1
- package/templates/tanstack-start/src/questpie/server/config/admin.ts +27 -30
- package/templates/tanstack-start/src/routeTree.gen.ts +138 -0
- package/templates/tanstack-start/src/routes/__root.tsx +0 -2
- package/templates/tanstack-start/src/routes/admin/$.tsx +12 -1
- package/templates/tanstack-start/src/routes/admin/index.tsx +12 -5
- package/templates/tanstack-start/src/routes/admin.tsx +8 -1
- package/templates/tanstack-start/src/tanstack-start.d.ts +1 -0
- package/templates/tanstack-start/src/vite-env.d.ts +1 -0
- package/templates/tanstack-start/vite.config.ts +1 -3
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: questpie-admin
|
|
3
|
+
description: QUESTPIE admin panel — setup, branding, theming, sidebar, dashboard, views, blocks, custom fields, media, localization, live preview, auth, dark mode, CSS variables. Use when building or customizing the QUESTPIE admin UI.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: questpie
|
|
7
|
+
version: "3.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# QUESTPIE Admin Panel
|
|
11
|
+
|
|
12
|
+
The QUESTPIE admin panel is a **projection of your server schema** — not the framework itself. It reads collections, globals, and config via introspection and generates a full admin interface. Your backend works without it.
|
|
13
|
+
|
|
14
|
+
## Reference Topics
|
|
15
|
+
|
|
16
|
+
| Topic | File | Covers |
|
|
17
|
+
| --------- | ------------------------- | -------------------------------------------------------------------------------------- |
|
|
18
|
+
| Views | `references/views.md` | List views, form views, dashboard, sidebar, filters, bulk actions, visibility, history |
|
|
19
|
+
| Blocks | `references/blocks.md` | Block definitions, fields, prefetch, renderers, block picker |
|
|
20
|
+
| Custom UI | `references/custom-ui.md` | Custom fields, custom views, registries, reactive fields, widgets |
|
|
21
|
+
|
|
22
|
+
## Full Compiled Document
|
|
23
|
+
|
|
24
|
+
For the complete admin reference with all topics expanded: `AGENTS.md`
|
|
25
|
+
|
|
26
|
+
## Tech Stack
|
|
27
|
+
|
|
28
|
+
- **React** + **Tailwind CSS v4** + **shadcn** components
|
|
29
|
+
- **@base-ui/react** primitives (NOT @radix-ui)
|
|
30
|
+
- **@iconify/react** with Phosphor icon set (`ph:icon-name`)
|
|
31
|
+
- **sonner** for toasts — `toast.error()`, `toast.success()`
|
|
32
|
+
- Brutalist flat design: `--radius: 0px`, no shadows
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
### 1. Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun add @questpie/admin
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Runtime Config
|
|
43
|
+
|
|
44
|
+
```ts title="questpie.config.ts"
|
|
45
|
+
import { runtimeConfig } from "questpie";
|
|
46
|
+
|
|
47
|
+
export default runtimeConfig({
|
|
48
|
+
app: { url: process.env.APP_URL || "http://localhost:3000" },
|
|
49
|
+
db: { url: process.env.DATABASE_URL },
|
|
50
|
+
secret: process.env.APP_SECRET,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The admin module contributes the codegen plugin automatically. It discovers `config/admin.ts`, `blocks/`, views, components, and admin client modules.
|
|
55
|
+
|
|
56
|
+
### 3. Modules
|
|
57
|
+
|
|
58
|
+
```ts title="modules.ts"
|
|
59
|
+
import { adminModule, auditModule } from "@questpie/admin/server";
|
|
60
|
+
|
|
61
|
+
export default [adminModule, auditModule] as const;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Module | Provides |
|
|
65
|
+
| ------------- | ------------------------------------- |
|
|
66
|
+
| `adminModule` | User collection, auth pages, admin UI |
|
|
67
|
+
| `auditModule` | Audit log collection, timeline widget |
|
|
68
|
+
|
|
69
|
+
### 4. Admin Config
|
|
70
|
+
|
|
71
|
+
```ts title="config/admin.ts"
|
|
72
|
+
import { adminConfig } from "#questpie/factories";
|
|
73
|
+
|
|
74
|
+
export default adminConfig({
|
|
75
|
+
branding: {
|
|
76
|
+
name: { en: "My App Admin" },
|
|
77
|
+
},
|
|
78
|
+
sidebar: {
|
|
79
|
+
sections: [
|
|
80
|
+
{ id: "overview", title: { en: "Overview" } },
|
|
81
|
+
{ id: "content", title: { en: "Content" } },
|
|
82
|
+
],
|
|
83
|
+
items: [
|
|
84
|
+
{
|
|
85
|
+
sectionId: "overview",
|
|
86
|
+
type: "link",
|
|
87
|
+
label: { en: "Dashboard" },
|
|
88
|
+
href: "/admin",
|
|
89
|
+
icon: { type: "icon", props: { name: "ph:house" } },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
sectionId: "content",
|
|
93
|
+
type: "collection",
|
|
94
|
+
collection: "posts",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 5. Codegen
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
bunx questpie generate
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 6. Mount the Admin
|
|
108
|
+
|
|
109
|
+
```ts title="routes/admin/$.tsx"
|
|
110
|
+
import { AdminRouter } from "@questpie/admin/client";
|
|
111
|
+
import { admin } from "@/questpie/admin/admin";
|
|
112
|
+
|
|
113
|
+
export default function AdminPage() {
|
|
114
|
+
return <AdminRouter admin={admin} />;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The admin client config is auto-generated by codegen at `admin/.generated/client.ts`. No manual builder setup needed.
|
|
119
|
+
|
|
120
|
+
## Branding
|
|
121
|
+
|
|
122
|
+
```ts title="config/admin.ts"
|
|
123
|
+
import { adminConfig } from "#questpie/factories";
|
|
124
|
+
|
|
125
|
+
export default adminConfig({
|
|
126
|
+
branding: {
|
|
127
|
+
name: { en: "Barbershop Control", sk: "Riadenie barbershopu" },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Option | Type | Description |
|
|
133
|
+
| ------ | ---------------- | -------------------------------- |
|
|
134
|
+
| `name` | `string \| i18n` | App name shown in sidebar header |
|
|
135
|
+
| `logo` | `string` | Logo URL or path |
|
|
136
|
+
|
|
137
|
+
## Theming (CSS Variables)
|
|
138
|
+
|
|
139
|
+
The admin uses CSS variables for all theming. Override them in your own CSS file.
|
|
140
|
+
|
|
141
|
+
### Light Theme (`:root`)
|
|
142
|
+
|
|
143
|
+
| Variable | Default | Purpose |
|
|
144
|
+
| ---------------------- | --------- | -------------------------------- |
|
|
145
|
+
| `--background` | `#FFFFFF` | Page background |
|
|
146
|
+
| `--foreground` | `#0A0A0A` | Primary text |
|
|
147
|
+
| `--card` | `#F8F8F8` | Cards, panels, sidebar |
|
|
148
|
+
| `--popover` | `#FFFFFF` | Dropdowns, tooltips, dialogs |
|
|
149
|
+
| `--muted` | `#F0F0F0` | Hover states, table headers |
|
|
150
|
+
| `--muted-foreground` | `#666666` | Secondary text, placeholders |
|
|
151
|
+
| `--primary` | `#B700FF` | Brand accent (CTAs, focus rings) |
|
|
152
|
+
| `--primary-foreground` | `#FFFFFF` | Text on primary backgrounds |
|
|
153
|
+
| `--destructive` | `#FF3D57` | Errors, delete actions |
|
|
154
|
+
| `--success` | `#00E676` | Positive states |
|
|
155
|
+
| `--warning` | `#FFB300` | Caution states |
|
|
156
|
+
| `--info` | `#40C4FF` | Informational emphasis |
|
|
157
|
+
| `--border` | `#E0E0E0` | All structural borders |
|
|
158
|
+
| `--ring` | `#B700FF` | Focus ring color |
|
|
159
|
+
| `--radius` | `0px` | Border radius (0 = brutalist) |
|
|
160
|
+
|
|
161
|
+
### Dark Theme (`.dark` class)
|
|
162
|
+
|
|
163
|
+
Dark mode uses the `.dark` class on the root element. Key overrides:
|
|
164
|
+
|
|
165
|
+
| Variable | Dark Value |
|
|
166
|
+
| -------------- | ---------- |
|
|
167
|
+
| `--background` | `#0A0A0A` |
|
|
168
|
+
| `--foreground` | `#FFFFFF` |
|
|
169
|
+
| `--card` | `#111111` |
|
|
170
|
+
| `--border` | `#333333` |
|
|
171
|
+
| `--muted` | `#1A1A1A` |
|
|
172
|
+
|
|
173
|
+
### Typography
|
|
174
|
+
|
|
175
|
+
| Variable | Value |
|
|
176
|
+
| ------------- | ------------------------------------------------------------------- |
|
|
177
|
+
| `--font-sans` | `"Geist Variable"` — body text, descriptions |
|
|
178
|
+
| `--font-mono` | `"JetBrains Mono Variable"` — UI chrome: nav, buttons, tabs, badges |
|
|
179
|
+
|
|
180
|
+
### Sidebar Variables
|
|
181
|
+
|
|
182
|
+
Separate tokens for independent sidebar theming: `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-accent`, `--sidebar-border`, `--sidebar-ring`.
|
|
183
|
+
|
|
184
|
+
### Custom Theme
|
|
185
|
+
|
|
186
|
+
1. Copy the admin CSS file
|
|
187
|
+
2. Change variable values
|
|
188
|
+
3. Import your copy instead
|
|
189
|
+
4. Zero component changes needed
|
|
190
|
+
|
|
191
|
+
## Content Localization
|
|
192
|
+
|
|
193
|
+
When collections have `.localized()` fields, the admin shows a locale switcher:
|
|
194
|
+
|
|
195
|
+
```ts title="config/app.ts"
|
|
196
|
+
import { appConfig } from "questpie";
|
|
197
|
+
|
|
198
|
+
export default appConfig({
|
|
199
|
+
locale: {
|
|
200
|
+
locales: [
|
|
201
|
+
{ code: "en", label: { en: "English" }, flagCountryCode: "gb" },
|
|
202
|
+
{ code: "sk", label: { en: "Slovak" } },
|
|
203
|
+
],
|
|
204
|
+
defaultLocale: "en",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The admin tracks content locale separately from UI locale. Only localized field values change when switching.
|
|
210
|
+
|
|
211
|
+
## Media & Uploads
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
avatar: f.upload({
|
|
215
|
+
to: "assets",
|
|
216
|
+
mimeTypes: ["image/*"],
|
|
217
|
+
maxSize: 5_000_000,
|
|
218
|
+
}),
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The admin renders drag-and-drop upload, image preview, file info, and remove button.
|
|
222
|
+
|
|
223
|
+
## Live Preview
|
|
224
|
+
|
|
225
|
+
Live Preview is one system: the existing collection `FormView`, Preview button, `LivePreviewMode`, and frontend iframe. Preserve the normal form lifecycle. Do not introduce a separate visual-edit form API, a second default form view, or a parallel preview surface.
|
|
226
|
+
|
|
227
|
+
The admin form is authoritative. The iframe mirrors form state through `postMessage`, supports field/block focus, and may request inline scalar edits. Persistence still goes through existing save, autosave, Cmd+S, history, workflow, locks, and actions.
|
|
228
|
+
|
|
229
|
+
### Server Config
|
|
230
|
+
|
|
231
|
+
Add `.preview()` to a collection to enable split-screen editing:
|
|
232
|
+
|
|
233
|
+
```ts title="collections/pages.ts"
|
|
234
|
+
export const pages = collection("pages")
|
|
235
|
+
.fields(({ f }) => ({
|
|
236
|
+
title: f.text().required().localized(),
|
|
237
|
+
slug: f.text().required(),
|
|
238
|
+
content: f.blocks().localized(),
|
|
239
|
+
}))
|
|
240
|
+
.preview({
|
|
241
|
+
enabled: true,
|
|
242
|
+
position: "right",
|
|
243
|
+
defaultWidth: 50,
|
|
244
|
+
url: ({ record }) => {
|
|
245
|
+
const slug = record.slug as string;
|
|
246
|
+
return slug === "home" ? "/?preview=true" : `/${slug}?preview=true`;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Preview opens the existing split-screen editor. Patches and refresh/resync messages update the iframe mirror; save/autosave still writes through the form.
|
|
252
|
+
|
|
253
|
+
### Prepare a Frontend Page
|
|
254
|
+
|
|
255
|
+
Use exported APIs only: `useCollectionPreview`, `PreviewProvider`, `PreviewField`, and `BlockRenderer`.
|
|
256
|
+
|
|
257
|
+
Checklist:
|
|
258
|
+
|
|
259
|
+
1. Configure `.preview({ url })` on the collection.
|
|
260
|
+
2. Load the same record shape the page normally renders.
|
|
261
|
+
3. For workflow-published pages, public reads use `stage: "published"`; if the public client/HTTP API is exposed, anonymous read access also checks `input?.stage === "published"`. Authorized preview/draft reads can load the working record.
|
|
262
|
+
4. Call `useCollectionPreview({ initialData, onRefresh })` in the page renderer.
|
|
263
|
+
5. Wrap the visual output in `PreviewProvider`.
|
|
264
|
+
6. Render from `preview.data`, not the original loader object.
|
|
265
|
+
7. Wrap editable scalar text with `PreviewField field="..." editable="text" | "textarea"`.
|
|
266
|
+
8. Render block content with `BlockRenderer`; pass `selectedBlockId` and `onBlockClick`.
|
|
267
|
+
9. Keep add/remove/reorder/nesting block operations in the existing block editor.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import {
|
|
271
|
+
BlockRenderer,
|
|
272
|
+
PreviewField,
|
|
273
|
+
PreviewProvider,
|
|
274
|
+
useCollectionPreview,
|
|
275
|
+
} from "@questpie/admin/client";
|
|
276
|
+
import admin from "@/questpie/admin/.generated/client";
|
|
277
|
+
|
|
278
|
+
function PagePreview({ page }) {
|
|
279
|
+
const router = useRouter();
|
|
280
|
+
const preview = useCollectionPreview({
|
|
281
|
+
initialData: page,
|
|
282
|
+
onRefresh: () => router.invalidate(),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<PreviewProvider preview={preview}>
|
|
287
|
+
<main className={preview.isPreviewMode ? "questpie-preview" : undefined}>
|
|
288
|
+
<PreviewField field="title" editable="text" as="h1">
|
|
289
|
+
{preview.data.title}
|
|
290
|
+
</PreviewField>
|
|
291
|
+
|
|
292
|
+
<BlockRenderer
|
|
293
|
+
content={preview.data.content}
|
|
294
|
+
data={preview.data.content?._data}
|
|
295
|
+
renderers={admin.blocks}
|
|
296
|
+
selectedBlockId={preview.selectedBlockId}
|
|
297
|
+
onBlockClick={
|
|
298
|
+
preview.isPreviewMode ? preview.handleBlockClick : undefined
|
|
299
|
+
}
|
|
300
|
+
/>
|
|
301
|
+
</main>
|
|
302
|
+
</PreviewProvider>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Key Principles
|
|
308
|
+
|
|
309
|
+
- Keep `FormView`, the Preview button, and `LivePreviewMode`
|
|
310
|
+
- Never add a separate visual-edit form API, a second default form view, or parallel preview API names
|
|
311
|
+
- Preserve save/autosave/Cmd+S/history/workflow/locks/actions
|
|
312
|
+
- `useCollectionPreview` handles preview mode, mirrored data, refresh/resync, and focus state
|
|
313
|
+
- `PreviewProvider` supplies preview context to `PreviewField`
|
|
314
|
+
- `PreviewField` annotates scalar fields and can opt into inline editing with `editable`
|
|
315
|
+
- `BlockRenderer` preserves block IDs and block scopes for block annotations
|
|
316
|
+
- Use `BlockScopeProvider` only for custom/manual block rendering outside `BlockRenderer`
|
|
317
|
+
- Validate all iframe messages before updating form state
|
|
318
|
+
|
|
319
|
+
## History & Versions
|
|
320
|
+
|
|
321
|
+
Enable `auditModule` for activity timeline. Enable versioning on collections for snapshot restore:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
export const pages = collection("pages")
|
|
325
|
+
.fields(({ f }) => ({ ... }))
|
|
326
|
+
.options({ versioning: true });
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Scope (Multi-Tenancy)
|
|
330
|
+
|
|
331
|
+
The admin provides scope primitives for multi-tenant applications. Import from `@questpie/admin/client`.
|
|
332
|
+
|
|
333
|
+
### ScopeProvider
|
|
334
|
+
|
|
335
|
+
Wraps the admin to enable scope selection. Manages scope ID in React state and persists to localStorage.
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
import { ScopeProvider } from "@questpie/admin/client";
|
|
339
|
+
|
|
340
|
+
<ScopeProvider
|
|
341
|
+
headerName="x-selected-workspace" // HTTP header for scope ID
|
|
342
|
+
storageKey="admin-workspace" // localStorage key
|
|
343
|
+
defaultScope={null} // default value
|
|
344
|
+
>
|
|
345
|
+
<AdminLayout>...</AdminLayout>
|
|
346
|
+
</ScopeProvider>;
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### ScopePicker
|
|
350
|
+
|
|
351
|
+
Dropdown for selecting the current scope. Place in sidebar via `slots.afterBrand`:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { ScopePicker } from "@questpie/admin/client";
|
|
355
|
+
|
|
356
|
+
<AdminLayout
|
|
357
|
+
admin={admin}
|
|
358
|
+
basePath="/admin"
|
|
359
|
+
slots={{
|
|
360
|
+
afterBrand: (
|
|
361
|
+
<div className="px-3 py-2 border-b">
|
|
362
|
+
<ScopePicker
|
|
363
|
+
collection="workspaces"
|
|
364
|
+
labelField="name"
|
|
365
|
+
allowClear
|
|
366
|
+
compact
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
),
|
|
370
|
+
}}
|
|
371
|
+
/>;
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Three data sources: `collection` (queries a collection), `options` (static array), `loadOptions` (async function).
|
|
375
|
+
|
|
376
|
+
### useScopedFetch / createScopedFetch
|
|
377
|
+
|
|
378
|
+
Inject scope header into all API calls:
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
import { useScopedFetch, createScopedFetch } from "@questpie/admin/client";
|
|
382
|
+
|
|
383
|
+
// React hook
|
|
384
|
+
const scopedFetch = useScopedFetch();
|
|
385
|
+
const client = useMemo(
|
|
386
|
+
() => createClient({ baseURL: "/api", fetch: scopedFetch }),
|
|
387
|
+
[scopedFetch],
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Non-React
|
|
391
|
+
const scopedFetch = createScopedFetch("x-selected-workspace", () =>
|
|
392
|
+
getScopeId(),
|
|
393
|
+
);
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### useScope / useScopeSafe
|
|
397
|
+
|
|
398
|
+
Access current scope state in any component:
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import { useScope, useScopeSafe } from "@questpie/admin/client";
|
|
402
|
+
|
|
403
|
+
const { scopeId, setScope, clearScope, headerName } = useScope(); // throws outside ScopeProvider
|
|
404
|
+
const scope = useScopeSafe(); // returns null outside ScopeProvider
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
For the full server-side setup (context resolver, type augmentation, access rules), see the `questpie` skill's `references/multi-tenancy.md`.
|
|
408
|
+
|
|
409
|
+
## Common Mistakes
|
|
410
|
+
|
|
411
|
+
1. **CRITICAL: Using `asChild` prop** — QUESTPIE admin uses `@base-ui/react`, which uses the `render` prop. `asChild` is a Radix pattern and does NOT work here.
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
// WRONG
|
|
415
|
+
<DialogTrigger asChild><Button>Open</Button></DialogTrigger>
|
|
416
|
+
// CORRECT
|
|
417
|
+
<DialogTrigger render={<Button>Open</Button>} />
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
2. **CRITICAL: Importing from `@radix-ui/*`** — use `@base-ui/react` instead.
|
|
421
|
+
|
|
422
|
+
3. **HIGH: Using `@phosphor-icons/react`** — use `@iconify/react` with `ph:` prefix.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
// WRONG
|
|
426
|
+
import { CaretDown } from "@phosphor-icons/react";
|
|
427
|
+
// CORRECT
|
|
428
|
+
import { Icon } from "@iconify/react";
|
|
429
|
+
<Icon icon="ph:caret-down" width={16} height={16} />;
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
4. **HIGH: Using lucide-react icons** — use `@iconify/react` with Phosphor icon set.
|
|
433
|
+
|
|
434
|
+
5. **MEDIUM: Custom `<button>` or `<div>` instead of shadcn components** — use `<Button>`, `<Card>`, etc.
|
|
435
|
+
|
|
436
|
+
6. **MEDIUM: `console.error` for user errors** — use `toast.error()` from `sonner`.
|