Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
import type { FieldValues, Path } from 'react-hook-form'
|
|
|
|
import { useFormContext } from 'react-hook-form'
|
|
|
|
import { texts, format } from '@/texts'
|
|
|
|
interface SeoCharCounterHintProps<T extends FieldValues> {
|
|
name: Path<T>
|
|
max: number
|
|
min?: number
|
|
}
|
|
|
|
/**
|
|
* Live `n/max` counter for an SEO/char-limited text field, rendered as the
|
|
* field's `description` — shared across the admin CRUD form modals
|
|
* (tags, event categories, blog articles) so the display logic and its
|
|
* `max`/`min` thresholds live in one place instead of being hand-defined
|
|
* per modal. `max`/`min` should always be passed from the exported
|
|
* constants next to the corresponding zod schema, not bare literals.
|
|
*/
|
|
export function SeoCharCounterHint<T extends FieldValues>({ name, max, min }: SeoCharCounterHintProps<T>) {
|
|
const { watch } = useFormContext<T>()
|
|
// `watch(name)`'s return type doesn't fully resolve for a naked generic
|
|
// `T extends FieldValues` (react-hook-form's conditional path-value type
|
|
// needs a concrete field-map to compute against) — widen explicitly to
|
|
// `unknown` and narrow via `typeof` rather than let it flow as `any`.
|
|
const value: unknown = watch(name)
|
|
const length = typeof value === 'string' ? value.length : 0
|
|
const tooShort = min !== undefined && length > 0 && length < min
|
|
|
|
return (
|
|
<span className={length > max || tooShort ? 'text-fourth' : undefined}>
|
|
{`${length.toLocaleString('fa-IR')}/${max.toLocaleString('fa-IR')}`}
|
|
{min !== undefined ? format(texts.common.seoCharMinHint, { min: min.toLocaleString('fa-IR') }) : ''}
|
|
</span>
|
|
)
|
|
}
|