Compare commits
34 Commits
v3.0.0-bet
...
fix/beta/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa60901385 | ||
|
|
e75527b0a1 | ||
|
|
9fc47cea6e | ||
|
|
5482e7ea15 | ||
|
|
77c99c2f49 | ||
|
|
5ff1bb366c | ||
|
|
e6d04436a8 | ||
|
|
81099cbb04 | ||
|
|
4509c38f4c | ||
|
|
90e6a4fcd8 | ||
|
|
4690cd819a | ||
|
|
afd69c4d54 | ||
|
|
de52490a98 | ||
|
|
129fadfd2c | ||
|
|
cea7d58d96 | ||
|
|
6baff8a3ba | ||
|
|
ced79be591 | ||
|
|
5b9cee67c0 | ||
|
|
bcbca0e44a | ||
|
|
cd95daf029 | ||
|
|
9da85430a5 | ||
|
|
f4d526d6e5 | ||
|
|
3b55458c0d | ||
|
|
51dc3f06b1 | ||
|
|
d6282221db | ||
|
|
f264c8087a | ||
|
|
1b16730b20 | ||
|
|
f6bdc0aaf6 | ||
|
|
a8e3095e45 | ||
|
|
5ac4e73991 | ||
|
|
9ee6425761 | ||
|
|
8c2fc71149 | ||
|
|
88bef2e140 | ||
|
|
a1c99c8b45 |
@@ -7,6 +7,8 @@
|
||||
|
||||
<a href="https://discord.gg/payload"><img alt="Discord" src="https://img.shields.io/discord/967097582721572934?label=Discord&color=7289da&style=flat-square" /></a>
|
||||
|
||||
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/dw/payload?style=flat-square" /></a>
|
||||
|
||||
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/v/payload?style=flat-square" /></a>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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). |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) |
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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`,
|
||||
{
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -106,7 +106,6 @@ const traverseFields = ({
|
||||
switch (field.type) {
|
||||
case 'array':
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
let fieldSelect: SelectType
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
36
packages/next/src/elements/Nav/getNavPrefs.ts
Normal file
36
packages/next/src/elements/Nav/getNavPrefs.ts
Normal 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,
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -39,7 +39,6 @@ export const initReq = cache(async function (
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: 'null',
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
|
||||
@@ -19,6 +19,7 @@ export const LocaleSelector: React.FC<{
|
||||
options: localeOptions,
|
||||
}}
|
||||
onChange={(value: string) => onChange(value)}
|
||||
path="locale"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -36,6 +36,7 @@ export const ToggleTheme: React.FC = () => {
|
||||
],
|
||||
}}
|
||||
onChange={onChange}
|
||||
path="theme"
|
||||
value={autoMode ? 'auto' : theme}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -98,6 +98,7 @@ export const LoginForm: React.FC<{
|
||||
label: t('general:password'),
|
||||
required: true,
|
||||
}}
|
||||
path="password"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'>
|
||||
|
||||
10
packages/payload/src/admin/elements/Nav.ts
Normal file
10
packages/payload/src/admin/elements/Nav.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type NavPreferences = {
|
||||
groups: NavGroupPreferences
|
||||
open: boolean
|
||||
}
|
||||
|
||||
export type NavGroupPreferences = {
|
||||
[key: string]: {
|
||||
open: boolean
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user