Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts",
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
"dev:pattern-library": "pnpm --filter @tools/pattern-library dev",
"dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts",
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod",
"dev:vercel-postgres": "cross-env PAYLOAD_DATABASE=vercel-postgres pnpm runts ./test/dev.ts",
Expand Down
19 changes: 19 additions & 0 deletions packages/ui/src/elements/Banner/Banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'
import React from 'react'

import { Banner } from './index.js'

export const meta = {
description: 'Contextual notification banner for displaying status messages.',
title: 'Elements / Banner',
}

export const Default = () => <Banner type="default">This is a default banner message.</Banner>

export const Info = () => <Banner type="info">Your changes have been saved successfully.</Banner>

export const Success = () => <Banner type="success">Document published successfully.</Banner>

export const Error = () => (
<Banner type="error">An error occurred while saving. Please try again.</Banner>
)
53 changes: 53 additions & 0 deletions packages/ui/src/elements/Banner/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Banner

The Banner component displays contextual notification messages in the Payload admin panel. It supports four semantic type variants (default, info, success, error), an optional icon with configurable alignment, and can function as a plain container, a clickable button, or a navigable link depending on which props are provided.

## Import

```tsx
import { Banner } from '@payloadcms/ui'
```

## Usage

```tsx
<Banner type="info">Your changes have been saved as a draft.</Banner>
```

## Props

| Prop | Type | Default | Description |
| ----------- | --------------------------------------------- | ----------- | ----------------------------------------------------------------------------- |
| `type` | `'default' \| 'info' \| 'success' \| 'error'` | `'default'` | Controls the color and semantic intent of the banner |
| `children` | `ReactNode` | — | Content rendered inside the banner |
| `icon` | `ReactNode` | — | Optional icon element rendered alongside the content |
| `alignIcon` | `'left' \| 'right'` | `'right'` | Side of the banner where the icon is placed (only applies when `icon` is set) |
| `to` | `string` | — | When provided, renders the banner as a `<Link>` navigating to this path |
| `onClick` | `MouseEventHandler` | — | When provided (without `to`), renders the banner as a `<button>` |
| `className` | `string` | — | Additional CSS class names appended to the root element |

## Variants

### Success banner

```tsx
<Banner type="success">Document published successfully.</Banner>
```

### Error banner with left-aligned icon

```tsx
import { XIcon } from '@payloadcms/ui'

;<Banner alignIcon="left" icon={<XIcon />} type="error">
Failed to connect to the database. Please check your connection settings.
</Banner>
```

### Linked banner acting as a call-to-action

```tsx
<Banner to="/admin/collections/posts" type="info">
You have 3 posts awaiting review. Click here to view them.
</Banner>
```
38 changes: 38 additions & 0 deletions packages/ui/src/elements/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'
import React from 'react'

import { Button } from './index.js'

export const meta = {
description:
'Primary action trigger used throughout the admin panel for forms, dialogs, and navigation.',
title: 'Elements / Button',
}

export const Primary = () => <Button buttonStyle="primary">Save changes</Button>

export const Secondary = () => <Button buttonStyle="secondary">Cancel</Button>

export const Subtle = () => <Button buttonStyle="subtle">View details</Button>

export const Destructive = () => <Button buttonStyle="destructive">Delete document</Button>

export const Ghost = () => <Button buttonStyle="ghost">Ghost action</Button>

export const Disabled = () => (
<Button buttonStyle="primary" disabled>
Disabled
</Button>
)

export const Large = () => (
<Button buttonStyle="primary" size="large">
Large button
</Button>
)

export const WithIcon = () => (
<Button buttonStyle="primary" icon="plus">
Add item
</Button>
)
64 changes: 64 additions & 0 deletions packages/ui/src/elements/Button/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Button

The Button component is the primary action trigger in the Payload admin panel. It handles navigation, form submission, and destructive operations.

## Import

```tsx
import { Button } from '@payloadcms/ui'
```

## Usage

```tsx
<Button buttonStyle="primary" onClick={handleSave}>
Save Document
</Button>
```

## Variants

| Style | Use case |
| ------------- | ---------------------------------- |
| `primary` | Main call-to-action (Save, Submit) |
| `secondary` | Secondary actions (Cancel, Back) |
| `subtle` | Low-emphasis actions |
| `destructive` | Irreversible actions (Delete) |
| `ghost` | Minimal visual weight |
| `pill` | Tag-style buttons |

## Props

| Prop | Type | Default | Description |
| ------------- | --------------------- | ----------- | -------------------------- |
| `buttonStyle` | string | `'primary'` | Visual style variant |
| `size` | `'medium' \| 'large'` | `'medium'` | Button size |
| `disabled` | boolean | `false` | Disables interaction |
| `icon` | ReactNode | — | Icon shown alongside label |
| `onClick` | function | — | Click handler |

