quality / commitlint (push) Skipped
quality / changes (push) Successful in 9s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 39s
quality / web (push) Successful in 1m29s
quality / go (push) Successful in 1m3s
quality / bird2 (push) Successful in 15s
CD / quality (push) Successful in 3m43s
CD / publish (push) Failing after 8m29s
Единый kitDataGridTableLayout и ResourcePage/FrameDataGrid на всех списках. Календарь задач переведён на EventCalendar, lookup — на cascader, у операций появился вид доски. Удалены settings-7 и самописные DataGridSection/toolbar. Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
|
|
import {
|
|
Frame,
|
|
FrameDescription,
|
|
FrameFooter,
|
|
FrameHeader,
|
|
FramePanel,
|
|
FrameTitle,
|
|
} from '@/components/reui/frame'
|
|
import { cn } from '@evobgp/ui/lib/utils'
|
|
|
|
/**
|
|
* Frame shell for analytics / comparison panels (not list grids).
|
|
* @see https://reui.io/docs/components/base/frame
|
|
* @see https://reui.io/preview/base/chart-1
|
|
*/
|
|
export const panelCardClassName = 'w-full gap-0 p-0'
|
|
export const panelCardHeaderClassName = 'border-b'
|
|
/** Flush body inside FramePanel (toolbar / data-grid). */
|
|
export const panelCardContentFlushClassName = 'p-0'
|
|
/** Horizontal inset for toolbar / pagination rows. */
|
|
export const panelCardInsetClassName = 'px-5 py-3'
|
|
export const panelCardFooterClassName = 'border-t bg-transparent'
|
|
|
|
interface PanelCardProps {
|
|
title?: ReactNode
|
|
description?: ReactNode
|
|
actions?: ReactNode
|
|
footer?: ReactNode
|
|
children?: ReactNode
|
|
className?: string
|
|
headerClassName?: string
|
|
contentClassName?: string
|
|
footerClassName?: string
|
|
size?: 'default' | 'sm'
|
|
}
|
|
|
|
export function PanelCard({
|
|
title,
|
|
description,
|
|
actions,
|
|
footer,
|
|
children,
|
|
className,
|
|
headerClassName,
|
|
contentClassName,
|
|
footerClassName,
|
|
size = 'default',
|
|
}: PanelCardProps) {
|
|
const hasHeader = Boolean(title || description || actions)
|
|
const spacing = size === 'sm' ? 'sm' : 'default'
|
|
|
|
return (
|
|
<Frame
|
|
dense
|
|
spacing={spacing}
|
|
className={cn(panelCardClassName, 'min-w-0', className)}
|
|
>
|
|
<FramePanel className="flex flex-col gap-0 p-0 shadow-xs">
|
|
{hasHeader ? (
|
|
<FrameHeader
|
|
className={cn(
|
|
panelCardHeaderClassName,
|
|
'flex-row items-start justify-between gap-3',
|
|
headerClassName,
|
|
)}
|
|
>
|
|
<div className="flex min-w-0 flex-1 flex-col gap-px">
|
|
{title ? <FrameTitle>{title}</FrameTitle> : null}
|
|
{description ? (
|
|
<FrameDescription>{description}</FrameDescription>
|
|
) : null}
|
|
</div>
|
|
{actions ? (
|
|
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
|
{actions}
|
|
</div>
|
|
) : null}
|
|
</FrameHeader>
|
|
) : null}
|
|
{children != null && children !== false ? (
|
|
<div className={cn('min-w-0 flex-1', contentClassName)}>{children}</div>
|
|
) : null}
|
|
{footer ? (
|
|
<FrameFooter className={cn(panelCardFooterClassName, footerClassName)}>
|
|
{footer}
|
|
</FrameFooter>
|
|
) : null}
|
|
</FramePanel>
|
|
</Frame>
|
|
)
|
|
}
|