@asteby/metacore-runtime-react 23.6.0 → 23.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,6 @@ import {
26
26
  Button,
27
27
  Input,
28
28
  Textarea,
29
- Label,
30
29
  Select,
31
30
  SelectContent,
32
31
  SelectItem,
@@ -44,6 +43,7 @@ import { DynamicSelectField } from './dynamic-select-field'
44
43
  import { DynamicDateField } from './dynamic-date-field'
45
44
  import { UploadField } from './upload-field'
46
45
  import { isLineItemsField, resolveWidget, resolveDependsValue, getDependsOn } from './dynamic-form-schema'
46
+ import { FieldGrid, FieldCell, FieldLabel } from './field-grid'
47
47
  import type { ActionFieldDef } from './types'
48
48
  // Canonical registry lives in @asteby/metacore-sdk
49
49
  import {
@@ -218,6 +218,10 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
218
218
  const { t } = useTranslation()
219
219
  const api = useApi()
220
220
  const [executing, setExecuting] = useState(false)
221
+ // `action.label` is an addon-contributed i18n key; its locale bundle loads
222
+ // asynchronously, so translate at render (defaultValue keeps an already
223
+ // localized label unchanged).
224
+ const label = t(action.label, { defaultValue: action.label })
221
225
 
222
226
  const execute = async () => {
223
227
  setExecuting(true)
@@ -244,10 +248,10 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
244
248
  <AlertDialogHeader>
245
249
  <AlertDialogTitle className="flex items-center gap-2">
246
250
  <DynamicIcon name={action.icon} className="h-5 w-5" />
247
- {action.label}
251
+ {label}
248
252
  </AlertDialogTitle>
249
253
  <AlertDialogDescription>
250
- {action.confirmMessage || `${action.label}?`}
254
+ {action.confirmMessage || `${label}?`}
251
255
  </AlertDialogDescription>
252
256
  </AlertDialogHeader>
253
257
  <AlertDialogFooter>
@@ -258,7 +262,7 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
258
262
  style={action.color ? { backgroundColor: action.color } : undefined}
259
263
  >
260
264
  {executing ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <DynamicIcon name={action.icon} className="mr-2 h-4 w-4" />}
261
- {action.label}
265
+ {label}
262
266
  </AlertDialogAction>
263
267
  </AlertDialogFooter>
264
268
  </AlertDialogContent>
@@ -268,6 +272,10 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
268
272
 
269
273
  function GenericActionModal({ open, onOpenChange, action, model, record, endpoint, onSuccess }: ActionModalProps) {
270
274
  const { t } = useTranslation()
275
+ // Addon-contributed labels (action + fields) are i18n keys whose locale
276
+ // bundle loads asynchronously; translate at render so they don't render raw.
277
+ // defaultValue keeps an already-localized string unchanged.
278
+ const tl = (s: string) => t(s, { defaultValue: s })
271
279
  const api = useApi()
272
280
  const [formData, setFormData] = useState<Record<string, any>>({})
273
281
  const [executing, setExecuting] = useState(false)
@@ -326,13 +334,13 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
326
334
  if (isLineItemsField(field)) {
327
335
  const rows = formData[field.key]
328
336
  if (!Array.isArray(rows) || rows.length === 0) {
329
- toast.error(`${field.label} requiere al menos un renglón`)
337
+ toast.error(`${tl(field.label)} requiere al menos un renglón`)
330
338
  return
331
339
  }
332
340
  continue
333
341
  }
334
342
  if (!formData[field.key] && formData[field.key] !== false) {
335
- toast.error(`${field.label} es requerido`)
343
+ toast.error(`${tl(field.label)} es requerido`)
336
344
  return
337
345
  }
338
346
  }
@@ -383,44 +391,43 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
383
391
  is inline (guaranteed) since an arbitrary max-h-[90vh] class may be
384
392
  dropped by a consuming app's Tailwind scan. */}
385
393
  <DialogContent
386
- className={'flex max-h-[90vh] flex-col overflow-hidden ' + (widthPx ? '' : 'sm:max-w-lg')}
394
+ className={'flex max-h-[90vh] flex-col overflow-hidden ' + (widthPx ? '' : 'sm:max-w-xl')}
387
395
  style={{ maxHeight: '90vh', ...(widthPx ? { maxWidth: widthPx, width: '95vw' } : {}) }}
388
396
  >
389
397
  <DialogHeader className="shrink-0">
390
398
  <DialogTitle className="flex items-center gap-2">
391
399
  <DynamicIcon name={action.icon} className="h-5 w-5" />
392
- {action.label}
400
+ {tl(action.label)}
393
401
  </DialogTitle>
394
402
  {action.confirmMessage && <DialogDescription>{action.confirmMessage}</DialogDescription>}
395
403
  </DialogHeader>
396
- {/* Scrollable body. Responsive 2-column grid: scalar fields
397
- (journal, date, reference) flow side-by-side instead of one
398
- tall vertical stack; line-items grids and textareas span the
399
- full width. Mirrors DynamicForm driven only by field shape. */}
400
- <div className="-mx-1 grid min-h-0 flex-1 gap-4 overflow-y-auto px-1 py-4 sm:grid-cols-2">
401
- {action.fields?.map((field) => {
402
- const fullWidth =
403
- isLineItemsField(field) ||
404
- resolveWidget(field) === 'textarea' ||
405
- resolveWidget(field) === 'richtext'
406
- return (
407
- <div
408
- key={field.key}
409
- className={'grid gap-2 ' + (fullWidth ? 'sm:col-span-2' : '')}
410
- >
411
- <Label htmlFor={field.key}>
412
- {field.label}
413
- {field.required && <span className="text-red-500 ml-1">*</span>}
414
- </Label>
415
- {renderField(field, formData[field.key], (v: any) => updateField(field.key, v), formData)}
416
- </div>
417
- )
418
- })}
419
- {relations.length > 0 && (
420
- <div className="sm:col-span-2">
421
- <DynamicRelations record={record} relations={relations} />
422
- </div>
423
- )}
404
+ {/* Scrollable body. The shared FieldGrid lays scalar fields out
405
+ in two responsive columns (single column on phones); line-items
406
+ grids and textareas span the full width. `min-w-0` on each cell
407
+ (in FieldCell) keeps a long select/input value from blowing the
408
+ grid past the dialog and spawning a horizontal scrollbar. */}
409
+ <div className="-mx-1 min-h-0 flex-1 overflow-y-auto px-1 py-4">
410
+ <FieldGrid>
411
+ {action.fields?.map((field) => {
412
+ const fullWidth =
413
+ isLineItemsField(field) ||
414
+ resolveWidget(field) === 'textarea' ||
415
+ resolveWidget(field) === 'richtext'
416
+ return (
417
+ <FieldCell key={field.key} fullWidth={fullWidth}>
418
+ <FieldLabel htmlFor={field.key} required={field.required}>
419
+ {tl(field.label)}
420
+ </FieldLabel>
421
+ {renderField(field, formData[field.key], (v: any) => updateField(field.key, v), formData)}
422
+ </FieldCell>
423
+ )
424
+ })}
425
+ {relations.length > 0 && (
426
+ <FieldCell fullWidth>
427
+ <DynamicRelations record={record} relations={relations} />
428
+ </FieldCell>
429
+ )}
430
+ </FieldGrid>
424
431
  </div>
425
432
  <DialogFooter className="shrink-0">
426
433
  <Button variant="outline" onClick={() => onOpenChange(false)} disabled={executing}>
@@ -432,7 +439,7 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
432
439
  style={action.color ? { backgroundColor: action.color, color: 'white' } : undefined}
433
440
  >
434
441
  {executing ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <DynamicIcon name={action.icon} className="mr-2 h-4 w-4" />}
435
- {action.label}
442
+ {tl(action.label)}
436
443
  </Button>
437
444
  </DialogFooter>
438
445
  </DialogContent>