## Examples

### Primary action

```tsx
<Button buttonStyle="primary" onClick={handleSubmit}>
Publish
</Button>
```

### Destructive action

```tsx
<Button buttonStyle="destructive" onClick={handleDelete}>
Delete
</Button>
```

### Large secondary button

```tsx
<Button buttonStyle="secondary" size="large" onClick={handleCancel}>
Cancel
</Button>
```
63 changes: 63 additions & 0 deletions packages/ui/src/fields/Checkbox/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'
import React, { useState } from 'react'

import { CheckboxInput } from './Input.js'

export const meta = {
description: 'Boolean toggle rendered as a styled checkbox.',
title: 'Fields / Checkbox',
}

export const Unchecked = () => {
const [checked, setChecked] = useState(false)
return (
<CheckboxInput
checked={checked}
label="Enable feature"
name="enableFeature"
onToggle={() => setChecked((prev) => !prev)}
/>
)
}

export const Checked = () => {
const [checked, setChecked] = useState(true)
return (
<CheckboxInput
checked={checked}
label="Enable feature"
name="enableFeature"
onToggle={() => setChecked((prev) => !prev)}
/>
)
}

export const PartiallyChecked = () => {
const [checked, setChecked] = useState(false)
return (
<CheckboxInput
checked={checked}
label="Select all"
name="selectAll"
onToggle={() => setChecked((prev) => !prev)}
partialChecked
/>
)
}

export const ReadOnly = () => (
<CheckboxInput checked label="Read-only option" name="readOnly" onToggle={() => {}} readOnly />
)

export const Required = () => {
const [checked, setChecked] = useState(false)
return (
<CheckboxInput
checked={checked}
label="I agree to the terms"
name="agreeToTerms"
onToggle={() => setChecked((prev) => !prev)}
required
/>
)
}
82 changes: 82 additions & 0 deletions packages/ui/src/fields/Checkbox/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# CheckboxInput

The `CheckboxInput` component is the presentational layer for the Payload checkbox field. It renders a styled checkbox with a custom check/indeterminate icon, an associated label, and optional slots for content before and after the input. Use this component directly when you need a standalone checkbox outside of a Payload form context. The connected form component is `CheckboxField`.

## Import

```tsx
import { CheckboxInput } from '@payloadcms/ui'
```

## Usage

```tsx
<CheckboxInput
checked={isActive}
label="Active"
name="active"
onToggle={(e) => setIsActive(e.target.checked)}
/>
```

## Props

| Prop | Type | Default | Description |
| ---------------- | ------------------------------------------------------ | ------- | ---------------------------------------------------------------------------------- |
| `onToggle` | `(event: React.ChangeEvent<HTMLInputElement>) => void` | — | **Required.** Called when the checkbox state changes |
| `checked` | `boolean` | — | Whether the checkbox is checked |
| `partialChecked` | `boolean` | — | When `true` and `checked` is `false`, renders the indeterminate (`LineIcon`) state |
| `label` | `string \| Record<string, string>` | — | Label text rendered next to the checkbox via `FieldLabel` |
| `name` | `string` | — | Native `name` attribute; also used for `aria-labelledby` and `title` |
| `id` | `string` | — | Native `id` for the `<input>`; auto-generated via `useId` if omitted |
| `readOnly` | `boolean` | — | Disables the checkbox; also forced `true` before client hydration |
| `required` | `boolean` | — | Marks the field as required |
| `localized` | `boolean` | — | Shows a localization indicator on the label |
| `className` | `string` | — | Additional CSS class names for the root wrapper |
| `inputRef` | `React.RefObject<HTMLInputElement \| null>` | — | Ref forwarded to the underlying `<input>` element |
| `BeforeInput` | `ReactNode` | — | Slot rendered before the checkbox input group |
| `AfterInput` | `ReactNode` | — | Slot rendered after the checkbox input group |
| `Label` | `ReactNode` | — | Custom label component; overrides the default `FieldLabel` |
| `Error` | `ReactNode` | — | Custom error component rendered inside the input group |

## Variants

### Basic controlled checkbox

```tsx
const [accepted, setAccepted] = React.useState(false)

<CheckboxInput
checked={accepted}
label="I accept the terms and conditions"
name="acceptTerms"
required
onToggle={(e) => setAccepted(e.target.checked)}
/>
```

### Indeterminate (partial) state

Used for "select all" controls where only some child items are selected:

```tsx
<CheckboxInput
checked={false}
label="Select all"
name="selectAll"
partialChecked={someSelected}
onToggle={handleSelectAll}
/>
```

### Read-only display

```tsx
<CheckboxInput
checked
label="Email notifications enabled"
name="emailNotifications"
readOnly
onToggle={() => {}}
/>
```
Loading
Loading