@setzkasten-cms/ui 0.4.2
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/LICENSE +37 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +2936 -0
- package/package.json +41 -0
- package/src/adapters/proxy-asset-store.ts +210 -0
- package/src/adapters/proxy-content-repository.ts +259 -0
- package/src/components/admin-app.tsx +275 -0
- package/src/components/collection-view.tsx +103 -0
- package/src/components/entry-form.tsx +76 -0
- package/src/components/entry-list.tsx +119 -0
- package/src/components/page-builder.tsx +1134 -0
- package/src/components/toast.tsx +48 -0
- package/src/fields/array-field-renderer.tsx +101 -0
- package/src/fields/boolean-field-renderer.tsx +28 -0
- package/src/fields/field-renderer.tsx +60 -0
- package/src/fields/icon-field-renderer.tsx +130 -0
- package/src/fields/image-field-renderer.tsx +266 -0
- package/src/fields/number-field-renderer.tsx +38 -0
- package/src/fields/object-field-renderer.tsx +41 -0
- package/src/fields/override-field-renderer.tsx +48 -0
- package/src/fields/select-field-renderer.tsx +42 -0
- package/src/fields/text-field-renderer.tsx +313 -0
- package/src/hooks/use-field.ts +82 -0
- package/src/hooks/use-save.ts +46 -0
- package/src/index.ts +34 -0
- package/src/providers/setzkasten-provider.tsx +80 -0
- package/src/stores/app-store.ts +61 -0
- package/src/stores/form-store.test.ts +111 -0
- package/src/stores/form-store.ts +298 -0
- package/src/styles/admin.css +2017 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Setzkasten Community License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lilapixel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to use,
|
|
7
|
+
copy, modify, merge, publish, and distribute the Software, subject to the
|
|
8
|
+
following conditions:
|
|
9
|
+
|
|
10
|
+
1. The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
2. The Software may not be used for commercial purposes without a separate
|
|
14
|
+
commercial license from the copyright holder. "Commercial purposes" means
|
|
15
|
+
any use of the Software that is primarily intended for or directed toward
|
|
16
|
+
commercial advantage or monetary compensation. This includes, but is not
|
|
17
|
+
limited to:
|
|
18
|
+
- Using the Software to manage content for a commercial website or product
|
|
19
|
+
- Offering the Software as part of a paid service
|
|
20
|
+
- Using the Software within a for-profit organization
|
|
21
|
+
|
|
22
|
+
3. Non-commercial use is permitted without restriction. This includes:
|
|
23
|
+
- Personal projects
|
|
24
|
+
- Open source projects
|
|
25
|
+
- Educational and academic use
|
|
26
|
+
- Non-profit organizations
|
|
27
|
+
|
|
28
|
+
4. A commercial license ("Enterprise License") may be obtained by contacting
|
|
29
|
+
Lilapixel at hello@lilapixel.de.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode, MutableRefObject } from 'react';
|
|
4
|
+
import { SetzKastenConfig, ContentRepository, AuthProvider, AssetStore, ContentEventBus, FieldRecord, FieldPath, AnyFieldDef, CollectionDefinition, Result, CommitResult, EntryListItem, EntryData, Asset, TreeNode, AssetMetadata } from '@setzkasten-cms/core';
|
|
5
|
+
import * as zustand from 'zustand';
|
|
6
|
+
|
|
7
|
+
interface SetzKastenContext {
|
|
8
|
+
config: SetzKastenConfig;
|
|
9
|
+
repository: ContentRepository;
|
|
10
|
+
auth: AuthProvider;
|
|
11
|
+
assets: AssetStore;
|
|
12
|
+
eventBus: ContentEventBus;
|
|
13
|
+
}
|
|
14
|
+
interface SetzKastenProviderProps {
|
|
15
|
+
config: SetzKastenConfig;
|
|
16
|
+
repository: ContentRepository;
|
|
17
|
+
auth: AuthProvider;
|
|
18
|
+
assets: AssetStore;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Root provider – the composition root injects all concrete adapters here.
|
|
23
|
+
* UI components access ports only via this context (Dependency Inversion).
|
|
24
|
+
*/
|
|
25
|
+
declare function SetzKastenProvider({ config, repository, auth, assets, children, }: SetzKastenProviderProps): react_jsx_runtime.JSX.Element;
|
|
26
|
+
declare function useSetzKasten(): SetzKastenContext;
|
|
27
|
+
declare function useRepository(): ContentRepository;
|
|
28
|
+
declare function useAuth(): AuthProvider;
|
|
29
|
+
declare function useAssets(): AssetStore;
|
|
30
|
+
declare function useEventBus(): ContentEventBus;
|
|
31
|
+
declare function useConfig(): SetzKastenConfig;
|
|
32
|
+
|
|
33
|
+
interface FormState {
|
|
34
|
+
/** The schema this form is rendering */
|
|
35
|
+
schema: FieldRecord | null;
|
|
36
|
+
/** Current form values (Immer-managed) */
|
|
37
|
+
values: Record<string, unknown>;
|
|
38
|
+
/** Values at load time (for dirty checking) */
|
|
39
|
+
initialValues: Record<string, unknown>;
|
|
40
|
+
/** Validation errors keyed by dot-path */
|
|
41
|
+
errors: Record<string, string[]>;
|
|
42
|
+
/** Field paths the user has interacted with */
|
|
43
|
+
touched: Set<string>;
|
|
44
|
+
/** Form lifecycle status */
|
|
45
|
+
status: 'idle' | 'loading' | 'saving' | 'error';
|
|
46
|
+
/** Error message when status is 'error' */
|
|
47
|
+
errorMessage?: string;
|
|
48
|
+
}
|
|
49
|
+
interface FormActions {
|
|
50
|
+
/** Initialize form with schema and values. If draftValues is provided, values starts as draft but initialValues stays as saved. */
|
|
51
|
+
init(schema: FieldRecord, values: Record<string, unknown>, draftValues?: Record<string, unknown>): void;
|
|
52
|
+
/** Set a field value by path */
|
|
53
|
+
setFieldValue(path: FieldPath, value: unknown): void;
|
|
54
|
+
/** Get a field value by path */
|
|
55
|
+
getFieldValue(path: FieldPath): unknown;
|
|
56
|
+
/** Mark a field as touched */
|
|
57
|
+
touchField(path: FieldPath): void;
|
|
58
|
+
/** Check if a specific field is dirty */
|
|
59
|
+
isFieldDirty(path: FieldPath): boolean;
|
|
60
|
+
/** Check if any field is dirty */
|
|
61
|
+
isDirty(): boolean;
|
|
62
|
+
/** Set validation errors for a field */
|
|
63
|
+
setFieldErrors(path: FieldPath, errors: string[]): void;
|
|
64
|
+
/** Clear all validation errors */
|
|
65
|
+
clearErrors(): void;
|
|
66
|
+
/** Set form status */
|
|
67
|
+
setStatus(status: FormState['status'], errorMessage?: string): void;
|
|
68
|
+
/** Reset form to initial values */
|
|
69
|
+
reset(): void;
|
|
70
|
+
/** Undo last change */
|
|
71
|
+
undo(): void;
|
|
72
|
+
/** Redo last undone change */
|
|
73
|
+
redo(): void;
|
|
74
|
+
/** Whether undo is available */
|
|
75
|
+
canUndo(): boolean;
|
|
76
|
+
/** Whether redo is available */
|
|
77
|
+
canRedo(): boolean;
|
|
78
|
+
/** Reset to initial values and clear history (Verwerfen) */
|
|
79
|
+
resetToInitial(): void;
|
|
80
|
+
}
|
|
81
|
+
type FormStore = FormState & FormActions;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a Zustand vanilla store for form state.
|
|
84
|
+
* One store per open entry – not a singleton.
|
|
85
|
+
*
|
|
86
|
+
* Includes undo/redo via CommandHistory (snapshot-based, debounced).
|
|
87
|
+
*/
|
|
88
|
+
declare function createFormStore(): zustand.StoreApi<FormStore>;
|
|
89
|
+
|
|
90
|
+
interface RecentEntry {
|
|
91
|
+
readonly collection: string;
|
|
92
|
+
readonly slug: string;
|
|
93
|
+
readonly label: string;
|
|
94
|
+
readonly timestamp: number;
|
|
95
|
+
}
|
|
96
|
+
interface AppState {
|
|
97
|
+
currentCollection: string | null;
|
|
98
|
+
currentSlug: string | null;
|
|
99
|
+
sidebarOpen: boolean;
|
|
100
|
+
theme: 'light' | 'dark' | 'system';
|
|
101
|
+
recentEntries: RecentEntry[];
|
|
102
|
+
}
|
|
103
|
+
interface AppActions {
|
|
104
|
+
navigate(collection: string, slug: string): void;
|
|
105
|
+
toggleSidebar(): void;
|
|
106
|
+
setTheme(theme: AppState['theme']): void;
|
|
107
|
+
addRecentEntry(entry: Omit<RecentEntry, 'timestamp'>): void;
|
|
108
|
+
}
|
|
109
|
+
type AppStore = AppState & AppActions;
|
|
110
|
+
declare function createAppStore(): zustand.StoreApi<AppStore>;
|
|
111
|
+
|
|
112
|
+
interface FieldRendererProps {
|
|
113
|
+
field: AnyFieldDef;
|
|
114
|
+
path: FieldPath;
|
|
115
|
+
store: ReturnType<typeof createFormStore>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generic field renderer – looks up the correct component by field type.
|
|
119
|
+
* Adding a new field type = adding an entry to rendererMap.
|
|
120
|
+
*/
|
|
121
|
+
declare const FieldRenderer: react.NamedExoticComponent<FieldRendererProps>;
|
|
122
|
+
|
|
123
|
+
interface EntryFormProps {
|
|
124
|
+
schema: FieldRecord;
|
|
125
|
+
initialValues: Record<string, unknown>;
|
|
126
|
+
/** If provided, form starts with these values but initialValues stays as the saved baseline (for dirty detection). */
|
|
127
|
+
draftValues?: Record<string, unknown>;
|
|
128
|
+
onSave?: (values: Record<string, unknown>) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Optional ref that receives the internal FormStore instance.
|
|
131
|
+
* Useful for external subscribers (e.g. live preview sync).
|
|
132
|
+
*/
|
|
133
|
+
storeRef?: MutableRefObject<ReturnType<typeof createFormStore> | null>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Generates a complete form from a schema definition.
|
|
137
|
+
* Each instance creates its own FormStore (one store per entry).
|
|
138
|
+
*/
|
|
139
|
+
declare const EntryForm: react.NamedExoticComponent<EntryFormProps>;
|
|
140
|
+
|
|
141
|
+
interface EntryListProps {
|
|
142
|
+
collection: string;
|
|
143
|
+
label: string;
|
|
144
|
+
onSelect: (slug: string) => void;
|
|
145
|
+
onCreate: () => void;
|
|
146
|
+
selectedSlug?: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* List of entries in a collection with create/delete actions.
|
|
150
|
+
*/
|
|
151
|
+
declare function EntryList({ collection, label, onSelect, onCreate, selectedSlug, }: EntryListProps): react_jsx_runtime.JSX.Element;
|
|
152
|
+
|
|
153
|
+
interface CollectionViewProps {
|
|
154
|
+
collectionKey: string;
|
|
155
|
+
collection: CollectionDefinition;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Full CRUD view for a collection: list on the left, form on the right.
|
|
159
|
+
*/
|
|
160
|
+
declare function CollectionView({ collectionKey, collection }: CollectionViewProps): react_jsx_runtime.JSX.Element;
|
|
161
|
+
|
|
162
|
+
declare function AdminApp(): react_jsx_runtime.JSX.Element;
|
|
163
|
+
|
|
164
|
+
type ToastType = 'success' | 'error' | 'info';
|
|
165
|
+
interface ToastContextValue {
|
|
166
|
+
toast: (message: string, type?: ToastType) => void;
|
|
167
|
+
}
|
|
168
|
+
declare function useToast(): ToastContextValue;
|
|
169
|
+
declare function ToastProvider({ children }: {
|
|
170
|
+
children: React.ReactNode;
|
|
171
|
+
}): react_jsx_runtime.JSX.Element;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Hook for individual field state – subscribes only to its own path.
|
|
175
|
+
* This prevents unnecessary re-renders when other fields change.
|
|
176
|
+
*
|
|
177
|
+
* IMPORTANT: All selectors must return referentially stable values
|
|
178
|
+
* to avoid React 19 useSyncExternalStore infinite loops.
|
|
179
|
+
*/
|
|
180
|
+
declare function useField(store: ReturnType<typeof createFormStore>, path: FieldPath): {
|
|
181
|
+
value: unknown;
|
|
182
|
+
errors: string[];
|
|
183
|
+
isTouched: boolean;
|
|
184
|
+
isDirty: boolean;
|
|
185
|
+
setValue: (newValue: unknown) => void;
|
|
186
|
+
touch: () => void;
|
|
187
|
+
path: FieldPath;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Hook for saving form content to the repository.
|
|
192
|
+
*/
|
|
193
|
+
declare function useSave(store: ReturnType<typeof createFormStore>, collection: string, slug: string): {
|
|
194
|
+
save: () => Promise<Result<CommitResult>>;
|
|
195
|
+
saving: boolean;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
interface ProxyConfig {
|
|
199
|
+
/** Base URL of the Setzkasten API proxy, e.g. '/api/setzkasten/github' */
|
|
200
|
+
proxyBaseUrl: string;
|
|
201
|
+
/** GitHub owner */
|
|
202
|
+
owner: string;
|
|
203
|
+
/** GitHub repo name */
|
|
204
|
+
repo: string;
|
|
205
|
+
/** Branch to operate on */
|
|
206
|
+
branch: string;
|
|
207
|
+
/** Base path within the repo for content files, e.g. 'src/content' */
|
|
208
|
+
contentPath?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Client-side ContentRepository that calls the server-side GitHub proxy.
|
|
212
|
+
* The GitHub token never leaves the server – all API calls go through
|
|
213
|
+
* /api/setzkasten/github/[...path].
|
|
214
|
+
*/
|
|
215
|
+
declare class ProxyContentRepository implements ContentRepository {
|
|
216
|
+
private baseUrl;
|
|
217
|
+
private owner;
|
|
218
|
+
private repo;
|
|
219
|
+
private branch;
|
|
220
|
+
private contentPath;
|
|
221
|
+
constructor(config: ProxyConfig);
|
|
222
|
+
private apiUrl;
|
|
223
|
+
private contentFilePath;
|
|
224
|
+
listEntries(collection: string): Promise<Result<EntryListItem[]>>;
|
|
225
|
+
getEntry(collection: string, slug: string): Promise<Result<EntryData>>;
|
|
226
|
+
saveEntry(collection: string, slug: string, data: EntryData, assets?: Asset[]): Promise<Result<CommitResult>>;
|
|
227
|
+
deleteEntry(collection: string, slug: string): Promise<Result<CommitResult>>;
|
|
228
|
+
getTree(ref?: string): Promise<Result<TreeNode[]>>;
|
|
229
|
+
private uploadAsset;
|
|
230
|
+
private uint8ToBase64;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface ProxyAssetConfig {
|
|
234
|
+
/** Base URL of the Setzkasten GitHub proxy */
|
|
235
|
+
proxyBaseUrl: string;
|
|
236
|
+
/** GitHub owner */
|
|
237
|
+
owner: string;
|
|
238
|
+
/** GitHub repo */
|
|
239
|
+
repo: string;
|
|
240
|
+
/** Branch */
|
|
241
|
+
branch: string;
|
|
242
|
+
/** Base path for assets in the repo, e.g. 'public/images' */
|
|
243
|
+
assetsPath?: string;
|
|
244
|
+
/** Public URL prefix for assets, e.g. '/images' */
|
|
245
|
+
publicUrlPrefix?: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Asset store that uploads files to GitHub via the server-side proxy.
|
|
249
|
+
* Preserves original filenames (Setzkasten principle).
|
|
250
|
+
*/
|
|
251
|
+
declare class ProxyAssetStore implements AssetStore {
|
|
252
|
+
private baseUrl;
|
|
253
|
+
private owner;
|
|
254
|
+
private repo;
|
|
255
|
+
private branch;
|
|
256
|
+
private assetsPath;
|
|
257
|
+
private publicUrlPrefix;
|
|
258
|
+
constructor(config: ProxyAssetConfig);
|
|
259
|
+
private apiUrl;
|
|
260
|
+
upload(directory: string, filename: string, content: Uint8Array, mimeType: string): Promise<Result<AssetMetadata>>;
|
|
261
|
+
delete(path: string): Promise<Result<void>>;
|
|
262
|
+
list(directory: string): Promise<Result<AssetMetadata[]>>;
|
|
263
|
+
private listRecursive;
|
|
264
|
+
private isImageFile;
|
|
265
|
+
getUrl(path: string): string;
|
|
266
|
+
getPreviewUrl(path: string): string;
|
|
267
|
+
private uint8ToBase64;
|
|
268
|
+
private guessMimeType;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export { AdminApp, type AppActions, type AppState, type AppStore, CollectionView, EntryForm, EntryList, FieldRenderer, type FieldRendererProps, type FormActions, type FormState, type FormStore, type ProxyAssetConfig, ProxyAssetStore, type ProxyConfig, ProxyContentRepository, type RecentEntry, type SetzKastenContext, SetzKastenProvider, ToastProvider, createAppStore, createFormStore, useAssets, useAuth, useConfig, useEventBus, useField, useRepository, useSave, useSetzKasten, useToast };
|