Compare commits

..

34 Commits

Author SHA1 Message Date
Jarrod Flesch
fa60901385 Merge branch 'beta' into fix/beta/field-path-props 2024-11-14 12:57:20 -05:00
Jarrod Flesch
e75527b0a1 chore: clean up types for HiddenField and WatchCondition (#9208)
### What?
Aligns types for HiddenField and the WatchCondition component with the
rest of the fields. Since path is required when rendering a Field
component, there is no need to keep it optional in the WatchCondition
component.

### Why?
Hidden fields were requiring the `field` property to be passed, but the
only reason it needed it was to allow the path to fallback to name if
path was not passed. But path is required so there is no need for this
anymore.

This makes using the HiddenField simpler now.

### How?
Adjusts type on the HiddenField and the WatchCondition component.
2024-11-14 12:56:17 -05:00
Jarrod Flesch
9fc47cea6e chore: ensures all fields recieve parentPath and parentSchemaPath 2024-11-14 12:52:52 -05:00
Jacob Fletcher
5482e7ea15 perf: removes i18n.supportedLanguages from client config (#9209)
Similar to https://github.com/payloadcms/payload/pull/9195 but
specifically removing `i18n.supportedLanguages` from the client config.
This is a potentially large object that does not need to be sent through
the network when making RSC requests.
2024-11-14 12:48:00 -05:00
Jarrod Flesch
77c99c2f49 feat!: re-order DefaultCellComponentProps generics (#9207)
### What?
Changes the order of the `DefaultCellComponentProps` generic type,
allowing us to infer the type of cellData when a ClientField type is
passed as the first generic argument. You can override the cellData type
by passing the second generic.

Previously:
```ts
type DefaultCellComponentProps<TCellData = any, TField extends ClientField = ClientField>
```

New:
```ts
type DefaultCellComponentProps<TField extends ClientField = ClientField, TCellData = undefined>
```

### Why?
Changing the ClientField type to be the first argument allows us to
infer the cellData value type based on the type of field.

I could have kept the same signature but the usage would look like:
```ts
// Not very DX friendly
const MyCellComponent<DefaultCellComponentProps<,ClientField>> = () => null
```

### How?
The changes made
[here](https://github.com/payloadcms/payload/compare/chore/beta/simplify-DefaultCellComponentProps?expand=1#diff-24f3c92e546c2be3fed0bab305236bba83001309a7239c20a3e3dbd6f5f71dc6R29-R73)
allow this. You can override the type by passing in the second argument
to the generic.
2024-11-14 12:31:42 -05:00
Elliot DeNolf
5ff1bb366c chore: misc cleanup (#9206)
- Proper error logger usage
- Some no-fallthrough warning cleanup
2024-11-14 11:14:08 -05:00
James Mikrut
e6d04436a8 fix(ui): fixes layout shift when form is submitted (#9184)
Some fields cause layout shift when you submit the form. This PR reduces
that flicker.
2024-11-14 02:57:01 +00:00
Jarrod Flesch
81099cbb04 chore: improve custom server cell types (#9188)
### What?
Exposes DefaultServerCellComponentProps type for custom server cell
components.

### Why?
So users can type their custom server cell components properly.
2024-11-13 17:03:03 -05:00
Sasha
4509c38f4c docs: add within and intersects operators documentation (#9194)
Adds documentation for `within` and `intersects` operators.

#### Querying - within

In order to do query based on whether points are within a specific area
defined in GeoJSON, you can use the `within` operator.
Example:
```ts
const polygon: Point[] = [
  [9.0, 19.0], // bottom-left
  [9.0, 21.0], // top-left
  [11.0, 21.0], // top-right
  [11.0, 19.0], // bottom-right
  [9.0, 19.0], // back to starting point to close the polygon
]

payload.find({
  collection: "points",
  where: {
    point: {
      within: {
        type: 'Polygon',
        coordinates: [polygon],
      },
    },
  },
})
```


#### Querying - intersects

In order to do query based on whether points intersect a specific area
defined in GeoJSON, you can use the `intersects` operator.
Example:
```ts
const polygon: Point[] = [
  [9.0, 19.0], // bottom-left
  [9.0, 21.0], // top-left
  [11.0, 21.0], // top-right
  [11.0, 19.0], // bottom-right
  [9.0, 19.0], // back to starting point to close the polygon
]

payload.find({
  collection: "points",
  where: {
    point: {
      intersects: {
        type: 'Polygon',
        coordinates: [polygon],
      },
    },
  },
})
```
2024-11-13 21:59:22 +00:00
Jarrod Flesch
90e6a4fcd8 docs: note about passing req to local operations (#9192) 2024-11-13 16:49:50 -05:00
Elliot DeNolf
4690cd819a feat(storage-uploadthing)!: upgrade to v7 (#8346)
Upgrade uploadthing to v7

The `options` that can be passed to the plugin now mirror the
`UTApiOptions` of v7.

The most notable change is to pass `token` with
`process.env.UPLOADTHING_TOKEN` instead of `apiKey` with
`process.env.UPLOADTHING_SECRET`.

```diff
options: {
- apiKey: process.env.UPLOADTHING_SECRET,
+ token: process.env.UPLOADTHING_TOKEN,
  acl: 'public-read',
},
2024-11-13 21:27:02 +00:00
Dan Ribbens
afd69c4d54 chore: fix community e2e test (#9187) 2024-11-13 15:47:45 -05:00
Dan Ribbens
de52490a98 chore: fix community test (#9186) 2024-11-13 15:37:44 -05:00
Jarrod Flesch
129fadfd2c fix: wires up abort controller logic for list columns (#9180)
### What?
List column state could become out of sync if toggling columns happened
in rapid succession as seen in CI. Or when using a spotty connection
where responses could come back out of order.

### Why?
State was not being preserved between toggles. Leading to incorrect
columns being toggled on/off.

### How?
Updates internal column state before making the request to the server so
when a future toggle occurs it has up to date state of all columns. Also
introduces an abort controller to prevent the out of order response
issue.
2024-11-13 14:58:49 -05:00
Jacob Fletcher
cea7d58d96 docs: updates and improves migration guide (#9176)
This is a first pass at updating the 3.0 migration guide. While this
makes significant changes and improvements to the guide, it does not
necessarily reflect _all_ of the migration steps needed in their
entirety quite yet. Those will continue to come in.

Key changes:
- Cleans up outdated examples and removes old ones
- Updates code snippets to latest patterns
- Diffs everything for improved readability
2024-11-13 14:29:50 -05:00
Elliot DeNolf
6baff8a3ba chore(release): v3.0.0-beta.130 [skip ci] 2024-11-13 14:18:00 -05:00
James Mikrut
ced79be591 Chore/clean community (#9181)
Cleans up _community test suite
2024-11-13 14:12:19 -05:00
Sasha
5b9cee67c0 fix(db-postgres): create relationship-v2-v3 migration (#9178)
### What?
This command from here:
https://github.com/payloadcms/payload/pull/6339
```sh
payload migrate:create --file @payloadcms/db-postgres/relationships-v2-v3
```
stopped working after db-postgers and drizzle packages were separated 

### How?
Passes correct `dirname` to `getPredefinedMigration`

Additionally, adds support for `.js` files in `getPredefinedMigration`
2024-11-13 19:02:17 +00:00
Jarrod Flesch
bcbca0e44a chore: improves field types (#9172)
### What?
Ensures `path` is required and only present on the fields that expect it
(all fields except row).

Deprecates `useFieldComponents` and `FieldComponentsProvider` and
instead extends the RenderField component to account for all field
types. This also improves type safety within `RenderField`.

### Why?
`path` being optional just adds DX overhead and annoyance. 

### How?
Added `FieldPaths` type which is added to iterable field types. Placed
`path` back onto the ClientFieldBase type.
2024-11-13 13:53:47 -05:00
Paul
cd95daf029 fix: add inline <head><style> to ensure the order of declared css layers as much as possible (#9123)
Should help alleviate some problems outlined in
https://github.com/payloadcms/payload/issues/8878
2024-11-13 13:49:38 -05:00
James Mikrut
9da85430a5 feat: adds ability to define base filter for list view (#9177)
Adds the ability to define base list view filters, which is super
helpful when you're doing multi-tenant things in Payload.
2024-11-13 18:34:01 +00:00
Paul
f4d526d6e5 fix: fallbackLocale not respecting default settings, locale specific fallbacks and not respecting 'none' or false (#8591)
This PR fixes and improves a few things around localisation and
fallbackLocale:
- For the REST API `fallbackLocale` and `fallback-locale` are treated
the same for consistency with the Local API
- `fallback: false` in config is now respected, by default results will
not fallback to `defaultLocale` unless this config is true, can also be
overridden by providing an explicit `fallbackLocale` in the request
- locale specific fallbacks will now take priority over `defaultLocale`
unless an explicit fallback is provided
- Fixes types on operations to allow `'none'` as a value for
fallbackLocale
- `fallback` is now true by default if unspecified

Closes https://github.com/payloadcms/payload/issues/8443
2024-11-13 12:13:31 -06:00
Patrik
3b55458c0d fix(next): safely check for state when creating first user (#9168)
On createFirstUser, state from form-state was returning null.

![Screenshot 2024-11-13 at 9 58
04 AM](https://github.com/user-attachments/assets/19019e3e-09fc-42e6-9b9a-9198772d9133)

Only return `state` if response from form-state is not null.
2024-11-13 11:19:43 -05:00
Patrik
51dc3f06b1 chore(templates): update lock file for website template (#9169) 2024-11-13 16:15:18 +00:00
Dan Ribbens
d6282221db feat: customize log levels and downgrade common errors to info (#9156)
### What?

Allows configuration of the log level based on the error being thrown
and also downgrades common errors to be info instead of error by
default.

### Why?

Currently all errors result in logger.error being called which can
polute the logs with junk that is normal and doesn't need attention.

### How?

Adds a config property called `loggingLevels` that is used to override
the default log levels based on the name of the error being thrown.
Sanitize config will provide the defaulted 'info' level errors which can
be overriden in the config.

Before
![Screenshot 2024-11-12
144459](https://github.com/user-attachments/assets/47318329-23b7-4627-afc4-a0bcf4dc3d58)

After

![image](https://github.com/user-attachments/assets/85b06be4-0ab8-4ca2-b237-d6a4d54add3a)
2024-11-13 09:24:53 -05:00
Elliot DeNolf
f264c8087a chore: add download/week to README 2024-11-12 21:51:11 -05:00
Elliot DeNolf
1b16730b20 chore: remove useless script, can use HUSKY=0 2024-11-12 20:50:52 -05:00
Jacob Fletcher
f6bdc0aaf6 feat(next): initializes nav group prefs on the server and consolidates records (#9145) 2024-11-12 20:05:12 -05:00
Jarrod Flesch
a8e3095e45 fix: expose server and client props to custom list slot components (#9159)
### What?
Adds `serverProps` and `clientProps` to custom list view slot
components.

### Why?
They were missing and should be exposed.

### How?
Created custom types for list slot components and threads them through
into `renderListSlots` function and passes them through to each
`RenderServerComponent` that renders list view slot components.
2024-11-12 18:18:19 -05:00
Paul
5ac4e73991 feat(templates): update config structure in website template to be more clear (#9161) 2024-11-12 22:56:32 +00:00
Jacob Fletcher
9ee6425761 docs: updates custom components and field props (#9157) 2024-11-12 22:35:29 +00:00
Elliot DeNolf
8c2fc71149 chore(release): v3.0.0-beta.129 [skip ci] 2024-11-12 17:07:25 -05:00
Patrik
88bef2e140 chore: updates flaky uploads tests (#9149)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-11-12 16:41:08 -05:00
Alessio Gravili
a1c99c8b45 fix(richtext-lexical): inline blocks drawer not rendering any fields due to incorrect schemapath suffix (#9158) 2024-11-12 16:31:20 -05:00
236 changed files with 7099 additions and 10061 deletions

View File

@@ -7,6 +7,8 @@
&nbsp;
<a href="https://discord.gg/payload"><img alt="Discord" src="https://img.shields.io/discord/967097582721572934?label=Discord&color=7289da&style=flat-square" /></a>
&nbsp;
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/dw/payload?style=flat-square" /></a>
&nbsp;
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/v/payload?style=flat-square" /></a>
&nbsp;
<a href="https://twitter.com/payloadcms"><img src="https://img.shields.io/badge/follow-payloadcms-1DA1F2?logo=twitter&style=flat-square" alt="Payload Twitter" /></a>

View File

@@ -31,7 +31,7 @@ The following options are available:
| **`hidden`** | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| **`hooks`** | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| **`useAsTitle`** | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title. |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#components). |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| **`defaultColumns`** | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this Collection. |
| **`enableRichTextLink`** | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
@@ -39,11 +39,12 @@ The following options are available:
| **`meta`** | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](./metadata). |
| **`preview`** | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#components). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| **`listSearchableFields`** | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| **`pagination`** | Set pagination-specific options for this Collection. [More details](#pagination). |
| **`baseListFilter`** | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
### Components
### Custom Components
Collections can set their own [Custom Components](./components) which only apply to [Collection](../configuration/collections)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.

View File

@@ -18,9 +18,9 @@ All Custom Components in Payload are [React Server Components](https://react.dev
There are four main types of Custom Components in Payload:
- [Root Components](#root-components)
- [Collection Components](./collections#components)
- [Global Components](./globals#components)
- [Field Components](./fields)
- [Collection Components](./collections#custom-components)
- [Global Components](./globals#custom-components)
- [Field Components](./fields#custom-components)
To swap in your own Custom Component, first consult the list of available components, determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
@@ -149,95 +149,6 @@ export default buildConfig({
}
```
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
<strong>Note:</strong>
You can also use set [Collection Components](./collections#components) and [Global Components](./globals#components) in their respective configs.
</Banner>
### Custom Providers
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export const MyProvider: React.FC = ({ children }) => {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
}
export const useMyCustomContext = () => useContext(MyCustomContext)
```
<Banner type="warning">
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
</Banner>
## Building Custom Components
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api/overview) directly on the front-end, among other things.
@@ -361,6 +272,7 @@ But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/us
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](./hooks#useconfig) hook:
```tsx
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
@@ -379,14 +291,13 @@ export const MyClientComponent: React.FC = () => {
See [Using Hooks](#using-hooks) for more details.
</Banner>
All [Field Components](./fields) automatically receive their respective Field Config through a common [`field`](./fields#the-field-prop) prop:
All [Field Components](./fields) automatically receive their respective Field Config through props.
```tsx
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldClientComponent = ({ field: { name } }) => {
export const MyClientFieldComponent: TextFieldServerComponent = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
@@ -395,28 +306,6 @@ export const MyClientFieldComponent: TextFieldClientComponent = ({ field: { name
}
```
### Using Hooks
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts:
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Getting the Current Language
All Custom Components can support multiple languages to be consistent with Payload's [Internationalization](../configuration/i18n). To do this, first add your translation resources to the [I18n Config](../configuration/i18n).
@@ -439,6 +328,7 @@ export default async function MyServerComponent({ i18n }) {
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
@@ -482,6 +372,7 @@ export default async function MyServerComponent({ payload, locale }) {
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
@@ -503,7 +394,29 @@ const Greeting: React.FC = () => {
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Styling Custom Components
### Using Hooks
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can one of the many hooks available depending on your needs.
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Adding Styles
Payload has a robust [CSS Library](./customizing-css) that you can use to style your Custom Components similarly to Payload's built-in styling. This will ensure that your Custom Components match the existing design system, and so that they automatically adapt to any theme changes that might occur.
@@ -539,10 +452,99 @@ Payload also exports its [SCSS](https://sass-lang.com) library for reuse which i
background-color: var(--theme-elevation-900);
}
}
```
<Banner type="success">
<strong>Note:</strong>
You can also drill into Payload's own component styles, or easily apply global, app-wide CSS. More on that [here](./customizing-css).
</Banner>
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
<strong>Note:</strong>
You can also use set [Collection Components](./collections#custom-components) and [Global Components](./globals#custom-components) in their respective configs.
</Banner>
### Custom Providers
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export const MyProvider: React.FC = ({ children }) => {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
}
export const useMyCustomContext = () => useContext(MyCustomContext)
```
<Banner type="warning">
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
</Banner>

View File

@@ -6,7 +6,7 @@ desc:
keywords:
---
[Fields](../fields/overview) within the [Admin Panel](./overview) can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#field-components), [Conditional Logic](#conditional-logic), [Custom Validations](../fields/overview#validation), and more.
[Fields](../fields/overview) within the [Admin Panel](./overview) can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#custom-components), [Conditional Logic](#conditional-logic), [Custom Validations](../fields/overview#validation), and more.
For example, your app might need to render a specific interface that Payload does not inherently support, such as a color picker. To do this, you could replace the default [Text Field](../fields/text) input with your own user-friendly component that formats the data into a valid color value.
@@ -56,334 +56,7 @@ The following options are available:
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
## Field Components
Within the [Admin Panel](./overview), fields are rendered in three distinct places:
- [Field](#the-field-component) - The actual form field rendered in the Edit View.
- [Cell](#the-cell-component) - The table cell component rendered in the List View.
- [Filter](#the-filter-component) - The filter component rendered in the List View.
To easily swap in Field Components with your own, use the `admin.components` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
]
}
```
The following options are available:
| Component | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#the-field-component). |
| **`Cell`** | The table cell rendered of the List View. [More details](#the-cell-component). |
| **`Filter`** | The filter component rendered in the List View. [More details](#the-filter-component). || Component | Description |
| **`Label`** | Override the default Label of the Field Component. [More details](#the-label-component). |
| **`Error`** | Override the default Error of the Field Component. [More details](#the-error-component). |
| **`Description`** | Override the default Description of the Field Component. [More details](#the-description-component). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput).|
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
_\* **`beforeInput`** and **`afterInput`** are only supported in fields that do not contain other fields, such as [`Text`](../fields/text), and [`Textarea`](../fields/textarea)._
### The Field Component
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To easily swap in your own Field Component, use the `admin.components.Field` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
<Banner type="warning">
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#the-label-component), [`Error`](#the-error-component), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
</Banner>
All Field Components receive the following props:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### Sending and receiving values from the form
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](./hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](./hooks) documentation. For additional help, see [Building Custom Components](./components#building-custom-components).
</Banner>
#### TypeScript
When building Custom Field Components, you can import the component type to ensure type safety. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and for every client/server environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### The `field` Prop
All Field Components are passed their own Field Config through a common `field` prop. Within Server Components, this is the original Field Config as written within your Payload Config. Within Client Components, however, this is a "Client Config", which is a sanitized, client-friendly version of the Field Config. This is because the original Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), meaning it cannot be passed into Client Components without first being transformed.
The Client Field Config is an exact copy of the original Field Config, minus all non-serializable properties, plus all evaluated functions such as field labels, [Custom Components](../components), etc.
Server Component:
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyServerField: TextFieldServerComponent = ({ clientField }) => {
return <TextField field={clientField} />
}
```
<Banner type="info">
<strong>Tip:</strong>
Server Components can still access the original Field Config through the `field` prop.
</Banner>
Client Component:
```tsx
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyTextField: TextFieldClientComponent = ({ field }) => {
return <TextField field={field} />
}
```
The following additional properties are also provided to the `field` prop:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`_path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray[0].myField`. |
| **`_schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `myGroup.myArray.myField` |
<Banner type="info">
<strong>Note:</strong>
These properties are underscored to denote that they are not part of the original Field Config, and instead are attached during client sanitization to make fields easier to work with on the front-end.
</Banner>
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### The Cell Component
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To easily swap in your own Cell Component, use the `admin.components.Cell` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
All Cell Components receive the following props:
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
<Banner type="info">
<strong>Tip:</strong>
Use the [`useTableCell`](./hooks#usetablecell) hook to subscribe to the field's `cellData` and `rowData`.
</Banner>
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
### The Label Component
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To easily swap in your own Label Component, use the `admin.components.Label` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
Custom Label Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
### The Error Component
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To easily swap in your own Error Component, use the `admin.components.Error` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
Custom Error Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| --------------- | ------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
### The Description Property
## Field Descriptions
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
@@ -391,7 +64,7 @@ A description can be configured in three ways:
- As a string.
- As a function which returns a string. [More details](#description-functions).
- As a React component. [More details](#the-description-component).
- As a React component. [More details](#description).
To easily add a Custom Description to a field, use the `admin.description` property in your [Field Config](../fields/overview):
@@ -415,7 +88,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
<Banner type="warning">
<strong>Reminder:</strong>
To replace the Field Description with a [Custom Component](./components), use the `admin.components.Description` property. [More details](#the-description-component).
To replace the Field Description with a [Custom Component](./components), use the `admin.components.Description` property. [More details](#description).
</Banner>
#### Description Functions
@@ -448,89 +121,6 @@ All Description Functions receive the following arguments:
| -------------- | ---------------------------------------------------------------- |
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
### The Description Component
Alternatively to the [Description Property](#the-description-property), you can also use a [Custom Component](./components) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To easily add a Description Component to a field, use the `admin.components.Description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
}
```
_For details on how to build a Custom Description, see [Building Custom Components](./components#building-custom-components)._
Custom Description Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
### afterInput and beforeInput
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
## Conditional Logic
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes three arguments:
@@ -569,3 +159,322 @@ The `condition` function should return a boolean that will control if the field
]
}
```
## Custom Components
Within the [Admin Panel](./overview), fields are represented in three distinct places:
- [Field](#field) - The actual form field rendered in the Edit View.
- [Cell](#cell) - The table cell component rendered in the List View.
- [Filter](#filter) - The filter component rendered in the List View.
To easily swap in Field Components with your own, use the `admin.components` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
]
}
```
The following options are available:
| Component | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput).|
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
### Field
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To easily swap in your own Field Component, use the `admin.components.Field` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
<Banner type="warning">
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#label), [`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
</Banner>
#### Default Props
All Field Components receive the following props by default:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
| **`schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `posts.myGroup.myArray.myField`. |
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
In addition to the above props, all Server Components will also receive the following props:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`clientField`** | The serializable Client Field Config. |
| **`field`** | The Field Config. [More details](../fields/overview). |
| **`data`** | The current document being edited. |
| **`i18n`** | The [i18n](../configuration/i18n) object.
| **`payload`** | The [Payload](../local-api/overview) class. |
| **`permissions`** | The field permissions based on the currently authenticated user. |
| **`siblingData`** | The data of the field's siblings. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`value`** | The value of the field at render-time. |
#### Sending and receiving values from the form
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](./hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](./hooks) documentation. For additional help, see [Building Custom Components](./components#building-custom-components).
</Banner>
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### Cell
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To easily swap in your own Cell Component, use the `admin.components.Cell` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
All Cell Components receive the same [Default Field Component Props](#field), plus the following:
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
### Label
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To easily swap in your own Label Component, use the `admin.components.Label` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
All Custom Label Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Label Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
### Description
Alternatively to the [Description Property](#the-description-property), you can also use a [Custom Component](./components) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To easily add a Description Component to a field, use the `admin.components.Description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
}
```
All Custom Description Components receive the same [Default Field Component Props](#field).
For details on how to build a Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
### Error
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To easily swap in your own Error Component, use the `admin.components.Error` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
All Error Components receive the [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Error Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
### afterInput and beforeInput
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
}
```
All `afterInput` and `beforeInput` Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components).

View File

@@ -29,13 +29,13 @@ The following options are available:
| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **`group`** | Text used as a label for grouping Collection and Global links together in the navigation. |
| **`hidden`** | Set to true or a function, called with the current user, returning true to exclude this Global from navigation and admin routing. |
| **`components`** | Swap in your own React components to be used within this Global. [More details](#components). |
| **`components`** | Swap in your own React components to be used within this Global. [More details](#custom-components). |
| **`preview`** | Function to generate a preview URL within the Admin Panel for this Global that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this collection. |
| **`meta`** | Page metadata overrides to apply to this Global within the Admin Panel. [More details](./metadata). |
### Components
### Custom Components
Globals can set their own [Custom Components](./components) which only apply to [Global](../configuration/globals)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.

View File

@@ -44,7 +44,7 @@ The `useField` hook accepts the following arguments:
| Property | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path` | If you do not provide a `path` or a `name`, this hook will look for one using the [`useFieldProps`](#usefieldprops) hook. |
| `path` | If you do not provide a `path`, `name` will be used instead. This is the path to the field in the form data. |
| `validate` | A validation function executed client-side _before_ submitting the form to the server. Different than [Field-level Validation](../fields/overview#validation) which runs strictly on the server. |
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
@@ -72,32 +72,6 @@ type FieldType<T> = {
}
```
## useFieldProps
[Custom Field Components](./fields#the-field-component) can be rendered on the server. When using a server component as a custom field component, you can access dynamic props from within any client component rendered by your custom server component. This is done using the `useFieldProps` hook. This is important because some fields can be dynamic, such as when nested in an [`array`](../fields/array) or [`blocks`](../fields/block) field. For example, items can be added, re-ordered, or deleted on-the-fly.
You can use the `useFieldProps` hooks to access dynamic props like `path`:
```tsx
'use client'
import { useFieldProps } from '@payloadcms/ui'
const CustomTextField: React.FC = () => {
const { path } = useFieldProps() // highlight-line
return (
<div>
{path}
</div>
)
}
```
<Banner type="success">
<strong>Tip:</strong>
The [`useField`](#usefield) hook calls the `useFieldProps` hook internally, so you don't need to use both in the same component unless explicitly needed.
</Banner>
## useFormFields
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
@@ -900,27 +874,6 @@ const MyComponent: React.FC = () => {
}
```
## useTableCell
Similar to [`useFieldProps`](#usefieldprops), all [Custom Cell Components](./fields#the-cell-component) are rendered on the server, and as such, only have access to static props at render time. But, some props need to be dynamic, such as the field value itself.
For this reason, dynamic props like `cellData` are managed in their own React context, which can be accessed using the `useTableCell` hook.
```tsx
'use client'
import { useTableCell } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
const { cellData } = useTableCell() // highlight-line
return (
<div>
{cellData}
</div>
)
}
```
## useDocumentEvents
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:

View File

@@ -65,7 +65,7 @@ export default buildConfig({
},
],
defaultLocale: 'en', // required
fallback: true,
fallback: true, // defaults to true
},
})
```
@@ -81,7 +81,7 @@ The following options are available:
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`defaultLocale`** | Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated unless a fallback is explicitly provided in the request. True by default. |
### Locales

View File

@@ -76,6 +76,7 @@ The following options are available:
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |

View File

@@ -147,7 +147,7 @@ You can control the user experience of the join field using the `admin` config p
| Option | Description |
|------------------------|----------------------------------------------------------------------------------------|
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
| **`components.Label`** | Override the default Label of the Field Component. [More details](#the-label-component). |
| **`components.Label`** | Override the default Label of the Field Component. [More details](../admin/fields#label) |
## Join Field Data

View File

@@ -100,7 +100,7 @@ Here are the available Presentational Fields:
<Banner type="warning">
<strong>Tip:</strong>
Don't see a Field Type that fits your needs? You can build your own using a [Custom Field Component](../admin/fields#the-field-component).
Don't see a Field Type that fits your needs? You can build your own using a [Custom Field Component](../admin/fields#field).
</Banner>
## Field Options

View File

@@ -73,6 +73,59 @@ export const ExampleCollection: CollectionConfig = {
}
```
## Querying
## Querying - near
In order to do query based on the distance to another point, you can use the `near` operator. When querying using the near operator, the returned documents will be sorted by nearest first.
## Querying - within
In order to do query based on whether points are within a specific area defined in GeoJSON, you can use the `within` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: "points",
where: {
point: {
within: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```
## Querying - intersects
In order to do query based on whether points intersect a specific area defined in GeoJSON, you can use the `intersects` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: "points",
where: {
point: {
intersects: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```

View File

@@ -204,4 +204,4 @@ If you are looking to create a dynamic select field, the following tutorial will
drawerTitle="How to Create a Custom Select Field: A Step-by-Step Guide"
/>
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field-component) docs.
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field) docs.

View File

@@ -32,8 +32,8 @@ export const MyUIField: Field = {
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field-component) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field-component) |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field) |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

View File

@@ -97,6 +97,17 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
_There are more options available on an operation by operation basis outlined below._
## Transactions
When your database uses transactions you need to thread req through to all local operations. Postgres uses transactions and MongoDB uses transactions when you are using replica sets. Passing req without transactions is still recommended.
```js
const post = await payload.find({
collection: 'posts',
req, // passing req is recommended
})
```
<Banner type="warning">
<strong>Note:</strong>
<br />

File diff suppressed because it is too large Load Diff

View File

@@ -37,21 +37,23 @@ _The exact query syntax will depend on the API you are using, but the concepts a
The following operators are available for use in queries:
| Operator | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `equals` | The value must be exactly equal. |
| `not_equals` | The query will return all documents where the value is not equal. |
| `greater_than` | For numeric or date-based fields. |
| `greater_than_equal` | For numeric or date-based fields. |
| `less_than` | For numeric or date-based fields. |
| `less_than_equal` | For numeric or date-based fields. |
| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. |
| `contains` | Must contain the value entered, case-insensitive. |
| `in` | The value must be found within the provided comma-delimited list of values. |
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
| `all` | The value must contain all values provided in the comma-delimited list. |
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
| `near` | For distance related to a [Point Field](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
| Operator | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `equals` | The value must be exactly equal. |
| `not_equals` | The query will return all documents where the value is not equal. |
| `greater_than` | For numeric or date-based fields. |
| `greater_than_equal` | For numeric or date-based fields. |
| `less_than` | For numeric or date-based fields. |
| `less_than_equal` | For numeric or date-based fields. |
| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. |
| `contains` | Must contain the value entered, case-insensitive. |
| `in` | The value must be found within the provided comma-delimited list of values. |
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
| `all` | The value must contain all values provided in the comma-delimited list. |
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
| `near` | For distance related to a [Point Field](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
| `within` | For [point fields][../fields/point] to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying---within) |
| `intersects` | For [point fields][../fields/point] to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying---intersects) |
<Banner type="success">
<strong>Tip:</strong>

View File

@@ -20,7 +20,9 @@ export const Login = ({ tenantSlug }: Props) => {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (!usernameRef?.current?.value || !passwordRef?.current?.value) {return}
if (!usernameRef?.current?.value || !passwordRef?.current?.value) {
return
}
const actionRes = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/external-users/login`,
{

View File

@@ -6,7 +6,9 @@ import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
export const ensureUniqueSlug: FieldHook = async ({ data, originalDoc, req, value }) => {
// if value is unchanged, skip validation
if (originalDoc.slug === value) {return value}
if (originalDoc.slug === value) {
return value
}
const incomingTenantID = typeof data?.tenant === 'object' ? data.tenant.id : data?.tenant
const currentTenantID =

View File

@@ -14,7 +14,9 @@ export const Pages: CollectionConfig = {
read: (args) => {
// when viewing pages inside the admin panel
// restrict access to the ones your user has access to
if (isPayloadAdminPanel(args.req)) {return filterByTenantRead(args)}
if (isPayloadAdminPanel(args.req)) {
return filterByTenantRead(args)
}
// when viewing pages from outside the admin panel
// you should be able to see your tenants and public tenants

View File

@@ -7,7 +7,9 @@ export const tenantRead: Access = (args) => {
const req = args.req
// Super admin can read all
if (isSuperAdmin(args)) {return true}
if (isSuperAdmin(args)) {
return true
}
const tenantIDs = getTenantAccessIDs(req.user)

View File

@@ -7,13 +7,19 @@ import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAcces
export const createAccess: Access<User> = (args) => {
const { req } = args
if (!req.user) {return false}
if (!req.user) {
return false
}
if (isSuperAdmin(args)) {return true}
if (isSuperAdmin(args)) {
return true
}
const adminTenantAccessIDs = getTenantAdminTenantAccessIDs(req.user)
if (adminTenantAccessIDs.length > 0) {return true}
if (adminTenantAccessIDs.length > 0) {
return true
}
return false
}

View File

@@ -1,6 +1,8 @@
import type { Access } from 'payload'
export const isAccessingSelf: Access = ({ id, req }) => {
if (!req?.user) {return false}
if (!req?.user) {
return false
}
return req.user.id === id
}

View File

@@ -5,9 +5,13 @@ import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAcces
export const updateAndDeleteAccess: Access = (args) => {
const { req } = args
if (!req.user) {return false}
if (!req.user) {
return false
}
if (isSuperAdmin(args)) {return true}
if (isSuperAdmin(args)) {
return true
}
const adminTenantAccessIDs = getTenantAdminTenantAccessIDs(req.user)

View File

@@ -6,7 +6,9 @@ import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
export const ensureUniqueUsername: FieldHook = async ({ data, originalDoc, req, value }) => {
// if value is unchanged, skip validation
if (originalDoc.username === value) {return value}
if (originalDoc.username === value) {
return value
}
const incomingTenantID = typeof data?.tenant === 'object' ? data.tenant.id : data?.tenant
const currentTenantID =

View File

@@ -8,7 +8,9 @@ export const autofillTenant: FieldHook = ({ req, value }) => {
// return that tenant ID as the value
if (!value) {
const tenantIDs = getTenantAccessIDs(req.user)
if (tenantIDs.length === 1) {return tenantIDs[0]}
if (tenantIDs.length === 1) {
return tenantIDs[0]
}
}
return value

View File

@@ -10,7 +10,9 @@ export const tenantField: Field = {
access: {
read: () => true,
update: (args) => {
if (isSuperAdmin(args)) {return true}
if (isSuperAdmin(args)) {
return true
}
return tenantFieldUpdate(args)
},
},

View File

@@ -1,7 +1,9 @@
import type { User } from '../payload-types'
export const getTenantAccessIDs = (user: null | User): string[] => {
if (!user) {return []}
if (!user) {
return []
}
return (
user?.tenants?.reduce((acc: string[], { tenant }) => {
if (tenant) {
@@ -13,7 +15,9 @@ export const getTenantAccessIDs = (user: null | User): string[] => {
}
export const getTenantAdminTenantAccessIDs = (user: null | User): string[] => {
if (!user) {return []}
if (!user) {
return []
}
return (
user?.tenants?.reduce((acc: string[], { roles, tenant }) => {

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"private": true,
"type": "module",
"scripts": {
@@ -67,7 +67,7 @@
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
"force:build": "pnpm run build:core:force",
"lint": "turbo run lint --concurrency 1 --continue",
"lint-staged": "node ./scripts/run-lint-staged.js",
"lint-staged": "lint-staged",
"lint:fix": "turbo run lint:fix --concurrency 1 --continue",
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
"prepare": "husky",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -29,7 +29,6 @@
"types": "./src/index.ts",
"files": [
"dist",
"mock.js",
"predefinedMigrations"
],
"scripts": {

View File

@@ -106,7 +106,6 @@ const traverseFields = ({
switch (field.type) {
case 'array':
case 'group':
case 'tab': {
let fieldSelect: SelectType

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -50,12 +50,17 @@ import {
requireDrizzleKit,
} from '@payloadcms/drizzle/postgres'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import path from 'path'
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
import { fileURLToPath } from 'url'
import type { Args, PostgresAdapter } from './types.js'
import { connect } from './connect.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
const postgresIDType = args.idType || 'serial'
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
@@ -88,6 +93,9 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
beforeSchemaInit: args.beforeSchemaInit ?? [],
createDatabase,
createExtensions,
createMigration(args) {
return createMigration.bind(this)({ ...args, dirname })
},
defaultDrizzleSnapshot,
disableCreateDatabase: args.disableCreateDatabase ?? false,
drizzle: undefined,
@@ -132,7 +140,6 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
createGlobal,
createGlobalVersion,
createJSONQuery,
createMigration,
createVersion,
defaultIDType: payloadIDType,
deleteMany,

View File

@@ -58,8 +58,8 @@ export const traverseFields = (args: Args) => {
})
})
}
case 'collapsible':
case 'collapsible':
case 'row': {
return traverseFields({
...args,
@@ -84,7 +84,6 @@ export const traverseFields = (args: Args) => {
}
case 'relationship':
case 'upload': {
if (typeof field.relationTo === 'string') {
if (field.type === 'upload' || !field.hasMany) {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,5 +1,4 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type { Connect } from 'payload'
import { createClient } from '@libsql/client'

View File

@@ -8,7 +8,7 @@ import type {
SQLiteTableWithColumns,
UniqueConstraintBuilder,
} from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins } from 'payload'
import type { Field } from 'payload'
import { buildIndexName, createTableName } from '@payloadcms/drizzle'
import { relations, sql } from 'drizzle-orm'

View File

@@ -1,7 +1,7 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins, TabAsField } from 'payload'
import type { Field, TabAsField } from 'payload'
import {
buildIndexName,
@@ -472,16 +472,15 @@ export const traverseFields = ({
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
break
}
case 'code':
case 'email':
case 'textarea': {
targetTable[fieldName] = withDefault(text(columnName), field)
break
}
case 'collapsible':
case 'collapsible':
case 'row': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
@@ -654,7 +653,6 @@ export const traverseFields = ({
}
case 'json':
case 'richText': {
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
break
@@ -691,8 +689,8 @@ export const traverseFields = ({
case 'point': {
break
}
case 'radio':
case 'radio':
case 'select': {
const options = field.options.map((option) => {
if (optionIsObject(option)) {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -50,12 +50,17 @@ import {
requireDrizzleKit,
} from '@payloadcms/drizzle/postgres'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import path from 'path'
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
import { fileURLToPath } from 'url'
import type { Args, VercelPostgresAdapter } from './types.js'
import { connect } from './connect.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<VercelPostgresAdapter> {
const postgresIDType = args.idType || 'serial'
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
@@ -133,7 +138,9 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
createGlobal,
createGlobalVersion,
createJSONQuery,
createMigration,
createMigration(args) {
return createMigration.bind(this)({ ...args, dirname })
},
createVersion,
defaultIDType: payloadIDType,
deleteMany,

View File

@@ -58,8 +58,8 @@ export const traverseFields = (args: Args) => {
})
})
}
case 'collapsible':
case 'collapsible':
case 'row': {
return traverseFields({
...args,
@@ -84,7 +84,6 @@ export const traverseFields = (args: Args) => {
}
case 'relationship':
case 'upload': {
if (typeof field.relationTo === 'string') {
if (field.type === 'upload' || !field.hasMany) {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -2,10 +2,8 @@ import type { CreateMigration } from 'payload'
import fs from 'fs'
import { createRequire } from 'module'
import path from 'path'
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
import prompts from 'prompts'
import { fileURLToPath } from 'url'
import type { BasePostgresAdapter } from './types.js'
@@ -16,10 +14,8 @@ const require = createRequire(import.meta.url)
export const createMigration: CreateMigration = async function createMigration(
this: BasePostgresAdapter,
{ file, forceAcceptWarning, migrationName, payload },
{ dirname, file, forceAcceptWarning, migrationName, payload },
) {
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const dir = payload.db.migrationDir
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -0,0 +1,36 @@
import type { NavPreferences, Payload, User } from 'payload'
import { cache } from 'react'
export const getNavPrefs = cache(
async ({ payload, user }: { payload: Payload; user: User }): Promise<NavPreferences> =>
user
? await payload
.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
user,
where: {
and: [
{
key: {
equals: 'nav',
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: user.id,
},
},
],
},
})
?.then((res) => res?.docs?.[0]?.value)
: null,
)

View File

@@ -1,6 +1,7 @@
'use client'
import type { groupNavItems } from '@payloadcms/ui/shared'
import type { NavPreferences } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { NavGroup, useConfig, useNav, useTranslation } from '@payloadcms/ui'
@@ -13,7 +14,8 @@ const baseClass = 'nav'
export const DefaultNavClient: React.FC<{
groups: ReturnType<typeof groupNavItems>
}> = ({ groups }) => {
navPreferences: NavPreferences
}> = ({ groups, navPreferences }) => {
const pathname = usePathname()
const {
@@ -29,7 +31,7 @@ export const DefaultNavClient: React.FC<{
<Fragment>
{groups.map(({ entities, label }, key) => {
return (
<NavGroup key={key} label={label}>
<NavGroup isOpen={navPreferences?.groups?.[label]?.open} key={key} label={label}>
{entities.map(({ slug, type, label }, i) => {
let href: string
let id: string

View File

@@ -12,11 +12,12 @@ import { NavWrapper } from './NavWrapper/index.js'
const baseClass = 'nav'
import { getNavPrefs } from './getNavPrefs.js'
import { DefaultNavClient } from './index.client.js'
export type NavProps = ServerProps
export const DefaultNav: React.FC<NavProps> = (props) => {
export const DefaultNav: React.FC<NavProps> = async (props) => {
const { i18n, locale, params, payload, permissions, searchParams, user, visibleEntities } = props
if (!payload?.config) {
@@ -56,6 +57,8 @@ export const DefaultNav: React.FC<NavProps> = (props) => {
i18n,
)
const navPreferences = await getNavPrefs({ payload, user })
return (
<NavWrapper baseClass={baseClass}>
<nav className={`${baseClass}__wrap`}>
@@ -72,7 +75,7 @@ export const DefaultNav: React.FC<NavProps> = (props) => {
user,
}}
/>
<DefaultNavClient groups={groups} />
<DefaultNavClient groups={groups} navPreferences={navPreferences} />
<RenderServerComponent
Component={afterNavLinks}
importMap={payload.importMap}

View File

@@ -8,6 +8,7 @@ import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { checkDependencies, parseCookies } from 'payload'
import React from 'react'
import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js'
import { getClientConfig } from '../../utilities/getClientConfig.js'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
@@ -131,38 +132,7 @@ export const RootLayout = async ({
})
}
const navPreferences = user
? (
await payload.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
req,
user,
where: {
and: [
{
key: {
equals: 'nav',
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: user.id,
},
},
],
},
})
)?.docs?.[0]
: null
const isNavOpen = navPreferences?.value?.open ?? true
const navPrefs = await getNavPrefs({ payload, user })
const clientConfig = await getClientConfig({
config,
@@ -171,12 +141,15 @@ export const RootLayout = async ({
return (
<html data-theme={theme} dir={dir} lang={languageCode}>
<head>
<style>{`@layer payload-default, payload;`}</style>
</head>
<body>
<RootProvider
config={clientConfig}
dateFNSKey={i18n.dateFNSKey}
fallbackLang={config.i18n.fallbackLanguage}
isNavOpen={isNavOpen}
isNavOpen={navPrefs?.open}
languageCode={languageCode}
languageOptions={languageOptions}
permissions={permissions}

View File

@@ -47,7 +47,10 @@ export const routeError = async ({
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
logger.error(err.stack)
const level = payload.config.loggingLevels[err.name] ?? 'error'
if (level) {
logger[level](level === 'info' ? { msg: err.message } : { err })
}
// Internal server errors can contain anything, including potentially sensitive data.
// Therefore, error details will be hidden from the response unless `config.debug` is `true`

View File

@@ -1,5 +1,6 @@
import type { PayloadRequest, SanitizedConfig } from 'payload'
import { sanitizeFallbackLocale } from 'payload'
/**
* Mutates the Request to contain 'locale' and 'fallbackLocale' based on data or searchParams
*/
@@ -17,12 +18,14 @@ export function addLocalesToRequestFromData(req: PayloadRequest): void {
localeOnReq = data.locale
}
if (
!fallbackLocaleOnReq &&
data?.['fallback-locale'] &&
typeof data?.['fallback-locale'] === 'string'
) {
fallbackLocaleOnReq = data['fallback-locale']
if (!fallbackLocaleOnReq) {
if (data?.['fallback-locale'] && typeof data?.['fallback-locale'] === 'string') {
fallbackLocaleOnReq = data['fallback-locale']
}
if (data?.['fallbackLocale'] && typeof data?.['fallbackLocale'] === 'string') {
fallbackLocaleOnReq = data['fallbackLocale']
}
}
const { fallbackLocale, locale } = sanitizeLocales({
@@ -54,15 +57,19 @@ export const sanitizeLocales = ({
locale,
localization,
}: SanitizeLocalesArgs): SanitizeLocalesReturn => {
if (['none', 'null'].includes(fallbackLocale)) {
fallbackLocale = 'null'
} else if (localization && !localization.localeCodes.includes(fallbackLocale)) {
fallbackLocale = localization.defaultLocale
// Check if localization has fallback enabled or if a fallback locale is provided
if (localization) {
fallbackLocale = sanitizeFallbackLocale({
fallbackLocale,
locale,
localization,
})
}
if (locale === '*') {
locale = 'all'
} else if (localization && !localization.localeCodes.includes(locale)) {
} else if (localization && !localization.localeCodes.includes(locale) && localization.fallback) {
locale = localization.defaultLocale
}

View File

@@ -1,7 +1,7 @@
import type { CustomPayloadRequestProperties, PayloadRequest, SanitizedConfig } from 'payload'
import { initI18n } from '@payloadcms/translations'
import { executeAuthStrategies, getDataLoader, parseCookies } from 'payload'
import { executeAuthStrategies, getDataLoader, parseCookies, sanitizeFallbackLocale } from 'payload'
import * as qs from 'qs-esm'
import { URL } from 'url'
@@ -26,6 +26,7 @@ export const createPayloadRequest = async ({
const payload = await getPayloadHMR({ config: configPromise })
const { config } = payload
const localization = config.localization
const urlProperties = new URL(request.url)
const { pathname, searchParams } = urlProperties
@@ -45,8 +46,10 @@ export const createPayloadRequest = async ({
language,
})
let locale
let fallbackLocale
const fallbackFromRequest =
searchParams.get('fallback-locale') || searchParams.get('fallbackLocale')
let locale = searchParams.get('locale')
let fallbackLocale = fallbackFromRequest
const overrideHttpMethod = request.headers.get('X-HTTP-Method-Override')
const queryToParse = overrideHttpMethod === 'GET' ? await request.text() : urlProperties.search
@@ -59,21 +62,24 @@ export const createPayloadRequest = async ({
})
: {}
if (config.localization) {
const locales = sanitizeLocales({
fallbackLocale: searchParams.get('fallback-locale'),
locale: searchParams.get('locale'),
localization: payload.config.localization,
if (localization) {
fallbackLocale = sanitizeFallbackLocale({
fallbackLocale,
locale,
localization,
})
const locales = sanitizeLocales({
fallbackLocale,
locale,
localization,
})
locale = locales.locale
fallbackLocale = locales.fallbackLocale
// Override if query params are present, in order to respect HTTP method override
if (query.locale) {
locale = query.locale
}
if (query?.['fallback-locale']) {
fallbackLocale = query['fallback-locale']
locale = query.locale as string
}
}

View File

@@ -1,5 +1,7 @@
import type { Payload } from 'payload'
import { sanitizeFallbackLocale } from 'payload'
type GetRequestLocalesArgs = {
data?: Record<string, any>
localization: Exclude<Payload['config']['localization'], false>
@@ -10,7 +12,7 @@ export function getRequestLocales({ data, localization, searchParams }: GetReque
locale: string
} {
let locale = searchParams.get('locale')
let fallbackLocale = searchParams.get('fallback-locale')
let fallbackLocale = searchParams.get('fallback-locale') || searchParams.get('fallbackLocale')
if (data) {
if (data?.locale) {
@@ -19,13 +21,16 @@ export function getRequestLocales({ data, localization, searchParams }: GetReque
if (data?.['fallback-locale']) {
fallbackLocale = data['fallback-locale']
}
if (data?.['fallbackLocale']) {
fallbackLocale = data['fallbackLocale']
}
}
if (fallbackLocale === 'none') {
fallbackLocale = 'null'
} else if (!localization.localeCodes.includes(fallbackLocale)) {
fallbackLocale = localization.defaultLocale
}
fallbackLocale = sanitizeFallbackLocale({
fallbackLocale,
locale,
localization,
})
if (locale === '*') {
locale = 'all'

View File

@@ -44,7 +44,7 @@ export const initPage = async ({
// we get above. Clone the req? We'll look into that eventually.
const req = await createLocalReq(
{
fallbackLocale: null,
fallbackLocale: false,
req: {
headers,
host: headers.get('host'),

View File

@@ -39,7 +39,6 @@ export const initReq = cache(async function (
const req = await createLocalReq(
{
fallbackLocale: 'null',
req: {
headers,
host: headers.get('host'),

View File

@@ -19,6 +19,7 @@ export const LocaleSelector: React.FC<{
options: localeOptions,
}}
onChange={(value: string) => onChange(value)}
path="locale"
/>
)
}

View File

@@ -159,6 +159,7 @@ export const APIViewClient: React.FC = () => {
label: t('version:draft'),
}}
onChange={() => setDraft(!draft)}
path="draft"
/>
)}
<CheckboxField
@@ -167,6 +168,7 @@ export const APIViewClient: React.FC = () => {
label: t('authentication:authenticated'),
}}
onChange={() => setAuthenticated(!authenticated)}
path="authenticated"
/>
</div>
{localeOptions && <LocaleSelector localeOptions={localeOptions} onChange={setLocale} />}
@@ -181,6 +183,7 @@ export const APIViewClient: React.FC = () => {
min: 0,
}}
onChange={(value) => setDepth(value?.toString())}
path="depth"
/>
</div>
</Form>

View File

@@ -36,6 +36,7 @@ export const ToggleTheme: React.FC = () => {
],
}}
onChange={onChange}
path="theme"
value={autoMode ? 'auto' : theme}
/>
)

View File

@@ -54,7 +54,7 @@ export const CreateFirstUserClient: React.FC<{
const controller = new AbortController()
formStateAbortControllerRef.current = controller
const { state } = await getFormState({
const response = await getFormState({
collectionSlug: userSlug,
docPermissions,
docPreferences,
@@ -64,7 +64,9 @@ export const CreateFirstUserClient: React.FC<{
signal: controller.signal,
})
return state
if (response && response.state) {
return response.state
}
},
[userSlug, getFormState, docPermissions, docPreferences],
)
@@ -103,6 +105,7 @@ export const CreateFirstUserClient: React.FC<{
label: t('authentication:newPassword'),
required: true,
}}
path="password"
/>
<ConfirmPasswordField />
<RenderFields

View File

@@ -26,7 +26,7 @@ export const getDocumentData = async ({
collection: collectionSlug,
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
locale: locale?.code,
overrideAccess: false,
user,
@@ -38,7 +38,7 @@ export const getDocumentData = async ({
slug: globalSlug,
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
locale: locale?.code,
overrideAccess: false,
user,

View File

@@ -149,6 +149,7 @@ export const renderDocument = async ({
data: doc,
docPermissions,
docPreferences,
fallbackLocale: false,
globalSlug,
locale: locale?.code,
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
@@ -278,7 +279,7 @@ export const renderDocument = async ({
data: initialData || {},
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
locale: locale?.code,
req,
user,

View File

@@ -86,6 +86,7 @@ export const ForgotPasswordForm: React.FC = () => {
label: t('authentication:username'),
required: true,
}}
path="username"
validate={(value) =>
text(value, {
name: 'username',
@@ -113,6 +114,7 @@ export const ForgotPasswordForm: React.FC = () => {
label: t('general:email'),
required: true,
}}
path="email"
validate={(value) =>
email(value, {
name: 'email',

View File

@@ -1,4 +1,9 @@
import type { ListPreferences, ListViewClientProps } from '@payloadcms/ui'
import type {
ListComponentClientProps,
ListComponentServerProps,
ListPreferences,
ListViewClientProps,
} from '@payloadcms/ui'
import type { AdminViewProps, ListQuery, Where } from 'payload'
import { DefaultListView, HydrateAuthProvider, ListQueryProvider } from '@payloadcms/ui'
@@ -112,7 +117,7 @@ export const renderListView = async (
const page = isNumber(query?.page) ? Number(query.page) : 0
const whereQuery = mergeListSearchAndWhere({
let whereQuery = mergeListSearchAndWhere({
collectionConfig,
search: typeof query?.search === 'string' ? query.search : undefined,
where: (query?.where as Where) || undefined,
@@ -130,11 +135,26 @@ export const renderListView = async (
? collectionConfig.defaultSort
: undefined)
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
const baseListFilter = await collectionConfig.admin.baseListFilter({
limit,
page,
req,
sort,
})
if (baseListFilter) {
whereQuery = {
and: [whereQuery, baseListFilter].filter(Boolean),
}
}
}
const data = await payload.find({
collection: collectionSlug,
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
includeLockStatus: true,
limit,
locale,
@@ -168,25 +188,43 @@ export const renderListView = async (
? collectionConfig.admin.description({ t: i18n.t })
: collectionConfig.admin.description
const listViewSlots = renderListViewSlots({
collectionConfig,
description: staticDescription,
payload,
})
const clientProps: ListViewClientProps = {
...listViewSlots,
const sharedClientProps: ListComponentClientProps = {
collectionSlug,
columnState,
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
listPreferences,
newDocumentURL: formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/create`,
}),
}
const sharedServerProps: ListComponentServerProps = {
collectionConfig,
i18n,
limit,
locale: fullLocale,
params,
payload,
permissions,
searchParams,
user,
}
const listViewSlots = renderListViewSlots({
clientProps: sharedClientProps,
collectionConfig,
description: staticDescription,
payload,
serverProps: sharedServerProps,
})
const clientProps: ListViewClientProps = {
...listViewSlots,
...sharedClientProps,
columnState,
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
listPreferences,
renderedFilters,
Table,
}
@@ -211,19 +249,10 @@ export const renderListView = async (
Fallback={DefaultListView}
importMap={payload.importMap}
serverProps={{
collectionConfig,
collectionSlug,
...sharedServerProps,
data,
i18n,
limit,
listPreferences,
listSearchableFields: collectionConfig.admin.listSearchableFields,
locale: fullLocale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
</ListQueryProvider>

View File

@@ -1,24 +1,35 @@
import type { ListViewSlots } from '@payloadcms/ui'
import type {
ListComponentClientProps,
ListComponentServerProps,
ListViewSlots,
} from '@payloadcms/ui'
import type { Payload, SanitizedCollectionConfig, StaticDescription } from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
export const renderListViewSlots = ({
collectionConfig,
description,
payload,
}: {
type Args = {
clientProps: ListComponentClientProps
collectionConfig: SanitizedCollectionConfig
description?: StaticDescription
payload: Payload
}): ListViewSlots => {
serverProps: ListComponentServerProps
}
export const renderListViewSlots = ({
clientProps,
collectionConfig,
description,
payload,
serverProps,
}: Args): ListViewSlots => {
const result: ListViewSlots = {} as ListViewSlots
if (collectionConfig.admin.components?.afterList) {
result.AfterList = (
<RenderServerComponent
clientProps={clientProps}
Component={collectionConfig.admin.components.afterList}
importMap={payload.importMap}
serverProps={serverProps}
/>
)
}
@@ -26,8 +37,10 @@ export const renderListViewSlots = ({
if (collectionConfig.admin.components?.afterListTable) {
result.AfterListTable = (
<RenderServerComponent
clientProps={clientProps}
Component={collectionConfig.admin.components.afterListTable}
importMap={payload.importMap}
serverProps={serverProps}
/>
)
}
@@ -35,8 +48,10 @@ export const renderListViewSlots = ({
if (collectionConfig.admin.components?.beforeList) {
result.BeforeList = (
<RenderServerComponent
clientProps={clientProps}
Component={collectionConfig.admin.components.beforeList}
importMap={payload.importMap}
serverProps={serverProps}
/>
)
}
@@ -44,8 +59,10 @@ export const renderListViewSlots = ({
if (collectionConfig.admin.components?.beforeListTable) {
result.BeforeListTable = (
<RenderServerComponent
clientProps={clientProps}
Component={collectionConfig.admin.components.beforeListTable}
importMap={payload.importMap}
serverProps={serverProps}
/>
)
}
@@ -55,9 +72,11 @@ export const renderListViewSlots = ({
<RenderServerComponent
clientProps={{
description,
...clientProps,
}}
Component={collectionConfig.admin.components.Description}
importMap={payload.importMap}
serverProps={serverProps}
/>
)
}

View File

@@ -37,7 +37,7 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
collection: collectionConfig.slug,
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
})
}
@@ -46,7 +46,7 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
slug: globalConfig.slug,
depth: 0,
draft: true,
fallbackLocale: null,
fallbackLocale: false,
})
}
} catch (error) {

View File

@@ -25,6 +25,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('general:email'),
required,
}}
path="email"
validate={email}
/>
)
@@ -38,6 +39,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('authentication:username'),
required,
}}
path="username"
validate={username}
/>
)
@@ -51,6 +53,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
label: t('authentication:emailOrUsername'),
required,
}}
path="username"
validate={(value, options) => {
const passesUsername = username(value, options)
const passesEmail = email(

View File

@@ -98,6 +98,7 @@ export const LoginForm: React.FC<{
label: t('general:password'),
required: true,
}}
path="password"
/>
</div>
<Link

View File

@@ -75,28 +75,11 @@ export const ResetPasswordForm: React.FC<Args> = ({ token }) => {
label: i18n.t('authentication:newPassword'),
required: true,
}}
indexPath=""
parentPath=""
parentSchemaPath=""
path="password"
schemaPath={`${userSlug}.password`}
/>
<ConfirmPasswordField />
<HiddenField
field={{
name: 'token',
type: 'text',
admin: {
hidden: true,
},
}}
indexPath=""
parentPath={userSlug}
parentSchemaPath={userSlug}
path="token"
schemaPath={`${userSlug}.token`}
value={token}
/>
<HiddenField path="token" schemaPath={`${userSlug}.token`} value={token} />
</div>
<FormSubmit size="large">{i18n.t('authentication:resetPassword')}</FormSubmit>
</Form>

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/payload-cloud",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.128",
"version": "3.0.0-beta.130",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -1,11 +1,76 @@
import type { I18nClient } from '@payloadcms/translations'
import type { ClientCollectionConfig } from '../../collections/config/client.js'
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
import type { ClientField } from '../../fields/config/client.js'
import type {
ArrayFieldClient,
BlocksFieldClient,
CheckboxFieldClient,
ClientField,
CodeFieldClient,
DateFieldClient,
EmailFieldClient,
Field,
GroupFieldClient,
JSONFieldClient,
NumberFieldClient,
PointFieldClient,
RadioFieldClient,
RelationshipFieldClient,
SelectFieldClient,
TextareaFieldClient,
TextFieldClient,
UploadFieldClient,
} from '../../fields/config/types.js'
import type { Payload } from '../../types/index.js'
export type RowData = Record<string, any>
export type DefaultCellComponentProps<TCellData = any, TField extends ClientField = ClientField> = {
readonly cellData: TCellData
export type DefaultCellComponentProps<
TField extends ClientField = ClientField,
TCellData = undefined,
> = {
readonly cellData: TCellData extends undefined
? TField extends RelationshipFieldClient
? number | Record<string, any> | string
: TField extends NumberFieldClient
? TField['hasMany'] extends true
? number[]
: number
: TField extends TextFieldClient
? TField['hasMany'] extends true
? string[]
: string
: TField extends
| CodeFieldClient
| EmailFieldClient
| JSONFieldClient
| RadioFieldClient
| TextareaFieldClient
? string
: TField extends BlocksFieldClient
? {
[key: string]: any
blockType: string
}[]
: TField extends CheckboxFieldClient
? boolean
: TField extends DateFieldClient
? Date | number | string
: TField extends GroupFieldClient
? Record<string, any>
: TField extends UploadFieldClient
? File | string
: TField extends ArrayFieldClient
? Record<string, unknown>[]
: TField extends SelectFieldClient
? TField['hasMany'] extends true
? string[]
: string
: TField extends PointFieldClient
? { x: number; y: number }
: any
: TCellData
readonly className?: string
readonly collectionConfig: ClientCollectionConfig
readonly columnIndex?: number
@@ -19,3 +84,12 @@ export type DefaultCellComponentProps<TCellData = any, TField extends ClientFiel
}) => void
readonly rowData: RowData
}
export type DefaultServerCellComponentProps<
TField extends ClientField = ClientField,
TCellData = any,
> = {
field: Field
i18n: I18nClient
payload: Payload
} & Omit<DefaultCellComponentProps<TField, TCellData>, 'field'>

View File

@@ -0,0 +1,10 @@
export type NavPreferences = {
groups: NavGroupPreferences
open: boolean
}
export type NavGroupPreferences = {
[key: string]: {
open: boolean
}
}

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,9 @@ import type {
type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'>
type ArrayFieldBaseClientProps = {
readonly path?: string
readonly validate?: ArrayFieldValidation
} & Pick<ServerFieldBase, 'permissions'>
} & FieldPaths &
Pick<ServerFieldBase, 'permissions'>
export type ArrayFieldClientProps = ArrayFieldBaseClientProps &
ClientFieldBase<ArrayFieldClientWithoutType>

View File

@@ -1,11 +1,12 @@
import type { MarkOptional } from 'ts-essentials'
import type { BlocksField, BlocksFieldClient, ClientField } from '../../fields/config/types.js'
import type { BlocksField, BlocksFieldClient } from '../../fields/config/types.js'
import type { BlocksFieldValidation } from '../../fields/validations.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,9 @@ import type {
type BlocksFieldClientWithoutType = MarkOptional<BlocksFieldClient, 'type'>
type BlocksFieldBaseClientProps = {
readonly path?: string
readonly validate?: BlocksFieldValidation
} & Pick<ServerFieldBase, 'permissions'>
} & FieldPaths &
Pick<ServerFieldBase, 'permissions'>
export type BlocksFieldClientProps = BlocksFieldBaseClientProps &
ClientFieldBase<BlocksFieldClientWithoutType>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -24,9 +25,8 @@ type CheckboxFieldBaseClientProps = {
readonly id?: string
readonly onChange?: (value: boolean) => void
readonly partialChecked?: boolean
readonly path?: string
readonly validate?: CheckboxFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type CheckboxFieldClientProps = CheckboxFieldBaseClientProps &
ClientFieldBase<CheckboxFieldClientWithoutType>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -20,9 +21,8 @@ type CodeFieldClientWithoutType = MarkOptional<CodeFieldClient, 'type'>
type CodeFieldBaseClientProps = {
readonly autoComplete?: string
readonly path?: string
readonly validate?: CodeFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type CodeFieldClientProps = ClientFieldBase<CodeFieldClientWithoutType> &
CodeFieldBaseClientProps

View File

@@ -5,6 +5,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -15,9 +16,7 @@ import type {
FieldLabelServerComponent,
} from '../types.js'
type CollapsibleFieldBaseClientProps = {
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
type CollapsibleFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,8 @@ import type {
type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'>
type DateFieldBaseClientProps = {
readonly path?: string
readonly validate?: DateFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type DateFieldClientProps = ClientFieldBase<DateFieldClientWithoutType> &
DateFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,8 @@ import type {
type EmailFieldClientWithoutType = MarkOptional<EmailFieldClient, 'type'>
type EmailFieldBaseClientProps = {
readonly path?: string
readonly validate?: EmailFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type EmailFieldClientProps = ClientFieldBase<EmailFieldClientWithoutType> &
EmailFieldBaseClientProps

View File

@@ -5,6 +5,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -17,9 +18,7 @@ import type {
type GroupFieldClientWithoutType = MarkOptional<GroupFieldClient, 'type'>
export type GroupFieldBaseClientProps = {
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
export type GroupFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
export type GroupFieldClientProps = ClientFieldBase<GroupFieldClientWithoutType> &
GroupFieldBaseClientProps

View File

@@ -1,12 +1,11 @@
import type { ClientField } from '../../fields/config/types.js'
import type { ClientFieldBase } from '../types.js'
import type { ClientFieldBase, FieldPaths } from '../types.js'
type HiddenFieldBaseClientProps = {
readonly disableModifyingForm?: false
readonly field?: {
readonly name?: string
} & ClientField
readonly field?: never
readonly path: string
readonly value?: unknown
}
} & Omit<FieldPaths, 'indexPath'>
export type HiddenFieldProps = ClientFieldBase & HiddenFieldBaseClientProps
export type HiddenFieldProps = HiddenFieldBaseClientProps &
Pick<ClientFieldBase, 'forceRender' | 'schemaPath'>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,8 @@ import type {
type JSONFieldClientWithoutType = MarkOptional<JSONFieldClient, 'type'>
type JSONFieldBaseClientProps = {
readonly path?: string
readonly validate?: JSONFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type JSONFieldClientProps = ClientFieldBase<JSONFieldClientWithoutType> &
JSONFieldBaseClientProps

View File

@@ -5,6 +5,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -17,15 +18,19 @@ import type {
type JoinFieldClientWithoutType = MarkOptional<JoinFieldClient, 'type'>
export type JoinFieldClientProps = {
path?: string
} & ClientFieldBase<JoinFieldClientWithoutType>
type JoinFieldBaseClientProps = Omit<FieldPaths, 'indexPath'>
export type JoinFieldClientProps = ClientFieldBase<JoinFieldClientWithoutType> &
JoinFieldBaseClientProps
export type JoinFieldServerProps = ServerFieldBase<JoinField>
export type JoinFieldServerComponent = FieldServerComponent<JoinField>
export type JoinFieldClientComponent = FieldClientComponent<JoinFieldClientWithoutType>
export type JoinFieldClientComponent = FieldClientComponent<
JoinFieldClientWithoutType,
JoinFieldBaseClientProps
>
export type JoinFieldLabelServerComponent = FieldLabelServerComponent<JoinField>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -20,9 +21,8 @@ type NumberFieldClientWithoutType = MarkOptional<NumberFieldClient, 'type'>
type NumberFieldBaseClientProps = {
readonly onChange?: (e: number) => void
readonly path?: string
readonly validate?: NumberFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type NumberFieldClientProps = ClientFieldBase<NumberFieldClientWithoutType> &
NumberFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,8 @@ import type {
type PointFieldClientWithoutType = MarkOptional<PointFieldClient, 'type'>
type PointFieldBaseClientProps = {
readonly path?: string
readonly validate?: PointFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type PointFieldClientProps = ClientFieldBase<PointFieldClientWithoutType> &
PointFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -24,10 +25,9 @@ type RadioFieldBaseClientProps = {
*/
readonly disableModifyingForm?: boolean
readonly onChange?: OnChange
readonly path?: string
readonly validate?: RadioFieldValidation
readonly value?: string
}
} & Omit<FieldPaths, 'indexPath'>
export type RadioFieldClientProps = ClientFieldBase<RadioFieldClientWithoutType> &
RadioFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -19,9 +20,8 @@ import type {
type RelationshipFieldClientWithoutType = MarkOptional<RelationshipFieldClient, 'type'>
type RelationshipFieldBaseClientProps = {
readonly path?: string
readonly validate?: RelationshipFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type RelationshipFieldClientProps = ClientFieldBase<RelationshipFieldClientWithoutType> &
RelationshipFieldBaseClientProps

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -27,9 +28,8 @@ type RichTextFieldBaseClientProps<
TAdapterProps = any,
TExtraProperties = object,
> = {
readonly path?: string
readonly validate?: RichTextFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type RichTextFieldClientProps<
TValue extends object = any,

View File

@@ -4,6 +4,7 @@ import type { RowField, RowFieldClient } from '../../fields/config/types.js'
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -20,9 +21,10 @@ type RowFieldClientWithoutType = MarkOptional<RowFieldClient, 'type'>
type RowFieldBaseClientProps = {
readonly forceRender?: boolean
} & Pick<ServerFieldBase, 'permissions'>
} & Omit<FieldPaths, 'path'> &
Pick<ServerFieldBase, 'permissions'>
export type RowFieldClientProps = ClientFieldBase<RowFieldClientWithoutType> &
export type RowFieldClientProps = Omit<ClientFieldBase<RowFieldClientWithoutType>, 'path'> &
RowFieldBaseClientProps
export type RowFieldServerProps = ServerFieldBase<RowField, RowFieldClientWithoutType>

View File

@@ -6,6 +6,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -20,10 +21,9 @@ type SelectFieldClientWithoutType = MarkOptional<SelectFieldClient, 'type'>
type SelectFieldBaseClientProps = {
readonly onChange?: (e: string | string[]) => void
readonly path?: string
readonly validate?: SelectFieldValidation
readonly value?: string
}
} & Omit<FieldPaths, 'indexPath'>
export type SelectFieldClientProps = ClientFieldBase<SelectFieldClientWithoutType> &
SelectFieldBaseClientProps

View File

@@ -11,6 +11,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -25,7 +26,7 @@ export type ClientTab =
| ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>)
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
type TabsFieldBaseClientProps = {} & Pick<ServerFieldBase, 'permissions'>
type TabsFieldBaseClientProps = FieldPaths & Pick<ServerFieldBase, 'permissions'>
type TabsFieldClientWithoutType = MarkOptional<TabsFieldClient, 'type'>

View File

@@ -7,6 +7,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -22,9 +23,8 @@ type TextFieldClientWithoutType = MarkOptional<TextFieldClient, 'type'>
type TextFieldBaseClientProps = {
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path?: string
readonly validate?: TextFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type TextFieldClientProps = ClientFieldBase<TextFieldClientWithoutType> &
TextFieldBaseClientProps

View File

@@ -7,6 +7,7 @@ import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../fo
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../forms/Field.js'
@@ -22,9 +23,8 @@ type TextareaFieldClientWithoutType = MarkOptional<TextareaFieldClient, 'type'>
type TextareaFieldBaseClientProps = {
readonly inputRef?: React.Ref<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path?: string
readonly validate?: TextareaFieldValidation
}
} & Omit<FieldPaths, 'indexPath'>
export type TextareaFieldClientProps = ClientFieldBase<TextareaFieldClientWithoutType> &
TextareaFieldBaseClientProps

View File

@@ -1,9 +1,25 @@
import type { MarkOptional } from 'ts-essentials'
import type { UIField, UIFieldClient } from '../../fields/config/types.js'
import type { FieldClientComponent, FieldServerComponent } from '../types.js'
import type {
ClientFieldBase,
FieldClientComponent,
FieldPaths,
FieldServerComponent,
ServerFieldBase,
} from '../types.js'
type UIFieldClientWithoutType = MarkOptional<UIFieldClient, 'type'>
export type UIFieldClientComponent = FieldClientComponent<UIFieldClientWithoutType>
type UIFieldBaseClientProps = Omit<FieldPaths, 'indexPath'>
export type UIFieldClientProps = ClientFieldBase<UIFieldClientWithoutType> & UIFieldBaseClientProps
export type UIFieldServerProps = ServerFieldBase<UIField, UIFieldClientWithoutType>
export type UIFieldClientComponent = FieldClientComponent<
UIFieldClientWithoutType,
UIFieldBaseClientProps
>
export type UIFieldServerComponent = FieldServerComponent<UIField, UIFieldClientWithoutType>

Some files were not shown because too many files have changed in this diff Show More