@bison-lab/payload-core 3.11.0 → 3.13.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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # `@bison-lab/payload-core`
2
2
 
3
3
  Site-agnostic Payload CMS configuration for Bison Lab sites: the SEO tab on
4
- a collection, the Theme Global, and the readers a page route and a root
5
- layout use. The rest of the shared CMS layer (access matrix, collection
6
- factories, slug field) joins it here as it is extracted.
4
+ a collection, the Theme Global, the Roles Global, and the readers a page
5
+ route and a root layout use. Collection factories join it here as they
6
+ are extracted.
7
7
 
8
8
  Full guide: <https://payload.bisonlab.ai/site/seo/>
9
9
 
@@ -22,10 +22,10 @@ lockstep.
22
22
 
23
23
  | Import | Contents | Runs where |
24
24
  | ---------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
25
- | `@bison-lab/payload-core` | `seoPlugin`, `createTheme`, `createBrandAssets`, `seedTheme`, `lookField`, `colorTokenField`, import-map strings, title and text helpers, types | Node. What `payload.config.ts` imports; it loads the plugin. |
25
+ | `@bison-lab/payload-core` | `seoPlugin`, `createTheme`, `createBrandAssets`, `createRoles`, `createFeatures`, `createPages`, `createUsers`, `createMedia`, `adminOnlyApiTab`, `seedTheme`, `seedRoles`, `seedFeatures`, predicates, `lookField`, `colorTokenField`, import-map strings, title and text helpers, types | Node. What `payload.config.ts` imports; it loads the plugin. |
26
26
  | `@bison-lab/payload-core/metadata` | `pageMetadata`, the title helpers, the same types | Server. What a page route imports; it does not load the plugin. |
27
27
  | `@bison-lab/payload-core/theme` | `getPublishedTheme`, `getPublishedIdentity`, `themeConfigFromDoc`, `themeHead`, `themeHeadFromDoc` | Server. What a root layout imports; it does not load the plugin or React. |
28
- | `@bison-lab/payload-core/admin` | Theme child fields (color scale, library, gray family, fonts, appearance, publish) | Admin. Referenced by import-map string; `generate:importmap` writes it. |
28
+ | `@bison-lab/payload-core/admin` | Theme child fields, the Roles and Features matrices, and the Users Roles checklist | Admin. Referenced by import-map string; `generate:importmap` writes it. |
29
29
  | `@bison-lab/payload-core/react` | `ThemePreview` (legacy; Theme has no preview pane) | Client. Kept so an older site import does not break. |
30
30
 
31
31
  ## What editors get
@@ -137,25 +137,34 @@ is never enforced and lives in the Needs-attention modal, not on the page.
137
137
  Pass `destructive` (or `seed.brandDestructive`); otherwise Theme uses the
138
138
  library’s `DESTRUCTIVE_SCALE_HEX` (`#ef4444`). Success falls back to `#22c55e`.
139
139
 
140
- **1. Register the Global.** Access is required — the predicates live in the
141
- site's `src/platform` until they move here. Pass `canManageBrand` as
142
- `update`.
140
+ **1. Register the Global.** Access is required — Theme does not read the
141
+ Roles or Features Global. Pass `canUseFeature("theme")` as `update`
142
+ (sites can keep `canManageBrand` until they adopt the switchboard).
143
143
 
144
144
  ```ts
145
145
  // payload.config.ts
146
- import { BRAND_ASSETS_SLUG, createBrandAssets, createTheme } from "@bison-lab/payload-core";
146
+ import {
147
+ BRAND_ASSETS_SLUG,
148
+ createBrandAssets,
149
+ canUseFeature,
150
+ createFeatures,
151
+ createRoles,
152
+ createTheme,
153
+ isAuthenticated,
154
+ } from "@bison-lab/payload-core";
147
155
  import bisonConfig from "../bison.config.json";
148
- import { canManageBrand, isAuthenticated } from "@/platform/access";
149
156
 
150
157
  export default buildConfig({
151
158
  collections: [
152
159
  createBrandAssets({
153
- access: { read: () => true, update: canManageBrand },
160
+ access: { read: () => true, update: canUseFeature("brand-assets") },
154
161
  }),
155
162
  ],
156
163
  globals: [
164
+ createRoles(),
165
+ createFeatures(),
157
166
  ...createTheme({
158
- access: { read: isAuthenticated, update: canManageBrand },
167
+ access: { read: isAuthenticated, update: canUseFeature("theme") },
159
168
  seed: bisonConfig,
160
169
  logo: { collection: BRAND_ASSETS_SLUG },
161
170
  identity: {
@@ -164,6 +173,10 @@ export default buildConfig({
164
173
  mark: { url: "/logo-mark.svg", alt: "Acme mark" },
165
174
  },
166
175
  },
176
+ colorUsages: {
177
+ collections: ["pages"],
178
+ globals: ["navigation"],
179
+ },
167
180
  onPublish: async () => {
168
181
  revalidateTag("theme");
169
182
  },
@@ -172,6 +185,15 @@ export default buildConfig({
172
185
  });
173
186
  ```
