@opensaas/stack-ui 0.3.0 → 0.5.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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +138 -0
- package/dist/components/AdminUI.d.ts +4 -4
- package/dist/components/AdminUI.d.ts.map +1 -1
- package/dist/components/Dashboard.d.ts +4 -4
- package/dist/components/Dashboard.d.ts.map +1 -1
- package/dist/components/Dashboard.js +4 -3
- package/dist/components/ItemForm.d.ts +4 -4
- package/dist/components/ItemForm.d.ts.map +1 -1
- package/dist/components/ItemForm.js +9 -5
- package/dist/components/ListView.d.ts +4 -4
- package/dist/components/ListView.d.ts.map +1 -1
- package/dist/components/ListView.js +18 -11
- package/dist/components/Navigation.d.ts +4 -4
- package/dist/components/Navigation.d.ts.map +1 -1
- package/dist/primitives/button.d.ts.map +1 -1
- package/dist/styles/globals.css +1 -1
- package/package.json +27 -27
- package/src/components/AdminUI.tsx +5 -5
- package/src/components/Dashboard.tsx +7 -10
- package/src/components/ItemForm.tsx +14 -10
- package/src/components/ListView.tsx +23 -21
- package/src/components/Navigation.tsx +5 -5
- package/src/primitives/button.tsx +1 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
> @opensaas/stack-ui@0.
|
|
2
|
+
> @opensaas/stack-ui@0.5.0 build /home/runner/work/stack/stack/packages/ui
|
|
3
3
|
> tsc && npm run build:css
|
|
4
4
|
|
|
5
5
|
npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
|
|
6
6
|
npm warn Unknown env config "npm-globalconfig". This will stop working in the next major version of npm.
|
|
7
7
|
npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
|
|
8
8
|
|
|
9
|
-
> @opensaas/stack-ui@0.
|
|
9
|
+
> @opensaas/stack-ui@0.5.0 build:css
|
|
10
10
|
> mkdir -p dist/styles && postcss ./src/styles/globals.css -o ./dist/styles/globals.css
|
|
11
11
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,143 @@
|
|
|
1
1
|
# @opensaas/stack-ui
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
## 0.4.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [#170](https://github.com/OpenSaasAU/stack/pull/170) [`3c4db9d`](https://github.com/OpenSaasAU/stack/commit/3c4db9d8318fc73d291991d8bdfa4f607c3a50ea) Thanks [@list({](https://github.com/list({)! - Add support for virtual fields with proper TypeScript type generation
|
|
10
|
+
|
|
11
|
+
Virtual fields are computed fields that don't exist in the database but are added to query results at runtime. This feature enables derived or computed values to be included in your API responses with full type safety.
|
|
12
|
+
|
|
13
|
+
**New Features:**
|
|
14
|
+
- Added `virtual()` field type for defining computed fields in your schema
|
|
15
|
+
- Virtual fields are automatically excluded from database schema and input types
|
|
16
|
+
- Virtual fields appear in output types with full TypeScript autocomplete
|
|
17
|
+
- Virtual fields support `resolveOutput` hooks for custom computation logic
|
|
18
|
+
|
|
19
|
+
**Type System Improvements:**
|
|
20
|
+
- Generated Context type now properly extends AccessContext from core
|
|
21
|
+
- Separate Input and Output types (e.g., `UserOutput` includes virtual fields, `UserCreateInput` does not)
|
|
22
|
+
- UI components now accept `AccessContext<any>` for better compatibility with custom context types
|
|
23
|
+
- Type aliases provide convenience (e.g., `User = UserOutput`)
|
|
24
|
+
|
|
25
|
+
**Example Usage:**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { list, text, virtual } from '@opensaas/stack-core'
|
|
29
|
+
|
|
30
|
+
export default config({
|
|
31
|
+
lists: {
|
|
32
|
+
|
|
33
|
+
fields: {
|
|
34
|
+
name: text(),
|
|
35
|
+
email: text(),
|
|
36
|
+
displayName: virtual({
|
|
37
|
+
type: 'string',
|
|
38
|
+
hooks: {
|
|
39
|
+
resolveOutput: async ({ item }) => {
|
|
40
|
+
return `${item.name} (${item.email})`
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `displayName` field will automatically appear in query results with full TypeScript support, but won't be part of create/update operations or the database schema.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- [#172](https://github.com/OpenSaasAU/stack/pull/172) [`929a2a9`](https://github.com/OpenSaasAU/stack/commit/929a2a9a2dfa80b1d973d259dd87828d644ea58d) Thanks [@list<Lists.User.TypeInfo>({](https://github.com/list<Lists.User.TypeInfo>({), [@list<Lists.User.TypeInfo>({](https://github.com/list<Lists.User.TypeInfo>({)! - Improve TypeScript type inference for field configs and list-level hooks by automatically passing TypeInfo from list level down
|
|
55
|
+
|
|
56
|
+
This change eliminates the need to manually specify type parameters on field builders when using features like virtual fields, and fixes a critical bug where list-level hooks weren't receiving properly typed parameters.
|
|
57
|
+
|
|
58
|
+
## Field Type Inference Improvements
|
|
59
|
+
|
|
60
|
+
Previously, users had to write `virtual<Lists.User.TypeInfo>({...})` to get proper type inference. Now TypeScript automatically infers the correct types from the list-level type parameter.
|
|
61
|
+
|
|
62
|
+
**Example:**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
|
|
67
|
+
fields: {
|
|
68
|
+
displayName: virtual<Lists.User.TypeInfo>({
|
|
69
|
+
type: 'string',
|
|
70
|
+
hooks: {
|
|
71
|
+
resolveOutput: ({ item }) => `${item.name} (${item.email})`,
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// After
|
|
78
|
+
|
|
79
|
+
fields: {
|
|
80
|
+
displayName: virtual({
|
|
81
|
+
type: 'string',
|
|
82
|
+
hooks: {
|
|
83
|
+
resolveOutput: ({ item }) => `${item.name} (${item.email})`,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## List-Level Hooks Type Inference Fix
|
|
91
|
+
|
|
92
|
+
Fixed a critical type parameter mismatch where `Hooks<TTypeInfo>` was passing the entire TypeInfo object as the first parameter instead of properly destructuring it into three required parameters:
|
|
93
|
+
1. `TOutput` - The item type (what's stored in DB)
|
|
94
|
+
2. `TCreateInput` - Prisma create input type
|
|
95
|
+
3. `TUpdateInput` - Prisma update input type
|
|
96
|
+
|
|
97
|
+
**Impact:**
|
|
98
|
+
- `resolveInput` now receives proper Prisma input types (e.g., `PostCreateInput`, `PostUpdateInput`)
|
|
99
|
+
- `validateInput` has access to properly typed input data
|
|
100
|
+
- `beforeOperation` and `afterOperation` have correct item types
|
|
101
|
+
- All list-level hook callbacks now get full IntelliSense and type checking
|
|
102
|
+
|
|
103
|
+
**Example:**
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
Post: list<Lists.Post.TypeInfo>({
|
|
107
|
+
fields: { title: text(), content: text() },
|
|
108
|
+
hooks: {
|
|
109
|
+
resolveInput: async ({ operation, resolvedData }) => {
|
|
110
|
+
// ✅ resolvedData is now properly typed as PostCreateInput or PostUpdateInput
|
|
111
|
+
// ✅ Full autocomplete for title, content, etc.
|
|
112
|
+
if (operation === 'create') {
|
|
113
|
+
console.log(resolvedData.title) // TypeScript knows this is string | undefined
|
|
114
|
+
}
|
|
115
|
+
return resolvedData
|
|
116
|
+
},
|
|
117
|
+
beforeOperation: async ({ operation, item }) => {
|
|
118
|
+
// ✅ item is now properly typed as Post with all fields
|
|
119
|
+
if (operation === 'update' && item) {
|
|
120
|
+
console.log(item.title) // TypeScript knows this is string
|
|
121
|
+
console.log(item.createdAt) // TypeScript knows this is Date
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Breaking Changes
|
|
129
|
+
- Field types now accept full `TTypeInfo extends TypeInfo` instead of just `TItem`
|
|
130
|
+
- `FieldsWithItemType` utility replaced with `FieldsWithTypeInfo`
|
|
131
|
+
- All field builders updated to use new type signature
|
|
132
|
+
- List-level hooks now receive properly typed parameters (may reveal existing type errors)
|
|
133
|
+
|
|
134
|
+
## Benefits
|
|
135
|
+
- ✨ Cleaner code without manual type parameter repetition
|
|
136
|
+
- 🎯 Better type inference in both field-level and list-level hooks
|
|
137
|
+
- 🔄 Consistent type flow from list configuration down to individual fields
|
|
138
|
+
- 🛡️ Maintained full type safety with improved DX
|
|
139
|
+
- 💡 Full IntelliSense support in all hook callbacks
|
|
140
|
+
|
|
3
141
|
## 0.3.0
|
|
4
142
|
|
|
5
143
|
## 0.2.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ServerActionInput } from '../server/types.js';
|
|
2
|
-
import { AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
3
|
-
export interface AdminUIProps
|
|
4
|
-
context: AccessContext<
|
|
2
|
+
import { type AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
3
|
+
export interface AdminUIProps {
|
|
4
|
+
context: AccessContext<unknown>;
|
|
5
5
|
config: OpenSaasConfig;
|
|
6
6
|
params?: string[];
|
|
7
7
|
searchParams?: {
|
|
@@ -21,5 +21,5 @@ export interface AdminUIProps<TPrisma> {
|
|
|
21
21
|
* - [list, 'create'] → ItemForm (create)
|
|
22
22
|
* - [list, id] → ItemForm (edit)
|
|
23
23
|
*/
|
|
24
|
-
export declare function AdminUI
|
|
24
|
+
export declare function AdminUI({ context, config, params, searchParams, basePath, serverAction, onSignOut, }: AdminUIProps): import("react/jsx-runtime").JSX.Element;
|
|
25
25
|
//# sourceMappingURL=AdminUI.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUI.d.ts","sourceRoot":"","sources":["../../src/components/AdminUI.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAqB,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"AdminUI.d.ts","sourceRoot":"","sources":["../../src/components/AdminUI.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAqB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG5F,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,YAAY,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5D,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,MAAM,EACN,MAAW,EACX,YAAiB,EACjB,QAAmB,EACnB,YAAY,EACZ,SAAS,GACV,EAAE,YAAY,2CA4Ed"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
2
|
-
export interface DashboardProps
|
|
3
|
-
context: AccessContext<
|
|
1
|
+
import { type AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
2
|
+
export interface DashboardProps {
|
|
3
|
+
context: AccessContext<unknown>;
|
|
4
4
|
config: OpenSaasConfig;
|
|
5
5
|
basePath?: string;
|
|
6
6
|
}
|
|
@@ -8,5 +8,5 @@ export interface DashboardProps<TPrisma> {
|
|
|
8
8
|
* Dashboard landing page showing all available lists
|
|
9
9
|
* Server Component
|
|
10
10
|
*/
|
|
11
|
-
export declare function Dashboard
|
|
11
|
+
export declare function Dashboard({ context, config, basePath }: DashboardProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
12
12
|
//# sourceMappingURL=Dashboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dashboard.d.ts","sourceRoot":"","sources":["../../src/components/Dashboard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAuB,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"Dashboard.d.ts","sourceRoot":"","sources":["../../src/components/Dashboard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAuB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG9F,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAmB,EAAE,EAAE,cAAc,oDAmHvF"}
|
|
@@ -7,13 +7,14 @@ import { Card, CardContent, CardHeader, CardTitle } from '../primitives/card.js'
|
|
|
7
7
|
* Dashboard landing page showing all available lists
|
|
8
8
|
* Server Component
|
|
9
9
|
*/
|
|
10
|
-
export async function Dashboard({ context, config, basePath = '/admin'
|
|
10
|
+
export async function Dashboard({ context, config, basePath = '/admin' }) {
|
|
11
11
|
const lists = Object.keys(config.lists || {});
|
|
12
12
|
// Get counts for each list
|
|
13
13
|
const listCounts = await Promise.all(lists.map(async (listKey) => {
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const delegate = context.db[getDbKey(listKey)];
|
|
16
|
+
const count = delegate?.count ? await delegate.count() : 0;
|
|
17
|
+
return { listKey, count };
|
|
17
18
|
}
|
|
18
19
|
catch (error) {
|
|
19
20
|
console.error(`Failed to get count for ${listKey}:`, error);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ServerActionInput } from '../server/types.js';
|
|
2
|
-
import { AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
3
|
-
export interface ItemFormProps
|
|
4
|
-
context: AccessContext<
|
|
2
|
+
import { type AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
3
|
+
export interface ItemFormProps {
|
|
4
|
+
context: AccessContext<unknown>;
|
|
5
5
|
config: OpenSaasConfig;
|
|
6
6
|
listKey: string;
|
|
7
7
|
mode: 'create' | 'edit';
|
|
@@ -13,5 +13,5 @@ export interface ItemFormProps<TPrisma> {
|
|
|
13
13
|
* Item form component - create or edit an item
|
|
14
14
|
* Server Component that fetches data and sets up actions
|
|
15
15
|
*/
|
|
16
|
-
export declare function ItemForm
|
|
16
|
+
export declare function ItemForm({ context, config, listKey, mode, itemId, basePath, serverAction, }: ItemFormProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
17
17
|
//# sourceMappingURL=ItemForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ItemForm.d.ts","sourceRoot":"","sources":["../../src/components/ItemForm.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAuB,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"ItemForm.d.ts","sourceRoot":"","sources":["../../src/components/ItemForm.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAuB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG9F,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,YAAY,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC7D;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,OAAO,EACP,IAAI,EACJ,MAAM,EACN,QAAmB,EACnB,YAAY,GACb,EAAE,aAAa,oDAuKf"}
|
|
@@ -27,10 +27,13 @@ export async function ItemForm({ context, config, listKey, mode, itemId, basePat
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
// Fetch item with relationships included
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const delegate = context.db[getDbKey(listKey)];
|
|
31
|
+
if (delegate?.findUnique) {
|
|
32
|
+
itemData = await delegate.findUnique({
|
|
33
|
+
where: { id: itemId },
|
|
34
|
+
...(Object.keys(includeRelationships).length > 0 && { include: includeRelationships }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
catch (error) {
|
|
36
39
|
console.error(`Failed to fetch item ${itemId}:`, error);
|
|
@@ -53,7 +56,8 @@ export async function ItemForm({ context, config, listKey, mode, itemId, basePat
|
|
|
53
56
|
if (relatedListConfig) {
|
|
54
57
|
try {
|
|
55
58
|
const dbContext = context.db;
|
|
56
|
-
const
|
|
59
|
+
const delegate = dbContext[getDbKey(relatedListName)];
|
|
60
|
+
const relatedItems = delegate?.findMany ? await delegate.findMany({}) : [];
|
|
57
61
|
// Use 'name' field as label if it exists, otherwise use 'id'
|
|
58
62
|
relationshipData[fieldName] = relatedItems.map((item) => ({
|
|
59
63
|
id: item.id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AccessContext, OpenSaasConfig
|
|
2
|
-
export interface ListViewProps
|
|
3
|
-
context: AccessContext<
|
|
1
|
+
import { type AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
2
|
+
export interface ListViewProps {
|
|
3
|
+
context: AccessContext<unknown>;
|
|
4
4
|
config: OpenSaasConfig;
|
|
5
5
|
listKey: string;
|
|
6
6
|
basePath?: string;
|
|
@@ -13,5 +13,5 @@ export interface ListViewProps<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
13
13
|
* List view component - displays items in a table
|
|
14
14
|
* Server Component that fetches data and renders client table
|
|
15
15
|
*/
|
|
16
|
-
export declare function ListView
|
|
16
|
+
export declare function ListView({ context, config, listKey, basePath, columns, page, pageSize, search, }: ListViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
17
17
|
//# sourceMappingURL=ListView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListView.d.ts","sourceRoot":"","sources":["../../src/components/ListView.tsx"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"ListView.d.ts","sourceRoot":"","sources":["../../src/components/ListView.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAuB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE9F,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,EAC7B,OAAO,EACP,MAAM,EACN,OAAO,EACP,QAAmB,EACnB,OAAO,EACP,IAAQ,EACR,QAAa,EACb,MAAM,GACP,EAAE,aAAa,oDA6Hf"}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import Link from 'next/link.js';
|
|
3
3
|
import { ListViewClient } from './ListViewClient.js';
|
|
4
4
|
import { formatListName } from '../lib/utils.js';
|
|
5
|
-
import { getDbKey, getUrlKey
|
|
5
|
+
import { getDbKey, getUrlKey } from '@opensaas/stack-core';
|
|
6
6
|
/**
|
|
7
7
|
* List view component - displays items in a table
|
|
8
8
|
* Server Component that fetches data and renders client table
|
|
@@ -47,15 +47,19 @@ export async function ListView({ context, config, listKey, basePath = '/admin',
|
|
|
47
47
|
include[fieldName] = true;
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
const delegate = dbContext[key];
|
|
51
|
+
if (delegate?.findMany && delegate?.count) {
|
|
52
|
+
;
|
|
53
|
+
[items, total] = await Promise.all([
|
|
54
|
+
delegate.findMany({
|
|
55
|
+
where,
|
|
56
|
+
skip,
|
|
57
|
+
take: pageSize,
|
|
58
|
+
...(Object.keys(include).length > 0 ? { include } : {}),
|
|
59
|
+
}),
|
|
60
|
+
delegate.count({ where }),
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
catch (error) {
|
|
61
65
|
console.error(`Failed to fetch ${listKey}:`, error);
|
|
@@ -65,7 +69,10 @@ export async function ListView({ context, config, listKey, basePath = '/admin',
|
|
|
65
69
|
// Extract only the relationship refs needed by client (don't send entire config)
|
|
66
70
|
const relationshipRefs = {};
|
|
67
71
|
Object.entries(listConfig.fields).forEach(([fieldName, field]) => {
|
|
68
|
-
if ('type' in field &&
|
|
72
|
+
if ('type' in field &&
|
|
73
|
+
field.type === 'relationship' &&
|
|
74
|
+
'ref' in field &&
|
|
75
|
+
typeof field.ref === 'string') {
|
|
69
76
|
relationshipRefs[fieldName] = field.ref;
|
|
70
77
|
}
|
|
71
78
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
2
|
-
export interface NavigationProps
|
|
3
|
-
context: AccessContext<
|
|
1
|
+
import { type AccessContext, OpenSaasConfig } from '@opensaas/stack-core';
|
|
2
|
+
export interface NavigationProps {
|
|
3
|
+
context: AccessContext<unknown>;
|
|
4
4
|
config: OpenSaasConfig;
|
|
5
5
|
basePath?: string;
|
|
6
6
|
currentPath?: string;
|
|
@@ -10,5 +10,5 @@ export interface NavigationProps<TPrisma> {
|
|
|
10
10
|
* Navigation sidebar showing all lists
|
|
11
11
|
* Server Component
|
|
12
12
|
*/
|
|
13
|
-
export declare function Navigation
|
|
13
|
+
export declare function Navigation({ context, config, basePath, currentPath, onSignOut, }: NavigationProps): import("react/jsx-runtime").JSX.Element;
|
|
14
14
|
//# sourceMappingURL=Navigation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navigation.d.ts","sourceRoot":"","sources":["../../src/components/Navigation.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAa,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"Navigation.d.ts","sourceRoot":"","sources":["../../src/components/Navigation.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAa,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGpF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,MAAM,EACN,QAAmB,EACnB,WAAgB,EAChB,SAAS,GACV,EAAE,eAAe,2CAkFjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/primitives/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;8EAwBnB,CAAA;AAED,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/primitives/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;8EAwBnB,CAAA;AAED,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,OAAO,cAAc,CAAC;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,QAAA,MAAM,MAAM,uFAOX,CAAA;AAGD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/styles/globals.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Composable React UI components for OpenSaas Stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,41 +60,41 @@
|
|
|
60
60
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
61
61
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
62
62
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
63
|
-
"@radix-ui/react-label": "^2.1.
|
|
63
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
64
64
|
"@radix-ui/react-popover": "^1.1.15",
|
|
65
65
|
"@radix-ui/react-select": "^2.2.6",
|
|
66
|
-
"@radix-ui/react-separator": "^1.1.
|
|
67
|
-
"@radix-ui/react-slot": "^1.2.
|
|
66
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
67
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
68
68
|
"class-variance-authority": "^0.7.1",
|
|
69
69
|
"clsx": "^2.1.1",
|
|
70
70
|
"date-fns": "^4.1.0",
|
|
71
|
-
"lucide-react": "^0.
|
|
72
|
-
"react-hook-form": "^7.
|
|
73
|
-
"tailwind-merge": "^3.
|
|
71
|
+
"lucide-react": "^0.561.0",
|
|
72
|
+
"react-hook-form": "^7.68.0",
|
|
73
|
+
"tailwind-merge": "^3.4.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@tailwindcss/postcss": "^4.
|
|
76
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
77
77
|
"@testing-library/jest-dom": "^6.9.1",
|
|
78
|
-
"@testing-library/react": "^16.
|
|
79
|
-
"@testing-library/user-event": "^14.
|
|
80
|
-
"@types/node": "^24.
|
|
81
|
-
"@types/react": "^19.2.
|
|
82
|
-
"@types/react-dom": "^19.2.
|
|
83
|
-
"@vitejs/plugin-react": "^5.
|
|
84
|
-
"@vitest/browser": "^4.0.
|
|
85
|
-
"@vitest/browser-playwright": "^4.0.
|
|
86
|
-
"@vitest/coverage-v8": "^4.0.
|
|
87
|
-
"happy-dom": "^20.0.
|
|
88
|
-
"next": "^16.0.
|
|
89
|
-
"playwright": "^1.
|
|
90
|
-
"postcss": "^8.
|
|
91
|
-
"postcss-cli": "^11.0.
|
|
92
|
-
"react": "^19.2.
|
|
93
|
-
"react-dom": "^19.2.
|
|
94
|
-
"tailwindcss": "^4.
|
|
78
|
+
"@testing-library/react": "^16.3.0",
|
|
79
|
+
"@testing-library/user-event": "^14.6.1",
|
|
80
|
+
"@types/node": "^24.10.1",
|
|
81
|
+
"@types/react": "^19.2.7",
|
|
82
|
+
"@types/react-dom": "^19.2.3",
|
|
83
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
84
|
+
"@vitest/browser": "^4.0.15",
|
|
85
|
+
"@vitest/browser-playwright": "^4.0.15",
|
|
86
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
87
|
+
"happy-dom": "^20.0.11",
|
|
88
|
+
"next": "^16.0.7",
|
|
89
|
+
"playwright": "^1.57.0",
|
|
90
|
+
"postcss": "^8.5.6",
|
|
91
|
+
"postcss-cli": "^11.0.1",
|
|
92
|
+
"react": "^19.2.1",
|
|
93
|
+
"react-dom": "^19.2.1",
|
|
94
|
+
"tailwindcss": "^4.1.17",
|
|
95
95
|
"typescript": "^5.9.3",
|
|
96
|
-
"vitest": "^4.0.
|
|
97
|
-
"@opensaas/stack-core": "0.
|
|
96
|
+
"vitest": "^4.0.15",
|
|
97
|
+
"@opensaas/stack-core": "0.5.0"
|
|
98
98
|
},
|
|
99
99
|
"scripts": {
|
|
100
100
|
"build": "tsc && npm run build:css",
|
|
@@ -4,11 +4,11 @@ import { Dashboard } from './Dashboard.js'
|
|
|
4
4
|
import { ListView } from './ListView.js'
|
|
5
5
|
import { ItemForm } from './ItemForm.js'
|
|
6
6
|
import type { ServerActionInput } from '../server/types.js'
|
|
7
|
-
import { AccessContext, getListKeyFromUrl, OpenSaasConfig } from '@opensaas/stack-core'
|
|
7
|
+
import { type AccessContext, getListKeyFromUrl, OpenSaasConfig } from '@opensaas/stack-core'
|
|
8
8
|
import { generateThemeCSS } from '../lib/theme.js'
|
|
9
9
|
|
|
10
|
-
export interface AdminUIProps
|
|
11
|
-
context: AccessContext<
|
|
10
|
+
export interface AdminUIProps {
|
|
11
|
+
context: AccessContext<unknown>
|
|
12
12
|
config: OpenSaasConfig
|
|
13
13
|
params?: string[]
|
|
14
14
|
searchParams?: { [key: string]: string | string[] | undefined }
|
|
@@ -28,7 +28,7 @@ export interface AdminUIProps<TPrisma> {
|
|
|
28
28
|
* - [list, 'create'] → ItemForm (create)
|
|
29
29
|
* - [list, id] → ItemForm (edit)
|
|
30
30
|
*/
|
|
31
|
-
export function AdminUI
|
|
31
|
+
export function AdminUI({
|
|
32
32
|
context,
|
|
33
33
|
config,
|
|
34
34
|
params = [],
|
|
@@ -36,7 +36,7 @@ export function AdminUI<TPrisma>({
|
|
|
36
36
|
basePath = '/admin',
|
|
37
37
|
serverAction,
|
|
38
38
|
onSignOut,
|
|
39
|
-
}: AdminUIProps
|
|
39
|
+
}: AdminUIProps) {
|
|
40
40
|
// Parse route from params
|
|
41
41
|
const [urlSegment, action] = params
|
|
42
42
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Link from 'next/link.js'
|
|
2
2
|
import { formatListName } from '../lib/utils.js'
|
|
3
|
-
import { AccessContext, getDbKey, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
3
|
+
import { type AccessContext, getDbKey, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
4
|
import { Card, CardContent, CardHeader, CardTitle } from '../primitives/card.js'
|
|
5
5
|
|
|
6
|
-
export interface DashboardProps
|
|
7
|
-
context: AccessContext<
|
|
6
|
+
export interface DashboardProps {
|
|
7
|
+
context: AccessContext<unknown>
|
|
8
8
|
config: OpenSaasConfig
|
|
9
9
|
basePath?: string
|
|
10
10
|
}
|
|
@@ -13,19 +13,16 @@ export interface DashboardProps<TPrisma> {
|
|
|
13
13
|
* Dashboard landing page showing all available lists
|
|
14
14
|
* Server Component
|
|
15
15
|
*/
|
|
16
|
-
export async function Dashboard
|
|
17
|
-
context,
|
|
18
|
-
config,
|
|
19
|
-
basePath = '/admin',
|
|
20
|
-
}: DashboardProps<TPrisma>) {
|
|
16
|
+
export async function Dashboard({ context, config, basePath = '/admin' }: DashboardProps) {
|
|
21
17
|
const lists = Object.keys(config.lists || {})
|
|
22
18
|
|
|
23
19
|
// Get counts for each list
|
|
24
20
|
const listCounts = await Promise.all(
|
|
25
21
|
lists.map(async (listKey) => {
|
|
26
22
|
try {
|
|
27
|
-
const
|
|
28
|
-
|
|
23
|
+
const delegate = context.db[getDbKey(listKey)]
|
|
24
|
+
const count = delegate?.count ? await delegate.count() : 0
|
|
25
|
+
return { listKey, count }
|
|
29
26
|
} catch (error) {
|
|
30
27
|
console.error(`Failed to get count for ${listKey}:`, error)
|
|
31
28
|
return { listKey, count: 0 }
|
|
@@ -3,11 +3,11 @@ import Link from 'next/link.js'
|
|
|
3
3
|
import { ItemFormClient } from './ItemFormClient.js'
|
|
4
4
|
import { formatListName } from '../lib/utils.js'
|
|
5
5
|
import type { ServerActionInput } from '../server/types.js'
|
|
6
|
-
import { AccessContext, getDbKey, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
6
|
+
import { type AccessContext, getDbKey, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
7
7
|
import { serializeFieldConfigs } from '../lib/serializeFieldConfig.js'
|
|
8
8
|
|
|
9
|
-
export interface ItemFormProps
|
|
10
|
-
context: AccessContext<
|
|
9
|
+
export interface ItemFormProps {
|
|
10
|
+
context: AccessContext<unknown>
|
|
11
11
|
config: OpenSaasConfig
|
|
12
12
|
listKey: string
|
|
13
13
|
mode: 'create' | 'edit'
|
|
@@ -21,7 +21,7 @@ export interface ItemFormProps<TPrisma> {
|
|
|
21
21
|
* Item form component - create or edit an item
|
|
22
22
|
* Server Component that fetches data and sets up actions
|
|
23
23
|
*/
|
|
24
|
-
export async function ItemForm
|
|
24
|
+
export async function ItemForm({
|
|
25
25
|
context,
|
|
26
26
|
config,
|
|
27
27
|
listKey,
|
|
@@ -29,7 +29,7 @@ export async function ItemForm<TPrisma>({
|
|
|
29
29
|
itemId,
|
|
30
30
|
basePath = '/admin',
|
|
31
31
|
serverAction,
|
|
32
|
-
}: ItemFormProps
|
|
32
|
+
}: ItemFormProps) {
|
|
33
33
|
const listConfig = config.lists[listKey]
|
|
34
34
|
const urlKey = getUrlKey(listKey)
|
|
35
35
|
|
|
@@ -58,10 +58,13 @@ export async function ItemForm<TPrisma>({
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Fetch item with relationships included
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const delegate = context.db[getDbKey(listKey)]
|
|
62
|
+
if (delegate?.findUnique) {
|
|
63
|
+
itemData = await delegate.findUnique({
|
|
64
|
+
where: { id: itemId },
|
|
65
|
+
...(Object.keys(includeRelationships).length > 0 && { include: includeRelationships }),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
65
68
|
} catch (error) {
|
|
66
69
|
console.error(`Failed to fetch item ${itemId}:`, error)
|
|
67
70
|
}
|
|
@@ -102,7 +105,8 @@ export async function ItemForm<TPrisma>({
|
|
|
102
105
|
if (relatedListConfig) {
|
|
103
106
|
try {
|
|
104
107
|
const dbContext = context.db
|
|
105
|
-
const
|
|
108
|
+
const delegate = dbContext[getDbKey(relatedListName)]
|
|
109
|
+
const relatedItems = delegate?.findMany ? await delegate.findMany({}) : []
|
|
106
110
|
|
|
107
111
|
// Use 'name' field as label if it exists, otherwise use 'id'
|
|
108
112
|
relationshipData[fieldName] = relatedItems.map((item: Record<string, unknown>) => ({
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import Link from 'next/link.js'
|
|
2
2
|
import { ListViewClient } from './ListViewClient.js'
|
|
3
3
|
import { formatListName } from '../lib/utils.js'
|
|
4
|
-
import {
|
|
5
|
-
AccessContext,
|
|
6
|
-
getDbKey,
|
|
7
|
-
getUrlKey,
|
|
8
|
-
OpenSaasConfig,
|
|
9
|
-
type PrismaClientLike,
|
|
10
|
-
} from '@opensaas/stack-core'
|
|
4
|
+
import { type AccessContext, getDbKey, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
11
5
|
|
|
12
|
-
export interface ListViewProps
|
|
13
|
-
context: AccessContext<
|
|
6
|
+
export interface ListViewProps {
|
|
7
|
+
context: AccessContext<unknown>
|
|
14
8
|
config: OpenSaasConfig
|
|
15
9
|
listKey: string
|
|
16
10
|
basePath?: string
|
|
@@ -24,7 +18,7 @@ export interface ListViewProps<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
24
18
|
* List view component - displays items in a table
|
|
25
19
|
* Server Component that fetches data and renders client table
|
|
26
20
|
*/
|
|
27
|
-
export async function ListView
|
|
21
|
+
export async function ListView({
|
|
28
22
|
context,
|
|
29
23
|
config,
|
|
30
24
|
listKey,
|
|
@@ -33,7 +27,7 @@ export async function ListView<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
33
27
|
page = 1,
|
|
34
28
|
pageSize = 50,
|
|
35
29
|
search,
|
|
36
|
-
}: ListViewProps
|
|
30
|
+
}: ListViewProps) {
|
|
37
31
|
const key = getDbKey(listKey)
|
|
38
32
|
const urlKey = getUrlKey(listKey)
|
|
39
33
|
const listConfig = config.lists[listKey]
|
|
@@ -86,15 +80,18 @@ export async function ListView<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
86
80
|
include[fieldName] = true
|
|
87
81
|
}
|
|
88
82
|
})
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
const delegate = dbContext[key]
|
|
84
|
+
if (delegate?.findMany && delegate?.count) {
|
|
85
|
+
;[items, total] = await Promise.all([
|
|
86
|
+
delegate.findMany({
|
|
87
|
+
where,
|
|
88
|
+
skip,
|
|
89
|
+
take: pageSize,
|
|
90
|
+
...(Object.keys(include).length > 0 ? { include } : {}),
|
|
91
|
+
}),
|
|
92
|
+
delegate.count({ where }),
|
|
93
|
+
])
|
|
94
|
+
}
|
|
98
95
|
} catch (error) {
|
|
99
96
|
console.error(`Failed to fetch ${listKey}:`, error)
|
|
100
97
|
}
|
|
@@ -105,7 +102,12 @@ export async function ListView<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
105
102
|
// Extract only the relationship refs needed by client (don't send entire config)
|
|
106
103
|
const relationshipRefs: Record<string, string> = {}
|
|
107
104
|
Object.entries(listConfig.fields).forEach(([fieldName, field]) => {
|
|
108
|
-
if (
|
|
105
|
+
if (
|
|
106
|
+
'type' in field &&
|
|
107
|
+
field.type === 'relationship' &&
|
|
108
|
+
'ref' in field &&
|
|
109
|
+
typeof field.ref === 'string'
|
|
110
|
+
) {
|
|
109
111
|
relationshipRefs[fieldName] = field.ref
|
|
110
112
|
}
|
|
111
113
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Link from 'next/link.js'
|
|
2
2
|
import { formatListName } from '../lib/utils.js'
|
|
3
|
-
import { AccessContext, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
3
|
+
import { type AccessContext, getUrlKey, OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
4
|
import { UserMenu } from './UserMenu.js'
|
|
5
5
|
|
|
6
|
-
export interface NavigationProps
|
|
7
|
-
context: AccessContext<
|
|
6
|
+
export interface NavigationProps {
|
|
7
|
+
context: AccessContext<unknown>
|
|
8
8
|
config: OpenSaasConfig
|
|
9
9
|
basePath?: string
|
|
10
10
|
currentPath?: string
|
|
@@ -15,13 +15,13 @@ export interface NavigationProps<TPrisma> {
|
|
|
15
15
|
* Navigation sidebar showing all lists
|
|
16
16
|
* Server Component
|
|
17
17
|
*/
|
|
18
|
-
export function Navigation
|
|
18
|
+
export function Navigation({
|
|
19
19
|
context,
|
|
20
20
|
config,
|
|
21
21
|
basePath = '/admin',
|
|
22
22
|
currentPath = '',
|
|
23
23
|
onSignOut,
|
|
24
|
-
}: NavigationProps
|
|
24
|
+
}: NavigationProps) {
|
|
25
25
|
const lists = Object.keys(config.lists || {})
|
|
26
26
|
|
|
27
27
|
return (
|
|
@@ -31,8 +31,7 @@ const buttonVariants = cva(
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
export interface ButtonProps
|
|
34
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
-
VariantProps<typeof buttonVariants> {
|
|
34
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
36
35
|
asChild?: boolean
|
|
37
36
|
}
|
|
38
37
|
|