mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
454 lines
18 KiB
Plaintext
454 lines
18 KiB
Plaintext
---
|
|
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
|
import DocsLink from '@ui/DocsLink.astro'
|
|
import Avatar from '@ui/Avatar.astro'
|
|
import Card from '@ui/Card.astro'
|
|
import CardHeader from '@ui/CardHeader.astro'
|
|
import CardBody from '@ui/CardBody.astro'
|
|
import CardTitle from '@ui/CardTitle.astro'
|
|
import CardSubtitle from '@ui/CardSubtitle.astro'
|
|
import Icon from '@ui/Icon.astro'
|
|
import ListGroup from '@ui/ListGroup.astro'
|
|
import ListGroupItem from '@ui/ListGroupItem.astro'
|
|
import Signal from '@ui/Signal.astro'
|
|
import { people, personById } from '@shared/lib/people'
|
|
import tasks from '@data/tasks.json'
|
|
|
|
interface Task {
|
|
name: string
|
|
assigned_to: number
|
|
priority: string
|
|
}
|
|
|
|
interface Column {
|
|
name: string
|
|
tasks: Task[]
|
|
}
|
|
|
|
const priorities: Record<string, { value: number; color: string }> = {
|
|
High: { value: 3, color: 'red' },
|
|
Medium: { value: 2, color: 'orange' },
|
|
Low: { value: 1, color: 'blue' },
|
|
}
|
|
|
|
const board = (tasks.columns as Column[]).map((column) => ({
|
|
name: column.name,
|
|
tasks: column.tasks.slice(0, 4),
|
|
}))
|
|
|
|
const selectedTopics = ['Design', 'Development', 'Marketing']
|
|
const availableTopics = ['Sales', 'Support', 'Analytics', 'Legal', 'Finance', 'Research', 'Security', 'Hiring']
|
|
|
|
const team = people.slice(0, 5)
|
|
const gallery = people.slice(5, 11)
|
|
|
|
const sections = [
|
|
{ title: 'Basics', description: 'Add data-sortable with SortableJS options as JSON to any list to make its children draggable.' },
|
|
{ title: 'Handles and tables', description: 'Limit dragging to a grip icon, or reorder the rows of a table.' },
|
|
{ title: 'Boards', description: 'Connect several lists to build a kanban board, a pick list or a nested tree.' },
|
|
{ title: 'Selection and swapping', description: 'The bundled SortableJS build also ships the MultiDrag and Swap plugins.' },
|
|
]
|
|
---
|
|
|
|
<DefaultLayout title="Sortable" pageMenu="plugins.sortable" pageLibs={['sortablejs']} description="Drag and drop to reorder lists, table rows, grids and kanban boards with SortableJS.">
|
|
<DocsLink slot="page-header-actions" path="/ui/plugins/sortable" />
|
|
<div class="row row-cards">
|
|
<div class="col-12">
|
|
<h2 class="page-section-title">{sections[0]!.title}</h2>
|
|
<p class="page-section-description">{sections[0]!.description}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Basic usage</CardTitle>
|
|
<CardSubtitle>Drag to reorder items within a single list.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"animation":150}'>
|
|
<ListGroupItem as="li">Element A</ListGroupItem>
|
|
<ListGroupItem as="li">Element B</ListGroupItem>
|
|
<ListGroupItem as="li">Element C</ListGroupItem>
|
|
<ListGroupItem as="li">Element D</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Two lists with drag and drop</CardTitle>
|
|
<CardSubtitle>Give lists the same <code>group</code> to move items between them.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="row g-3">
|
|
<div class="col-sm-6">
|
|
<ListGroup as="ul" data-sortable='{"group":"shared","animation":150}'>
|
|
<ListGroupItem as="li">Item 1</ListGroupItem>
|
|
<ListGroupItem as="li">Item 2</ListGroupItem>
|
|
<ListGroupItem as="li">Item 3</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<ListGroup as="ul" data-sortable='{"group":"shared","animation":150}'>
|
|
<ListGroupItem as="li">Item A</ListGroupItem>
|
|
<ListGroupItem as="li">Item B</ListGroupItem>
|
|
<ListGroupItem as="li">Item C</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Horizontal lists</CardTitle>
|
|
<CardSubtitle>Set <code>direction</code> to <code>horizontal</code> for rows of tags, and drag them between the two rows.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="subheader mb-2">Selected</div>
|
|
<div class="tag-list mb-4" data-sortable='{"group":"topics","animation":150,"direction":"horizontal","emptyInsertThreshold":40}'>
|
|
{selectedTopics.map((topic) => <span class="tag cursor-move">{topic}</span>)}
|
|
</div>
|
|
<div class="subheader mb-2">Available</div>
|
|
<div class="tag-list" data-sortable='{"group":"topics","animation":150,"direction":"horizontal","emptyInsertThreshold":40}'>
|
|
{availableTopics.map((topic) => <span class="tag cursor-move">{topic}</span>)}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Fixed items</CardTitle>
|
|
<CardSubtitle>Set <code>filter</code> to a selector to make matching items impossible to drag.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"animation":150,"filter":".sortable-fixed"}'>
|
|
<ListGroupItem as="li" class="sortable-fixed text-secondary d-flex align-items-center gap-2">
|
|
<Icon name="lock" /> Inbox (pinned)
|
|
</ListGroupItem>
|
|
<ListGroupItem as="li">Projects</ListGroupItem>
|
|
<ListGroupItem as="li">Reports</ListGroupItem>
|
|
<ListGroupItem as="li">Archive</ListGroupItem>
|
|
<ListGroupItem as="li" class="sortable-fixed text-secondary d-flex align-items-center gap-2">
|
|
<Icon name="lock" /> Trash (pinned)
|
|
</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<h2 class="page-section-title">{sections[1]!.title}</h2>
|
|
<p class="page-section-description">{sections[1]!.description}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Drag handle</CardTitle>
|
|
<CardSubtitle>Set <code>handle</code> so only the grip starts a drag, and the rest of the row stays clickable.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"animation":150,"handle":".sortable-handle"}'>
|
|
{
|
|
team.map((person) => (
|
|
<ListGroupItem as="li">
|
|
<div class="row align-items-center g-3">
|
|
<div class="col-auto">
|
|
<span class="sortable-handle cursor-move text-secondary" aria-hidden="true">
|
|
<Icon name="grip-vertical" />
|
|
</span>
|
|
</div>
|
|
<div class="col-auto">
|
|
<Avatar person={person} />
|
|
</div>
|
|
<div class="col text-truncate">
|
|
<a href="#" class="text-reset d-block text-truncate">
|
|
{person.full_name}
|
|
</a>
|
|
<div class="text-secondary text-truncate">{person.job_title}</div>
|
|
</div>
|
|
</div>
|
|
</ListGroupItem>
|
|
))
|
|
}
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Sortable table</CardTitle>
|
|
<CardSubtitle>Put <code>data-sortable</code> on the <code>tbody</code> to reorder rows.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<div class="table-responsive">
|
|
<table class="table table-vcenter card-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="w-1"><span class="visually-hidden">Drag</span></th>
|
|
<th>Name</th>
|
|
<th>Title</th>
|
|
<th>Country</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-sortable='{"animation":150,"handle":".sortable-handle"}'>
|
|
{
|
|
team.map((person) => (
|
|
<tr>
|
|
<td>
|
|
<span class="sortable-handle cursor-move text-secondary" aria-hidden="true">
|
|
<Icon name="grip-vertical" />
|
|
</span>
|
|
</td>
|
|
<td>{person.full_name}</td>
|
|
<td class="text-secondary">{person.job_title}</td>
|
|
<td class="text-secondary">{person.country}</td>
|
|
</tr>
|
|
))
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Grid of avatars</CardTitle>
|
|
<CardSubtitle>Sortable also works on a wrapping layout, not just on vertical lists.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="d-flex flex-wrap gap-3" data-sortable='{"animation":150}'>
|
|
{
|
|
gallery.map((person) => (
|
|
<div class="card card-sm cursor-move flex-fill">
|
|
<div class="card-body text-center">
|
|
<Avatar person={person} size="lg" class="mb-2" />
|
|
<div class="text-truncate">{person.first_name}</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Disabled list</CardTitle>
|
|
<CardSubtitle>Set <code>disabled</code> to <code>true</code> to lock a list, for example while the user has no edit rights.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"animation":150,"disabled":true}'>
|
|
<ListGroupItem as="li" class="d-flex align-items-center gap-2">
|
|
<Icon name="lock" class="text-secondary" /> Read-only item 1
|
|
</ListGroupItem>
|
|
<ListGroupItem as="li" class="d-flex align-items-center gap-2">
|
|
<Icon name="lock" class="text-secondary" /> Read-only item 2
|
|
</ListGroupItem>
|
|
<ListGroupItem as="li" class="d-flex align-items-center gap-2">
|
|
<Icon name="lock" class="text-secondary" /> Read-only item 3
|
|
</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<h2 class="page-section-title">{sections[2]!.title}</h2>
|
|
<p class="page-section-description">{sections[2]!.description}</p>
|
|
</div>
|
|
<div class="col-12">
|
|
<Card>
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Kanban board</CardTitle>
|
|
<CardSubtitle>Drag tasks between columns that share one <code>group</code>. Items are dragged as a tilted copy with a shadow. Set <code>forceFallback</code> to <code>false</code> to use the browser preview instead.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="row g-3">
|
|
{
|
|
board.map((column) => (
|
|
<div class="col-md-4">
|
|
<h4 class="mb-2">{column.name}</h4>
|
|
<div class="d-flex flex-column gap-2" data-sortable='{"group":"kanban","animation":150,"emptyInsertThreshold":40}'>
|
|
{column.tasks.map((task) => (
|
|
<div class="card card-sm cursor-move">
|
|
<div class="card-body">
|
|
<div class="mb-3">{task.name}</div>
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<span class="d-flex align-items-center gap-2 text-secondary">
|
|
<Signal value={priorities[task.priority]?.value ?? 0} color={priorities[task.priority]?.color} />
|
|
{task.priority}
|
|
</span>
|
|
<Avatar person={personById(task.assigned_to)} size="xs" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))
|
|
}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6 col-xl-4">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Clone from a list</CardTitle>
|
|
<CardSubtitle>Use <code>pull: "clone"</code> and <code>put: false</code> on the source so items are copied, not moved.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="row g-3">
|
|
<div class="col-sm-6">
|
|
<div class="subheader mb-2">Templates</div>
|
|
<ListGroup as="ul" data-sortable='{"group":{"name":"clone","pull":"clone","put":false},"sort":false,"animation":150}'>
|
|
<ListGroupItem as="li">Heading</ListGroupItem>
|
|
<ListGroupItem as="li">Paragraph</ListGroupItem>
|
|
<ListGroupItem as="li">Image</ListGroupItem>
|
|
<ListGroupItem as="li">Button</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="subheader mb-2">Page</div>
|
|
<ListGroup as="ul" data-sortable='{"group":"clone","animation":150}'>
|
|
<ListGroupItem as="li">Paragraph</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6 col-xl-4">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">One-way transfer</CardTitle>
|
|
<CardSubtitle>Set <code>put: false</code> on a list to make it a source only, and use it to build a pick list.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div class="row g-3">
|
|
<div class="col-sm-6">
|
|
<div class="subheader mb-2">Available</div>
|
|
<ListGroup as="ul" data-sortable='{"group":{"name":"transfer","put":false},"sort":false,"animation":150}'>
|
|
<ListGroupItem as="li">Email alerts</ListGroupItem>
|
|
<ListGroupItem as="li">SMS alerts</ListGroupItem>
|
|
<ListGroupItem as="li">Weekly digest</ListGroupItem>
|
|
<ListGroupItem as="li">Push notifications</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="subheader mb-2">Selected</div>
|
|
<ListGroup as="ul" data-sortable='{"group":"transfer","animation":150}'>
|
|
<ListGroupItem as="li">In-app messages</ListGroupItem>
|
|
</ListGroup>
|
|
</div>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-12 col-xl-4">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Nested lists</CardTitle>
|
|
<CardSubtitle>Put a sortable list inside an item and give all lists one <code>group</code> to move items between levels.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"group":"nested","animation":150,"fallbackOnBody":true,"swapThreshold":0.65}'>
|
|
<ListGroupItem as="li">
|
|
<div>Design</div>
|
|
<ListGroup as="ul" class="mt-2" data-sortable='{"group":"nested","animation":150,"fallbackOnBody":true,"swapThreshold":0.65}'>
|
|
<ListGroupItem as="li">Wireframes</ListGroupItem>
|
|
<ListGroupItem as="li">Prototype</ListGroupItem>
|
|
</ListGroup>
|
|
</ListGroupItem>
|
|
<ListGroupItem as="li">
|
|
<div>Development</div>
|
|
<ListGroup as="ul" class="mt-2" data-sortable='{"group":"nested","animation":150,"fallbackOnBody":true,"swapThreshold":0.65}'>
|
|
<ListGroupItem as="li">Frontend</ListGroupItem>
|
|
<ListGroupItem as="li">Backend</ListGroupItem>
|
|
<ListGroupItem as="li">Tests</ListGroupItem>
|
|
</ListGroup>
|
|
</ListGroupItem>
|
|
<ListGroupItem as="li">
|
|
<div>Launch</div>
|
|
<ListGroup as="ul" class="mt-2" data-sortable='{"group":"nested","animation":150,"fallbackOnBody":true,"swapThreshold":0.65}'>
|
|
<ListGroupItem as="li">Release notes</ListGroupItem>
|
|
</ListGroup>
|
|
</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<h2 class="page-section-title">{sections[3]!.title}</h2>
|
|
<p class="page-section-description">{sections[3]!.description}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Multiple selection</CardTitle>
|
|
<CardSubtitle>Set <code>multiDrag</code> to <code>true</code>. Hold <kbd>Ctrl</kbd> or <kbd>⌘</kbd> and click to select items, then drag them together.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"multiDrag":true,"animation":150}'>
|
|
<ListGroupItem as="li">Invoice #1001</ListGroupItem>
|
|
<ListGroupItem as="li">Invoice #1002</ListGroupItem>
|
|
<ListGroupItem as="li">Invoice #1003</ListGroupItem>
|
|
<ListGroupItem as="li">Invoice #1004</ListGroupItem>
|
|
<ListGroupItem as="li">Invoice #1005</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<Card class="h-100">
|
|
<CardHeader>
|
|
<div>
|
|
<CardTitle as="h3">Swap</CardTitle>
|
|
<CardSubtitle>Set <code>swap</code> to <code>true</code> to exchange two items instead of shifting the rest.</CardSubtitle>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ListGroup as="ul" data-sortable='{"swap":true,"animation":150}'>
|
|
<ListGroupItem as="li">First place</ListGroupItem>
|
|
<ListGroupItem as="li">Second place</ListGroupItem>
|
|
<ListGroupItem as="li">Third place</ListGroupItem>
|
|
<ListGroupItem as="li">Fourth place</ListGroupItem>
|
|
</ListGroup>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</DefaultLayout>
|