174
187
 
188
+ **Color usages.** `createTheme({ colorUsages })` is the only site wiring for
189
+ "is this custom color assigned?" Name every collection and global that
190
+ stores a `lookField` / `colorTokenField` (or a string token those fields
191
+ wrote). The package scans those documents — drafts included — and
192
+ rewrites tokens when Colors Delete needs a replacement. Omit the option,
193
+ or pass empty arrays, and every additional color is unused and deletes
194
+ immediately. A new look surface is added to `colorUsages`, not copied
195
+ into a site helper. Site adopt names the slugs (SPI-93).
196
+
175
197
  Then `payload generate:importmap` (the Theme child fields resolve from
176
198
  `@bison-lab/payload-core/admin`), `payload generate:types`, and
177
199
  `payload migrate:create`.
@@ -181,11 +203,13 @@ so the site renders what `bison-theme.css` rendered before anyone opens the
181
203
  admin:
182
204
 
183
205
  ```ts
184
- import { seedTheme } from "@bison-lab/payload-core";
206
+ import { seedFeatures, seedRoles, seedTheme } from "@bison-lab/payload-core";
185
207
  import bisonConfig from "../bison.config.json";
186
208
 
187
209
  export async function up({ payload }) {
188
210
  await seedTheme(payload, bisonConfig);
211
+ await seedRoles(payload); // pass the same extras as createRoles, if any
212
+ await seedFeatures(payload); // pass the same extras as createFeatures, if any
189
213
  }
190
214
  ```
191
215
 
@@ -226,24 +250,72 @@ Logo, favicon, and mobile-menu mark live in a locked cupboard, not in
226
250
  ordinary Media. Call `createBrandAssets` and point Theme at
227
251
  `BRAND_ASSETS_SLUG`. Access is the same shape as `createTheme`: `read` is
228
252
  typically public so the live site can load the file; `update` is
229
- `canManageBrand` and covers create, update, delete, and version history.
253
+ `canUseFeature("brand-assets")` (or `canManageBrand` until the site
254
+ adopts the switchboard) and covers create, update, delete, and version history.
230
255
  SVG preferred, PNG and ICO allowed; an SVG is sanitized on upload.
231
256
  Versions stay on so a replaced file can be rolled back.
232
257
 
233
258
  Each row carries a label, usage notes, and required alt text so a logo
234
259
  has its accessible name. Then `payload migrate:create`.
235
260
 
261
+ ## Roles
262
+
263
+ Settings → Roles is an Admin-editable array. Rank is drag-to-reorder.
264
+ Ticks are Content, Brand, Publish, and Users. Defaults ship in the
265
+ package (Brand on Designer only; Admin + Designer both store; Developer
266
+ is exclusive). A site may pass `extras` into `createRoles` — each extra
267
+ is a slug, a display label, and default ticks — and an Admin can add
268
+ further custom rows and edit any row’s name. Seed slugs stay read-only.
269
+ `canManageBrand` and the other predicates read the saved Global and fall
270
+ back to that seed (plus extras). The package never writes `developer`
271
+ onto a user row.
272
+
273
+ ```ts
274
+ createRoles({
275
+ extras: [{ role: "editor", label: "Editor", content: true, brand: false, publish: false, users: false }],
276
+ });
277
+ ```
278
+
279
+ ## Features
280
+
281
+ Settings → Features is a feature × role grid. The catalogue is code:
282
+ Pages, Media, Theme, Brand assets, Users, Roles, and Features. A site
283
+ may pass `extras` into `createFeatures` — those rows appear only there.
284
+ `canUseFeature(slug)` reads the saved grid and falls back to Content →
285
+ Pages/Media and Brand → Theme/Brand assets when the Global is empty.
286
+ Users, Roles, and Features cannot be turned off; Developer is always
287
+ allowed. `createPages` / `createMedia` use `canUseFeature` for create
288
+ and update (and `admin.hidden`). Theme and brand-assets still take
289
+ `access` arguments — pass `canUseFeature("theme")` /
290
+ `canUseFeature("brand-assets")` as `update`.
291
+
292
+ ```ts
293
+ createFeatures({
294
+ extras: [{ slug: "doctors", label: "Doctor search" }],
295
+ });
296
+ ```
297
+
298
+ ## Pages, users, and media
299
+
300
+ `createPages`, `createUsers`, `createMedia`, `slugField`, and
301
+ `adminOnlyApiTab` ship from the same React-free entry. Pages take the
302
+ site's hero and layout blocks, reserved-slug predicate, and preview
303
+ path. Users sit under Settings next to Roles, take `secureCookies`, and
304
+ offer whatever rows the Global (or the seed plus the same `extras`) has.
305
+ The checklist labels are the editable names. `adminOnlyApiTab` shows the
306
+ document API tab only to Developer.
307
+
236
308
  ## No generated types
