کشوی موجودیت با نشانی (EntityDrawer)
پنل کناری یک ردیف که موجودیت بازش در نشانی صفحه است — پیوند مستقیم، بازگشت مرورگر، J و K برای ردیف بعد و قبل، Esc و برگشت فوکوس
معرفی
در یک کنسول عملیات، کاربر ردیفها را یکییکی از خود فهرست بررسی میکند: حسابهای یک ناوگان، کارهای یک صف،
درخواستهای یک خرید. هر ردیف جزئیات دارد ولی صفحهٔ جدا لازم ندارد — کاربر میخواهد کنار فهرست بماند و با صفحهکلید
ردیف بعد را ببیند. useEntityDrawer و EntityDrawer الگوی تأییدشدهٔ همین کارند:
- موجودیت باز در نشانی است (
?account=a-12): پیوند مستقیم آن را باز میکند، «بازگشت» مرورگر میبندد؛ - J ردیف بعد و K ردیف قبل (کلیدهای فیزیکی، با هر زبان صفحهکلید)، با «3 از 25» و دکمههای قبلی/بعدی؛
- Esc میبندد و فوکوس به ردیف موجودیتی برمیگردد که آخر دیده شد؛
- بدنه حالت خودش را دارد (
state)، و پاورقی اقدامهای خودش را — کشو ناحیهٔ مستقلی است با یک اقدام اصلی خودش (یاDecisionActionsبرای تأیید و رد).
کشو، صفحه یا دیالوگ؟
| نیاز | پاسخ |
|---|---|
| موجودیت بخشها و زبانههایی با نشانی خودشان دارد، یا در منو جایی دارد | DetailPage |
| موجودیت از فهرستش بررسی میشود، کاربر ردیفها را پشت هم میبیند، پیوند مستقیم لازم است | EntityDrawer + useEntityDrawer |
| کار کوتاه دو سه فیلدی یا تأیید | Dialog |
استفاده
'use client'
import * as React from 'react'
import { Button, DataTable, useAsync } from '@partodata/ui'
import { EntityDrawer, ListPage, pageState, useEntityDrawer } from '@partodata/ui/templates'
type Account = { id: string; name: string; status: string }
async function getAccounts(): Promise<Account[]> {
const response = await fetch('/api/accounts')
if (!response.ok) throw new Error(`accounts: ${response.status}`)
return response.json()
}
async function getAccount(id: string): Promise<Account> {
const response = await fetch(`/api/accounts/${id}`)
if (!response.ok) throw new Error(`account: ${response.status}`)
return response.json()
}
export function AccountsScreen() {
const list = useAsync<Account[]>()
const loadList = React.useCallback(() => list.run(() => getAccounts()), [list.run])
React.useEffect(() => {
loadList()
}, [loadList])
const rows = list.data ?? []
const drawer = useEntityDrawer({ param: 'account', ids: rows.map((row) => row.id) })
const account = useAsync<Account>()
const runAccount = account.run
const loadAccount = React.useCallback(() => {
if (drawer.openId) runAccount(() => getAccount(drawer.openId!))
}, [runAccount, drawer.openId])
React.useEffect(() => {
loadAccount()
}, [loadAccount])
const columns = [
{
id: 'name',
header: 'حساب',
cell: (row: Account) => (
<Button variant="link" {...drawer.triggerProps(row.id)}>
{row.name}
</Button>
),
},
{ id: 'status', header: 'وضعیت', cell: (row: Account) => row.status },
]
return (
<ListPage
title="حسابها"
state={pageState({
data: list.data,
isLoading: list.isLoading,
error: list.error,
onRetry: loadList,
emptyCopy: { title: 'هنوز حسابی افزوده نشده است' },
})}
>
<DataTable columns={columns} data={rows} />
<EntityDrawer
drawer={drawer}
title={rows.find((row) => row.id === drawer.openId)?.name ?? 'حساب'}
state={pageState({
data: account.data,
isLoading: account.isLoading,
error: account.error,
onRetry: loadAccount,
})}
>
<p>{account.data?.status}</p>
</EntityDrawer>
</ListPage>
)
}paramیک واژهٔ کوچک است که موجودیت را نام میبرد؛ صفحه هرگز خودش آن را نمیخواند یا نمینویسد (نهuseSearchParams، نهrouter.push('?…')).idsشناسهٔ ردیفهای روی صفحه است، به همان ترتیب: J و K میان همینها جابهجا میشوند.- باز کردن یک ورودی تاریخچه میسازد (بازگشت مرورگر میبندد)؛ J و K همان ورودی را عوض میکنند (بازگشت باز هم میبندد، نه ردیفبهردیف عقب میرود)؛ پیوند مستقیمی که باز رسیده با بستن فقط پارامتر را برمیدارد.
- عنوان کشو نام موجودیت است؛ تا بارگذاری شود، نامش از ردیف (یا نوعش: «حساب»).
حالتها و انواع
size | عرض |
|---|---|
default | نیمی از صفحه از lg |
wide | دو سوم صفحه، برای موجودیتی که بدنهاش یک جدول است |
راهنمای استفاده
بکنید
- دکمهٔ باز کردن ردیف را
Button variant="link"با{...drawer.triggerProps(row.id)}کنید؛ همانجا فوکوس برمیگردد. - اقدام اصلی کشو را در
primaryActionخودش بدهید؛ تأیید و رد یکDecisionActionsاست.
نکنید
- موجودیت باز را خودتان در نشانی ننویسید یا از آن نخوانید.
- کشو را به صفحه تبدیل نکنید فقط برای اینکه نشانی داشته باشد: نشانی را همین الگو میدهد.
- کلیدهای میانبر J و K را خودتان روی صفحه نبندید.
Props
UseEntityDrawerOptions
EntityDrawer
دسترسیپذیری
- کشو یک دیالوگ با نام موجودیت است؛ فوکوس در آن میماند و Esc میبندد.
- دکمههای قبلی و بعدی نام دارند و میانبرشان را با
aria-keyshortcuts(K و J) میگویند؛ در انتهای فهرست غیرفعالاند ولی فوکوس را نگه میدارند (aria-disabled). J و K در فیلدی که تایپ میشود حرفاند، نه میانبر. - دکمهٔ باز کردن ردیف
aria-haspopup="dialog"وaria-expandedدارد؛ با بستن، فوکوس به ردیفی برمیگردد که آخر دیده شد (پس از J و K، ردیف تازه).
کامپوننتهای مرتبط
Sheet— پنل کناریای که کشو روی آن ساخته شده است.DecisionActions— تأیید و رد در پاورقی کشو.ListPage— فهرستی که کشو از آن باز میشود.
قالب جریان چندمرحلهای (WizardPage)
جریانی که در چند مرحله یک چیز میسازد — نشانگر مراحل، «قبلی» و «بعدی» با اعتبارسنجی هر مرحله، مرحلهٔ بازبینی، و مرحله در نشانی
سربرگ صفحه (PageHeader)
جزء سطح پایینی که سرِ صفحهٔ هر قالب صفحه را میسازد — عنوان (تنها h1 صفحه)، توضیح، راه بازگشت، اقدام اصلی و اقدامهای دیگر؛ صفحهٔ محصول آن را نمینویسد، قالبش رندرش میکند.