admin/components/heroui/SelectionControls.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

143 lines
4.1 KiB
TypeScript

'use client'
import type { ComponentProps, ReactNode } from 'react'
import {
Description,
FieldError,
Label,
Checkbox as HeroCheckbox,
Radio as HeroRadio,
RadioGroup as HeroRadioGroup,
Switch as HeroSwitch,
} from '@heroui/react'
import { cn } from '@/lib/cn'
interface ControlClassNames {
base?: string
label?: string
wrapper?: string
}
type CheckboxProps = Omit<ComponentProps<typeof HeroCheckbox>, 'children' | 'className' | 'onChange' | 'size'> & {
children?: ReactNode
className?: string
classNames?: ControlClassNames
color?: string
isInvalid?: boolean
size?: 'sm' | 'md' | 'lg'
onValueChange?: (selected: boolean) => void
}
export function Checkbox({ children, className, classNames, color: _color, isInvalid, size, onValueChange, ...props }: CheckboxProps) {
return (
<HeroCheckbox
{...props}
className={cn(classNames?.base, className)}
isInvalid={isInvalid}
onChange={onValueChange}
>
<HeroCheckbox.Content className={cn('flex items-center gap-2', size === 'lg' && 'text-lg')}>
<HeroCheckbox.Control className={classNames?.wrapper}>
<HeroCheckbox.Indicator />
</HeroCheckbox.Control>
<span className={classNames?.label}>{children}</span>
</HeroCheckbox.Content>
</HeroCheckbox>
)
}
type SwitchProps = Omit<ComponentProps<typeof HeroSwitch>, 'children' | 'className' | 'onChange'> & {
children?: ReactNode
className?: string
classNames?: ControlClassNames
size?: 'sm' | 'md' | 'lg'
onValueChange?: (selected: boolean) => void
}
export function Switch({ children, className, classNames, size, onValueChange, ...props }: SwitchProps) {
return (
<HeroSwitch
{...props}
className={cn(classNames?.base, className)}
size={size}
onChange={onValueChange}
>
<HeroSwitch.Content className="flex items-center gap-2">
<HeroSwitch.Control className={classNames?.wrapper}>
<HeroSwitch.Thumb />
</HeroSwitch.Control>
<span className={classNames?.label}>{children}</span>
</HeroSwitch.Content>
</HeroSwitch>
)
}
type RadioGroupProps = Omit<ComponentProps<typeof HeroRadioGroup>, 'children' | 'className' | 'onChange' | 'orientation'> & {
children?: ReactNode
className?: string
classNames?: ControlClassNames
description?: ReactNode
descriptionClassName?: string
errorMessage?: ReactNode
label?: ReactNode
orientation?: 'horizontal' | 'vertical'
size?: 'sm' | 'md' | 'lg'
onValueChange?: (value: string) => void
}
export function RadioGroup({
children,
className,
classNames,
description,
descriptionClassName,
errorMessage,
isInvalid,
label,
orientation = 'vertical',
size: _size,
onValueChange,
...props
}: RadioGroupProps) {
return (
<HeroRadioGroup
{...props}
// !flex-col: HeroUI روی orientation=horizontal کل گروه را ردیفی می‌کند و لیبل کنار گزینه‌ها می‌افتد
className={cn('flex !flex-col gap-2', className)}
isInvalid={isInvalid}
orientation={orientation}
onChange={onValueChange}
>
{label ? <Label className={classNames?.label}>{label}</Label> : null}
<div className={cn('flex gap-4', orientation === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col')}>{children}</div>
{description ? <Description className={descriptionClassName}>{description}</Description> : null}
{isInvalid && errorMessage ? <FieldError>{errorMessage}</FieldError> : null}
</HeroRadioGroup>
)
}
type RadioProps = Omit<ComponentProps<typeof HeroRadio>, 'children' | 'className'> & {
children?: ReactNode
className?: string
classNames?: ControlClassNames
size?: 'sm' | 'md' | 'lg'
}
export function Radio({ children, className, classNames, size: _size, ...props }: RadioProps) {
return (
<HeroRadio
{...props}
className={cn(classNames?.base, className)}
>
<HeroRadio.Content className="flex items-center gap-2">
<HeroRadio.Control className={classNames?.wrapper}>
<HeroRadio.Indicator />
</HeroRadio.Control>
<span className={classNames?.label}>{children}</span>
</HeroRadio.Content>
</HeroRadio>
)
}