Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import type { PropsWithChildren, ReactNode } from 'react'
|
|
|
|
import clsx from 'clsx'
|
|
|
|
type ConsumerFormSectionProps = PropsWithChildren<{
|
|
className?: string
|
|
description?: ReactNode
|
|
title: ReactNode
|
|
variant?: 'surface' | 'embedded'
|
|
}>
|
|
|
|
export const ConsumerFormSection = ({ children, className, description, title, variant = 'surface' }: ConsumerFormSectionProps) => (
|
|
<section className={clsx(variant === 'surface' ? 'consumer-surface p-4 sm:p-6' : 'p-4 sm:p-6', className)}>
|
|
<div className="mb-5 border-b border-consumer-border pb-4">
|
|
<h2 className="text-base font-extrabold text-consumer-text sm:text-lg">{title}</h2>
|
|
{description ? <p className="mt-1 text-xs leading-6 text-secondary-20">{description}</p> : null}
|
|
</div>
|
|
{children}
|
|
</section>
|
|
)
|
|
|
|
type ConsumerFormShellProps = PropsWithChildren<{
|
|
className?: string
|
|
description?: ReactNode
|
|
progress?: ReactNode
|
|
/** Keeps chrome static while the form body owns the vertical scrollport. */
|
|
scrollable?: boolean
|
|
status?: ReactNode
|
|
title?: ReactNode
|
|
utilityAction?: ReactNode
|
|
}>
|
|
|
|
/**
|
|
* Shared visual shell for long, task-oriented consumer forms.
|
|
* Domain forms keep ownership of their fields and validation; the shell only
|
|
* standardises hierarchy, canvas/surface contrast, status and progress.
|
|
*/
|
|
export const ConsumerFormShell = ({
|
|
children,
|
|
className,
|
|
description,
|
|
progress,
|
|
scrollable = false,
|
|
status,
|
|
title,
|
|
utilityAction,
|
|
}: ConsumerFormShellProps) => (
|
|
<div className={clsx('mx-auto w-full max-w-[54rem]', scrollable && 'flex h-full min-h-0 flex-col', className)}>
|
|
{title || status || description || utilityAction ? (
|
|
<div className="mb-4 flex flex-col gap-3 px-1 sm:flex-row sm:items-start sm:justify-between">
|
|
<div className="min-w-0">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{title ? <h1 className="text-lg font-extrabold text-consumer-text sm:text-xl">{title}</h1> : null}
|
|
{status ? (
|
|
<span className="rounded-full border border-primary-100 bg-primary-50 px-3 py-1 text-smt-semibold text-primary-700">
|
|
{status}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
{description ? <p className="mt-1 max-w-2xl text-xs leading-6 text-secondary-20">{description}</p> : null}
|
|
</div>
|
|
{utilityAction ? <div className="shrink-0">{utilityAction}</div> : null}
|
|
</div>
|
|
) : null}
|
|
|
|
<div className={clsx('consumer-surface', scrollable && 'flex min-h-0 flex-1 flex-col overflow-hidden')}>
|
|
{progress ? (
|
|
<div className="overflow-hidden rounded-t-[inherit] border-b border-consumer-border bg-consumer-surface-muted/70 p-4">
|
|
{progress}
|
|
</div>
|
|
) : null}
|
|
{scrollable ? <div className="min-h-0 flex-1 overflow-y-auto">{children}</div> : children}
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
type ConsumerFormActionsProps = PropsWithChildren<{
|
|
className?: string
|
|
helper?: ReactNode
|
|
}>
|
|
|
|
export const ConsumerFormActions = ({ children, className, helper }: ConsumerFormActionsProps) => (
|
|
<div className={clsx('consumer-sticky-action flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between', className)}>
|
|
{helper ? <div className="min-w-0">{helper}</div> : <span />}
|
|
<div className="flex shrink-0 flex-col gap-2 sm:flex-row">{children}</div>
|
|
</div>
|
|
)
|