Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
119 lines
4.9 KiB
TypeScript
119 lines
4.9 KiB
TypeScript
import type {
|
|
ConsumerFieldKind,
|
|
ConsumerInputRadius,
|
|
ConsumerInputSize,
|
|
ConsumerInputTone,
|
|
} from '@/components/consumer/input/consumerInputTypes'
|
|
import { cn } from '@/lib/cn'
|
|
|
|
const HEIGHT_CLASS: Record<ConsumerInputSize, string> = {
|
|
sm: 'h-8 min-h-8',
|
|
md: 'h-12 min-h-12',
|
|
lg: 'h-[52px] min-h-[52px]',
|
|
}
|
|
|
|
const RADIUS_CLASS: Record<ConsumerInputRadius, string> = {
|
|
full: 'rounded-full',
|
|
control: 'rounded-[10px]',
|
|
}
|
|
|
|
/** Label: 14px / medium / #808080 */
|
|
export const CONSUMER_FIELD_LABEL_CLASS = 'mb-1 block text-sm font-medium !text-secondary-20'
|
|
|
|
/** Value: 14px / #333A44; placeholder: 12px / #333A44 */
|
|
export const CONSUMER_FIELD_INPUT_CLASS =
|
|
'text-sm font-medium text-consumer-text placeholder:text-xs placeholder:font-medium placeholder:text-consumer-text !outline-none !ring-0 focus:!outline-none focus:!ring-0 focus-visible:!outline-none focus-visible:!ring-0'
|
|
|
|
export const CONSUMER_FIELD_DESCRIPTION_CLASS = 'text-xs font-medium text-secondary-20'
|
|
|
|
export const CONSUMER_FIELD_SELECT_VALUE_CLASS = 'text-sm font-medium text-consumer-text'
|
|
|
|
export const CONSUMER_FIELD_SELECT_PLACEHOLDER_CLASS = 'text-xs font-medium text-consumer-text'
|
|
|
|
export const CONSUMER_FIELD_SELECT_POPOVER_CLASS =
|
|
'max-h-[300px] overflow-y-auto rounded-consumer-modal border border-consumer-border bg-consumer-surface p-1'
|
|
|
|
export const CONSUMER_CHECKBOX_CLASS_NAMES = {
|
|
label: 'text-sm font-medium text-secondary-20',
|
|
wrapper: 'border-consumer-border data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-white',
|
|
}
|
|
|
|
export const CONSUMER_RADIO_CLASS_NAMES = {
|
|
label: 'text-sm font-medium text-consumer-text',
|
|
wrapper: 'border-consumer-border data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-white',
|
|
}
|
|
|
|
export function defaultConsumerTone(generalType: ConsumerFieldKind): ConsumerInputTone {
|
|
return generalType === 'select' ? 'muted' : 'bordered'
|
|
}
|
|
|
|
/** Select is always muted; other fields use `tone` or the type default. */
|
|
export function resolveConsumerTone(generalType: ConsumerFieldKind, tone?: ConsumerInputTone): ConsumerInputTone {
|
|
if (generalType === 'select') return 'muted'
|
|
|
|
return tone ?? defaultConsumerTone(generalType)
|
|
}
|
|
|
|
function focusClasses() {
|
|
return cn(
|
|
'!outline-none !ring-0 focus:!outline-none focus:!ring-0 focus-visible:!outline-none focus-visible:!ring-0',
|
|
'data-[focus-visible=true]:!outline-none data-[focus-visible=true]:!ring-0',
|
|
'data-[active=true]:!outline-none data-[active=true]:!ring-0',
|
|
'hover:!border-primary-300',
|
|
'focus:!border-consumer-focus focus-within:!border-consumer-focus',
|
|
'data-[focused=true]:!border-consumer-focus data-[focus-within=true]:!border-consumer-focus data-[focus-visible=true]:!border-consumer-focus',
|
|
// InputOTP اسلاتها با data-active فوکوس میشوند، نه data-focused
|
|
'data-[active=true]:!border-primary'
|
|
)
|
|
}
|
|
|
|
function toneClasses(tone: ConsumerInputTone) {
|
|
return tone === 'muted'
|
|
? 'border border-transparent bg-[#E2E2E280] hover:bg-[#e2e2e2b3]'
|
|
: 'border border-consumer-border bg-consumer-surface'
|
|
}
|
|
|
|
export function buildConsumerFieldWrapper({
|
|
radius,
|
|
size,
|
|
tone,
|
|
}: {
|
|
radius: ConsumerInputRadius
|
|
size: ConsumerInputSize
|
|
tone: ConsumerInputTone
|
|
}) {
|
|
return cn('shadow-none max-w-full px-2', HEIGHT_CLASS[size], RADIUS_CLASS[radius], toneClasses(tone), focusClasses())
|
|
}
|
|
|
|
export function buildConsumerTextareaWrapper({ radius, tone }: { radius: ConsumerInputRadius; tone: ConsumerInputTone }) {
|
|
return cn('shadow-none px-2 py-3 min-h-24', RADIUS_CLASS[radius], toneClasses(tone), focusClasses())
|
|
}
|
|
|
|
export function withConsumerFieldError(wrapper: string) {
|
|
return cn(
|
|
wrapper,
|
|
// فقط border قرمز ساده؛ outline/ring مربوط به HeroUI invalid-field-ring را حذف کن
|
|
// !border عرض را هم برمیگرداند تا border-0 سفارشی روی OTP بیاثر شود
|
|
'!border !border-fourth hover:!border-fourth focus:!border-fourth focus-within:!border-fourth',
|
|
'data-[focused=true]:!border-fourth data-[focus-within=true]:!border-fourth data-[active=true]:!border-fourth',
|
|
'!outline-none !ring-0 !shadow-none',
|
|
'focus:!outline-none focus:!ring-0 focus-visible:!outline-none focus-visible:!ring-0',
|
|
'data-[focused=true]:!outline-none data-[focus-visible=true]:!outline-none data-[focus-within=true]:!outline-none data-[active=true]:!outline-none',
|
|
'data-[focused=true]:!ring-0 data-[focus-visible=true]:!ring-0 data-[focus-within=true]:!ring-0 data-[active=true]:!ring-0'
|
|
)
|
|
}
|
|
|
|
export function buildConsumerOtpSegmentClass({
|
|
radius,
|
|
size,
|
|
tone = 'bordered',
|
|
}: {
|
|
radius: ConsumerInputRadius
|
|
size: ConsumerInputSize
|
|
tone?: ConsumerInputTone
|
|
}) {
|
|
const widthClass = size === 'sm' ? 'size-8' : size === 'md' ? 'size-12' : 'size-[52px]'
|
|
|
|
return cn(widthClass, RADIUS_CLASS[radius], toneClasses(tone), focusClasses(), 'text-sm font-medium text-consumer-text')
|
|
}
|