fix(admin-ui): IconButton icon prop + compact 2-col form layout
Bugs:
- IconButton @nstart/ui принимает icon={} prop, не children — иконки рендерились
пустыми квадратами (видно «Изменить» tooltip без значка карандашика).
Перевёл оба IconButton на icon={<PencilSimpleIcon/>} / icon={<XCircleIcon/>}.
UX:
- Modal max-w-2xl → max-w-4xl (форма дышит, ru-RU/en-US labels не оборачиваются).
- SchemaDrivenForm: scalar поля теперь в 2-колоночной grid, full-width
(i18n / array / textarea / nested object) занимают sm:col-span-2.
- Локальные ярлыки i18n: tracking-normal + whitespace-nowrap, w-14 — больше не
ломаются «ru-» «RU» из-за inherited tracking-label.
This commit is contained in:
@@ -96,19 +96,19 @@ export const SchemaDrivenForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(submit)} className="space-y-6">
|
<form onSubmit={handleSubmit(submit)} className="space-y-4">
|
||||||
<FormSection title={t('form.identity')}>
|
<FormSection title={t('form.identity')}>
|
||||||
<FormGrid columns={1}>
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<TextInput
|
<div className="sm:col-span-2">
|
||||||
label={t('form.businessKey')}
|
<TextInput
|
||||||
required
|
label={t('form.businessKey')}
|
||||||
disabled={mode === 'edit'}
|
required
|
||||||
placeholder="MY_RECORD_KEY"
|
disabled={mode === 'edit'}
|
||||||
error={formState.errors.businessKey ? t('form.error.required') : undefined}
|
placeholder="MY_RECORD_KEY"
|
||||||
{...register('businessKey', { required: true, minLength: 1 })}
|
error={formState.errors.businessKey ? t('form.error.required') : undefined}
|
||||||
/>
|
{...register('businessKey', { required: true, minLength: 1 })}
|
||||||
</FormGrid>
|
/>
|
||||||
<FormGrid columns={2}>
|
</div>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
label={t('form.validFrom')}
|
label={t('form.validFrom')}
|
||||||
@@ -119,11 +119,11 @@ export const SchemaDrivenForm = ({
|
|||||||
label={t('form.validTo')}
|
label={t('form.validTo')}
|
||||||
{...register('validTo')}
|
{...register('validTo')}
|
||||||
/>
|
/>
|
||||||
</FormGrid>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection title={t('form.dataFields')}>
|
<FormSection title={t('form.dataFields')}>
|
||||||
<FormGrid columns={1}>
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
{Object.entries(properties).map(([key, propSchema]) => (
|
{Object.entries(properties).map(([key, propSchema]) => (
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
key={key}
|
key={key}
|
||||||
@@ -136,7 +136,7 @@ export const SchemaDrivenForm = ({
|
|||||||
fieldError={formState.errors.data?.[key as keyof FormValues['data']]}
|
fieldError={formState.errors.data?.[key as keyof FormValues['data']]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</FormGrid>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
{serverError && <Alert variant="error">{serverError}</Alert>}
|
{serverError && <Alert variant="error">{serverError}</Alert>}
|
||||||
@@ -153,6 +153,14 @@ export const SchemaDrivenForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFullWidth = (s: JsonSchema): boolean => {
|
||||||
|
if (s['x-localized']) return true
|
||||||
|
if (s.type === 'array') return true
|
||||||
|
if ((s.maxLength ?? 0) > 200) return true
|
||||||
|
if (s.type === 'object') return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type FieldRendererProps = {
|
type FieldRendererProps = {
|
||||||
fieldKey: string
|
fieldKey: string
|
||||||
schema: JsonSchema
|
schema: JsonSchema
|
||||||
@@ -163,7 +171,16 @@ type FieldRendererProps = {
|
|||||||
fieldError?: unknown
|
fieldError?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldRenderer = ({
|
const FieldRenderer = (props: FieldRendererProps) => {
|
||||||
|
const wrapperClass = isFullWidth(props.schema) ? 'sm:col-span-2' : ''
|
||||||
|
return (
|
||||||
|
<div className={wrapperClass}>
|
||||||
|
<FieldBody {...props} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FieldBody = ({
|
||||||
fieldKey,
|
fieldKey,
|
||||||
schema,
|
schema,
|
||||||
required,
|
required,
|
||||||
@@ -188,11 +205,11 @@ const FieldRenderer = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name={`data.${fieldKey}.${loc}` as `data.${string}`}
|
name={`data.${fieldKey}.${loc}` as `data.${string}`}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-2xs font-mono text-carbon/70 w-12 shrink-0">
|
<span className="text-xs font-mono text-carbon/70 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
|
||||||
{loc === defaultLocale ? <strong>{loc}</strong> : loc}
|
{loc === defaultLocale ? <strong>{loc}</strong> : loc}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-0">
|
||||||
<TextInput {...field} value={(field.value as string | undefined) ?? ''} />
|
<TextInput {...field} value={(field.value as string | undefined) ?? ''} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,17 +156,15 @@ function DictionaryDetail() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
label={t('dict.action.edit')}
|
label={t('dict.action.edit')}
|
||||||
variant="default"
|
variant="default"
|
||||||
|
icon={<PencilSimpleIcon weight="regular" />}
|
||||||
onClick={() => setEdit({ kind: 'edit', record: r })}
|
onClick={() => setEdit({ kind: 'edit', record: r })}
|
||||||
>
|
/>
|
||||||
<PencilSimpleIcon size={16} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
label={t('dict.action.close')}
|
label={t('dict.action.close')}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
icon={<XCircleIcon weight="regular" />}
|
||||||
onClick={() => setEdit({ kind: 'close-confirm', record: r })}
|
onClick={() => setEdit({ kind: 'close-confirm', record: r })}
|
||||||
>
|
/>
|
||||||
<XCircleIcon size={16} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -186,7 +184,7 @@ function DictionaryDetail() {
|
|||||||
? `${t('dict.action.edit')}: ${edit.record.businessKey}`
|
? `${t('dict.action.edit')}: ${edit.record.businessKey}`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
maxWidth="max-w-2xl"
|
maxWidth="max-w-4xl"
|
||||||
>
|
>
|
||||||
{detailQuery.data && (edit.kind === 'create' || edit.kind === 'edit') && (
|
{detailQuery.data && (edit.kind === 'create' || edit.kind === 'edit') && (
|
||||||
<SchemaDrivenForm
|
<SchemaDrivenForm
|
||||||
|
|||||||
Reference in New Issue
Block a user