237
309
 
238
310
  The package cannot import a site's `payload-types`, so the shapes it reads
239
- (`SeoMeta`, `SeoImageDoc`, `SeoPage`, `ThemeDoc`) are hand-written and
311
+ (`SeoMeta`, `SeoImageDoc`, `SeoPage`, `ThemeDoc`, `RolesDoc`) are hand-written and
240
312
  structural. A generated `Page`, `Media` or Theme Global is assignable to
241
313
  them; nothing carries an index signature, since an interface will not assign
242
314
  to a type that has one.
243
315
 
244
316
  ## Changing a field
245
317
 
246
- `noIndexField`, the plugin's own fields, the Theme Global fields, and the
247
- brand-assets collection fields are columns in every consuming site. A
248
- change to them is a schema change: say "run `payload migrate:create`" in
249
- the changeset.
318
+ `noIndexField`, the plugin's own fields, the Theme Global fields, the
319
+ Roles Global fields, and the brand-assets collection fields are columns
320
+ in every consuming site. A change to them is a schema change: say "run
321
+ `payload migrate:create`" in the changeset.
package/dist/admin.d.mts CHANGED
@@ -19,10 +19,10 @@ declare const ColorScaleField: GroupFieldClientComponent;
19
19
  //#endregion
20
20
  //#region src/admin/library-field.d.ts
21
21
  /**
22
- * Custom colors: the same scale controls as system colors. A row that is
23
- * not referenced on a page deletes immediately. Rewriting in-use tokens
24
- * (`rewriteColorToken`) waits on a site usage lookup Theme cannot see
25
- * page assignments by itself.
22
+ * Custom colors: the same scale controls as system colors. Unused: the
23
+ * Theme store's color-usages endpoint returns nothing and the row is
24
+ * gone. In use: pick a live replacement; confirm rewrites stored tokens
25
+ * then drops the row. Success and Destructive stay off that list.
26
26
  */
27
27
  declare const LibraryField: ArrayFieldClientComponent;
28
28
  //#endregion
@@ -101,5 +101,39 @@ declare const IdentityFallback: UIFieldClientComponent;
101
101
  */
102
102
  declare const LookField: TextFieldClientComponent;
103
103
  //#endregion
