admin/components/consumer/input/consumerInputStyles.ts
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

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')
}