feat(admin-ui): shadcn TextArea (Stage 3.8a)

This commit is contained in:
Александр Зимин
2026-05-11 09:13:10 +00:00
parent 117136be84
commit 649b7c4244
2 changed files with 76 additions and 0 deletions
@@ -0,0 +1,73 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
/**
* TextArea — multi-line text input с handoff tokens. Заменяет {@code @nstart/ui}
* TextArea (Phase 3 migration step).
*
* Поддерживает 2 режима (как Input):
* - "bare" — просто <textarea>, для inline использования.
* - "field" — когда задан label/hint/error, рендерится FormField обёртка.
*/
export type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
/** Если задан — рендерится field-обёртка с label выше. */
label?: React.ReactNode
/** Optional helper text under textarea. */
hint?: React.ReactNode
/** Error message — overrides hint визуально, accent в pink. */
error?: React.ReactNode
/** Wrapper className когда rendering field-mode. */
containerClassName?: string
}
export const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
function TextArea(
{ className, label, hint, error, required, containerClassName, id, rows = 4, ...props },
ref,
) {
const autoId = React.useId()
const inputId = id ?? autoId
const textareaEl = (
<textarea
id={inputId}
ref={ref}
rows={rows}
required={required}
aria-required={required || undefined}
aria-invalid={Boolean(error) || undefined}
className={cn(
'flex w-full rounded-md border border-line bg-surface px-3 py-2 text-sm text-ink',
'placeholder:text-mute',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-bg',
'disabled:cursor-not-allowed disabled:opacity-50',
'resize-y min-h-[80px]',
error && 'border-pink focus-visible:ring-pink',
className,
)}
{...props}
/>
)
if (!label && !hint && !error) return textareaEl
return (
<div className={cn('flex flex-col gap-1', containerClassName)}>
{label && (
<label
htmlFor={inputId}
className="text-2xs font-medium font-display uppercase tracking-[0.10em] text-mute leading-none"
>
{label}
{required && <span className="text-pink ml-0.5">*</span>}
</label>
)}
{textareaEl}
{error ? (
<span className="text-xs text-pink">{error}</span>
) : hint ? (
<span className="text-xs text-mute">{hint}</span>
) : null}
</div>
)
},
)