104
- export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, SectionHeading, ThemeDocumentControls, ThemeSaveButton };
104
+ //#region src/admin/roles-matrix-field.d.ts
105
+ /**
106
+ * Payload's array field: drag rank stays, add is for custom rows, seed
107
+ * rows have no remove. Reset restores seed ticks and package labels.
108
+ */
109
+ declare const RolesMatrixField: ArrayFieldClientComponent;
110
+ //#endregion
111
+ //#region src/admin/roles-row-label.d.ts
112
+ /**
113
+ * Array row header. Shows the current display name, not “Role 01”.
114
+ */
115
+ declare function RolesRowLabel(): react_jsx_runtime0.JSX.Element;
116
+ //#endregion
117
+ //#region src/admin/role-slug-field.d.ts
118
+ /**
119
+ * Seed slugs stay read-only. A custom row's slug is typed here.
120
+ */
121
+ declare const RoleSlugField: TextFieldClientComponent;
122
+ //#endregion
123
+ //#region src/admin/roles-field.d.ts
124
+ /**
125
+ * Users Roles checklist. Order and labels come from the field options (the
126
+ * Roles Global, or the seed plus extras). Copy is `roleDescription`.
127
+ * Developer is exclusive; a custom role does not clear Designer.
128
+ */
129
+ declare const RolesField: SelectFieldClientComponent;
130
+ //#endregion
131
+ //#region src/admin/features-matrix-field.d.ts
132
+ /**
133
+ * Feature × role grid. Columns come from the Roles Global (or the seed).
134
+ * Locked rows and Developer cannot be cleared.
135
+ */
136
+ declare const FeaturesMatrixField: ArrayFieldClientComponent;
137
+ //#endregion
138
+ export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FeaturesMatrixField, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, LookField, PairingField, PublishChild, RoleSlugField, RolesField, RolesMatrixField, RolesRowLabel, SectionHeading, ThemeDocumentControls, ThemeSaveButton };
105
139
  //# sourceMappingURL=admin.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/admin/color-field.tsx","../src/admin/color-scale-field.tsx","../src/admin/library-field.tsx","../src/admin/font-field.tsx","../src/admin/pairing-field.tsx","../src/admin/appearance-field.tsx","../src/admin/grey-scale-field.tsx","../src/admin/contrast-report.tsx","../src/admin/section-heading.tsx","../src/admin/publish-child.tsx","../src/admin/save-button.tsx","../src/admin/identity-fallback.tsx","../src/admin/look-field.tsx"],"mappings":";;;;;;;;;;cAWa,UAAA,EAAY,wBAAA;;;;;;;cCsBZ,eAAA,EAAiB,yBAAA;;;;;;;;ADtB9B;cEca,YAAA,EAAc,yBAAA;;;;;;;;AFd3B;cGOa,SAAA,EAAW,0BAAA;;;;;;;cCNX,YAAA,EAAc,sBAAA;;;;;;;cCCd,eAAA,EAAiB,yBAAA;;;;;;;cCuDjB,cAAA,EAAgB,0BAAA;;;;;;;cChDhB,cAAA,EAAgB,sBAAA;;;;;;cCbhB,cAAA,EAAgB,sBAAA;;;cCgJhB,YAAA,EAAc,sBAAA;;;;;;;;iBC3IX,qBAAA,CAAA,GAAqB,kBAAA,CAAA,GAAA,CAAA,OAAA;AVDrC;;;;AAAA,iBUgBgB,eAAA,CAAA,GAAe,kBAAA,CAAA,GAAA,CAAA,OAAA;;iBA0Bf,gBAAA,CAAA,GAAgB,kBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;cC7CnB,gBAAA,EAAkB,sBAAA;;;;;;;cCIlB,SAAA,EAAW,wBAAA"}
1
+ {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/admin/color-field.tsx","../src/admin/color-scale-field.tsx","../src/admin/library-field.tsx","../src/admin/font-field.tsx","../src/admin/pairing-field.tsx","../src/admin/appearance-field.tsx","../src/admin/grey-scale-field.tsx","../src/admin/contrast-report.tsx","../src/admin/section-heading.tsx","../src/admin/publish-child.tsx","../src/admin/save-button.tsx","../src/admin/identity-fallback.tsx","../src/admin/look-field.tsx","../src/admin/roles-matrix-field.tsx","../src/admin/roles-row-label.tsx","../src/admin/role-slug-field.tsx","../src/admin/roles-field.tsx","../src/admin/features-matrix-field.tsx"],"mappings":";;;;;;;;;;cAWa,UAAA,EAAY,wBAAA;;;;;;;cCsBZ,eAAA,EAAiB,yBAAA;;;;;;;;ADtB9B;cEgBa,YAAA,EAAc,yBAAA;;;;;;;;AFhB3B;cGOa,SAAA,EAAW,0BAAA;;;;;;;cCNX,YAAA,EAAc,sBAAA;;;;;;;cCCd,eAAA,EAAiB,yBAAA;;;;;;;cCuDjB,cAAA,EAAgB,0BAAA;;;;;;;cChDhB,cAAA,EAAgB,sBAAA;;;;;;cCbhB,cAAA,EAAgB,sBAAA;;;cCgJhB,YAAA,EAAc,sBAAA;;;;;;;;iBC3IX,qBAAA,CAAA,GAAqB,kBAAA,CAAA,GAAA,CAAA,OAAA;AVDrC;;;;AAAA,iBUgBgB,eAAA,CAAA,GAAe,kBAAA,CAAA,GAAA,CAAA,OAAA;;iBA0Bf,gBAAA,CAAA,GAAgB,kBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;cC7CnB,gBAAA,EAAkB,sBAAA;;;;;;;cCIlB,SAAA,EAAW,wBAAA;;;;;;;cCYX,gBAAA,EAAkB,yBAAA;;;;;;iBCjBf,aAAA,CAAA,GAAa,kBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;cCChB,aAAA,EAAe,wBAAA;;;;;;;;cC+Cf,UAAA,EAAY,0BAAA;;;;;;;cCtCZ,mBAAA,EAAqB,yBAAA"}