dynamic-modal 1.1.24 → 1.1.26

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.
package/README.md CHANGED
@@ -441,6 +441,34 @@ dynamically.
441
441
  }
442
442
  ```
443
443
 
444
+ ### `watcher`
445
+
446
+ Use `watcher` when you want to display a derived read-only value built from
447
+ other fields in the same modal.
448
+
449
+ `watcher` listens to the fields listed in `watchList`, joins their current
450
+ values, and renders the result using your custom `Input` component in disabled
451
+ mode.
452
+
453
+ Example:
454
+
455
+ ```ts
456
+ {
457
+ elementType: 'watcher',
458
+ label: 'Full name preview',
459
+ watchList: ['firstName', 'middleName', 'lastName'],
460
+ style: {
461
+ width: '100%',
462
+ },
463
+ }
464
+ ```
465
+
466
+ Typical use cases:
467
+
468
+ - preview a full name from multiple inputs
469
+ - build a quick summary field for the user
470
+ - show a composed display value without storing it as a real form field
471
+
444
472
  ## Advanced conditions with async actions
445
473
 
446
474
  `renderIf` and `enableIf` can also use async logic instead of static value maps.
@@ -462,6 +490,52 @@ renderIf: {
462
490
 
463
491
  The same shape works for `enableIf`.
464
492
 
493
+ ## Variants and combinations (`renderIf`, `enableIf`, `liveData`)
494
+
495
+ The library supports these variants:
496
+
497
+ | Feature | Variant | Shape |
498
+ | --- | --- | --- |
499
+ | `renderIf` | static criteria | `renderIf: { fieldName: ['value1', 'value2'] }` |
500
+ | `renderIf` | wildcard | `renderIf: { fieldName: ['*'] }` |
501
+ | `renderIf` | async action | `renderIf: { condition: ['fieldName'], action: async (...) => boolean }` |
502
+ | `enableIf` | static criteria | `enableIf: { fieldName: ['value1', 'value2'] }` |
503
+ | `enableIf` | wildcard | `enableIf: { fieldName: ['*'] }` |
504
+ | `enableIf` | async action | `enableIf: { condition: ['fieldName'], action: async (...) => boolean }` |
505
+ | `liveData` | single trigger field | `liveData: { condition: ['fieldName'], action: async (...) => IOption[] }` |
506
+ | `liveData` | multiple trigger fields | `liveData: { condition: ['fieldA', 'fieldB'], action: async (...) => IOption[] }` |
507
+
508
+ Supported combinations by field type:
509
+
510
+ - `input`, `textarea`, `toggle`, `upload`, `custom-upload`: `renderIf` + `enableIf`
511
+ - `select`: `renderIf` + `enableIf` + `liveData`
512
+ - `table`: `renderIf` + `liveData`
513
+ - `watcher`: no `renderIf`/`enableIf`/`liveData` contract in its interface
514
+
515
+ Behavior note about multiple observed fields:
516
+
517
+ - In static mode (`Record<field, values>`), conditions are evaluated per field-change event.
518
+ - In async mode (`condition: [...]`), `action` receives the changed field value as first argument and the whole form as second argument.
519
+ - For `liveData`, when options refresh, the target field value is reset to its default (`defaultValue`) or `[]` in multi-select mode.
520
+
521
+ Minimal combination example (`select` with all three):
522
+
523
+ ```ts
524
+ {
525
+ elementType: 'select',
526
+ label: 'Options',
527
+ name: 'optionId',
528
+ options: [],
529
+ validation: { required: true, message: 'Required' },
530
+ renderIf: { typeId: ['*'] },
531
+ enableIf: { statusId: ['approved'] },
532
+ liveData: {
533
+ condition: ['typeId', 'statusId'],
534
+ action: async (changedValue, formData) => readOptions(changedValue, formData),
535
+ },
536
+ }
537
+ ```
538
+
465
539
  ## Examples by use case
466
540
 
467
541
  ### 1. Basic modal
@@ -582,6 +656,40 @@ reservedData: {
582
656
 
583
657
  That data will be merged into the object returned by `out`.
584
658
 
659
+ ### 6. Compose a read-only value with `watcher`
660
+
661
+ Use `watcher` when you want the modal to display a value derived from multiple
662
+ fields while the user types.
663
+
664
+ ```ts
665
+ fields: [
666
+ {
667
+ elementType: 'input',
668
+ label: 'First name',
669
+ name: 'firstName',
670
+ validation: { required: true, message: 'Required' },
671
+ },
672
+ {
673
+ elementType: 'input',
674
+ label: 'Last name',
675
+ name: 'lastName',
676
+ validation: { required: true, message: 'Required' },
677
+ },
678
+ {
679
+ elementType: 'watcher',
680
+ label: 'Preview',
681
+ watchList: ['firstName', 'lastName'],
682
+ style: { width: '100%' },
683
+ },
684
+ ];
685
+ ```
686
+
687
+ Important notes:
688
+
689
+ - `watcher` is display-only
690
+ - it does not submit its own value in the modal result
691
+ - it is useful for previews, concatenations, and human-readable summaries
692
+
585
693
  ## Configuration reference
586
694
 
587
695
  ### Modal-level config
@@ -598,6 +706,13 @@ Common properties of `IModalConfigProps`:
598
706
  - `minHeightBody`: minimum body height
599
707
  - `useSubmit`: if `false`, action button uses manual validation mode
600
708
  - `useBlur`: enables backdrop blur style
709
+ - `layout`: section-level customization for:
710
+ - `container`
711
+ - `header` (`showDivider?: boolean`)
712
+ - `title`
713
+ - `body`
714
+ - `footer` (`showDivider?: boolean`)
715
+ Each section supports `className` and `style`.
601
716
  - `actions.action`: main action button props
602
717
  - `actions.cancel`: optional cancel button props
603
718
  - `actions.containerStyle`: style for the action buttons container
@@ -611,11 +726,23 @@ Most form fields share:
611
726
  - `placeholder`
612
727
  - `defaultValue`
613
728
  - `style`
729
+ - `customProperties`
614
730
  - `disabled`
615
731
  - `validation`
616
732
  - `renderIf`
617
733
  - `enableIf`
618
734
 
735
+ Most field interfaces now also accept native HTML attributes according to the
736
+ element type (`input`, `textarea`, `button`, `select`, etc.). These extra props
737
+ are forwarded with the rest of the field config.
738
+
739
+ `watcher` uses:
740
+
741
+ - `label`
742
+ - `style`
743
+ - `customProperties`
744
+ - `watchList`
745
+
619
746
  Validation supports:
620
747
 
621
748
  - `required`
@@ -48,14 +48,17 @@ const MakeSelect = ({ element, control, watch, setValue, unregister, }) => {
48
48
  }
49
49
  });
50
50
  }
51
- if (elementLiveData) {
51
+ const shouldResolveLiveData = Boolean(elementLiveData?.condition?.includes(name));
52
+ if (shouldResolveLiveData) {
52
53
  setLiveSearching(true);
53
54
  checkLiveData(formData, { name, type })
54
55
  .then((options) => {
55
56
  if (options === undefined || options === null)
56
57
  return;
57
58
  setLiveData(options);
58
- setValue(elementName, options);
59
+ setValue(elementName, inputProps.isMulti
60
+ ? inputProps.defaultValue ?? []
61
+ : inputProps.defaultValue ?? '');
59
62
  })
60
63
  .finally(() => {
61
64
  setLiveSearching(false);
@@ -38,7 +38,9 @@ const MakeSelect = ({ element, control, watch, setValue, unregister, }) => {
38
38
  setLiveSearching,
39
39
  unregister,
40
40
  setValue,
41
- liveDataResetValue: inputProps.defaultValue ?? [],
41
+ liveDataResetValue: inputProps.isMulti
42
+ ? (inputProps.defaultValue ?? [])
43
+ : (inputProps.defaultValue ?? ''),
42
44
  });
43
45
  if (!render)
44
46
  return null;
@@ -21,7 +21,8 @@ const useConditionalField = ({ watch, elementName, renderIf, enableIf, liveDataC
21
21
  setRender(renderStatus);
22
22
  }
23
23
  }
24
- if (liveDataConfig && setLiveData) {
24
+ const shouldResolveLiveData = Boolean(liveDataConfig?.condition?.includes(meta.name));
25
+ if (shouldResolveLiveData && setLiveData) {
25
26
  const requestId = ++liveRequestId.current;
26
27
  setLiveSearching?.(true);
27
28
  try {
@@ -1,4 +1,4 @@
1
- import { FC, PropsWithChildren } from 'react';
1
+ import { CSSProperties, FC, PropsWithChildren, ReactNode } from 'react';
2
2
  import { FieldError } from 'react-hook-form';
3
3
  import { IOption } from './option';
4
4
  import { IMakeInput } from './make-input';
@@ -14,7 +14,17 @@ export interface IComponentAditionalProps {
14
14
  error?: FieldError;
15
15
  liveSearching?: boolean;
16
16
  }
17
+ export interface IModalSectionComponentProps {
18
+ children: ReactNode;
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ }
17
22
  export interface IComponentState {
23
+ ModalContainer?: FC<IModalSectionComponentProps>;
24
+ ModalHeader?: FC<IModalSectionComponentProps>;
25
+ ModalTitle?: FC<IModalSectionComponentProps>;
26
+ ModalBody?: FC<IModalSectionComponentProps>;
27
+ ModalFooter?: FC<IModalSectionComponentProps>;
18
28
  ModalButtonCancel: FC<Omit<IMakeButton, 'elementType'>>;
19
29
  ModalButtonAction: FC<Omit<IMakeButton, 'elementType'>>;
20
30
  Button: FC<Omit<IMakeButton, 'elementType'>>;
@@ -1,5 +1,6 @@
1
- import { ChangeEvent, CSSProperties } from 'react';
2
- export interface ICustomUpload {
1
+ import { ChangeEvent, CSSProperties, InputHTMLAttributes } from 'react';
2
+ type ICustomUploadHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'style' | 'name' | 'disabled' | 'value' | 'onChange' | 'type'>;
3
+ export interface ICustomUpload extends ICustomUploadHtmlProps {
3
4
  id?: string;
4
5
  value?: string;
5
6
  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
@@ -10,3 +11,4 @@ export interface ICustomUpload {
10
11
  name: string;
11
12
  disabled?: boolean;
12
13
  }
14
+ export {};
@@ -10,6 +10,7 @@ export interface IField {
10
10
  id?: string;
11
11
  label?: string;
12
12
  style?: CSSProperties;
13
+ customProperties?: Record<string, any>;
13
14
  placeholder?: string;
14
15
  defaultValue?: any;
15
16
  renderIf?: IModalRenderCondition;
@@ -1,12 +1,13 @@
1
- import { ChangeEvent, CSSProperties } from 'react';
1
+ import { ChangeEvent, CSSProperties, InputHTMLAttributes } from 'react';
2
+ type IInputUploadHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'style' | 'name' | 'disabled' | 'value' | 'onChange' | 'type'>;
2
3
  export interface IFileResult {
3
4
  name: string;
4
5
  size: number;
5
6
  data: string;
6
7
  }
7
- export interface IInputUpload {
8
+ export interface IInputUpload extends IInputUploadHtmlProps {
8
9
  id?: string;
9
- value?: string;
10
+ value?: string | number | readonly string[];
10
11
  onChange: (event: ChangeEvent<HTMLInputElement> | IFileResult | FileList | null) => void;
11
12
  accept?: string;
12
13
  label?: string;
@@ -17,3 +18,4 @@ export interface IInputUpload {
17
18
  disabled?: boolean;
18
19
  read?: boolean;
19
20
  }
21
+ export {};
@@ -1,12 +1,14 @@
1
- import { CSSProperties, ReactNode } from 'react';
2
- import { IFieldProps } from './field';
1
+ import { ButtonHTMLAttributes, CSSProperties, ReactNode } from 'react';
3
2
  import { FieldValues, UseFormGetValues } from 'react-hook-form';
4
- export interface IMakeButton {
3
+ import { IFieldProps } from './field';
4
+ type IButtonHtmlProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'id' | 'disabled' | 'className' | 'style' | 'type' | 'onClick' | 'children' | 'color'>;
5
+ export interface IMakeButton extends IButtonHtmlProps {
5
6
  id?: string;
6
7
  elementType: 'button';
7
8
  disabled?: boolean;
8
9
  className?: string;
9
10
  style?: CSSProperties;
11
+ customProperties?: Record<string, any>;
10
12
  variant?: string;
11
13
  text?: string;
12
14
  type?: 'button' | 'submit' | 'reset';
@@ -18,3 +20,4 @@ export interface IMakeButtonProps extends IFieldProps {
18
20
  element: Omit<IMakeButton, 'elementType'>;
19
21
  getValues: UseFormGetValues<FieldValues>;
20
22
  }
23
+ export {};
@@ -1,7 +1,10 @@
1
+ import { InputHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
- export interface IMakeCustomUpload extends IField {
3
+ type ICustomUploadHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'defaultValue' | 'disabled' | 'type'>;
4
+ export interface IMakeCustomUpload extends IField, ICustomUploadHtmlProps {
3
5
  elementType: 'custom-upload';
4
6
  }
5
7
  export interface IMakeCustomUploadProps extends IFieldProps {
6
8
  element: Omit<IMakeCustomUpload, 'elementType'>;
7
9
  }
10
+ export {};
@@ -1,7 +1,9 @@
1
- import { CSSProperties, FC } from 'react';
1
+ import { CSSProperties, FC, HTMLAttributes } from 'react';
2
2
  import { IFieldProps } from './field';
3
- export interface IMakeDescription {
3
+ type IDescriptionHtmlProps = Omit<HTMLAttributes<HTMLDivElement>, 'style'>;
4
+ export interface IMakeDescription extends IDescriptionHtmlProps {
4
5
  elementType: 'text';
6
+ customProperties?: Record<string, any>;
5
7
  text?: string;
6
8
  containerStyle?: CSSProperties;
7
9
  textStyle?: CSSProperties;
@@ -11,3 +13,4 @@ export interface IMakeDescription {
11
13
  export interface IMakeDescriptionProps extends IFieldProps {
12
14
  element: Omit<IMakeDescription, 'elementType'>;
13
15
  }
16
+ export {};
@@ -1,8 +1,10 @@
1
- import { CSSProperties } from 'react';
1
+ import { CSSProperties, HTMLAttributes } from 'react';
2
2
  import { IFieldProps } from './field';
3
3
  import { IModalField } from './modal';
4
- export interface IMakeFieldGroup {
4
+ type IFieldGroupHtmlProps = Omit<HTMLAttributes<HTMLDivElement>, 'style'>;
5
+ export interface IMakeFieldGroup extends IFieldGroupHtmlProps {
5
6
  elementType: 'group';
7
+ customProperties?: Record<string, any>;
6
8
  groups: Array<IModalField>;
7
9
  style?: CSSProperties;
8
10
  title?: string;
@@ -10,3 +12,4 @@ export interface IMakeFieldGroup {
10
12
  export interface IMakeFieldGroupProps extends IFieldProps {
11
13
  element: IMakeFieldGroup;
12
14
  }
15
+ export {};
@@ -1,12 +1,14 @@
1
- import { HTMLInputTypeAttribute } from 'react';
1
+ import { HTMLInputTypeAttribute, InputHTMLAttributes } from 'react';
2
2
  import { IField, IFieldProps } from './field';
3
- export interface IMakeInput extends IField {
3
+ type IInputHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'placeholder' | 'defaultValue' | 'disabled' | 'type' | 'min' | 'max'>;
4
+ export interface IMakeInput extends IField, IInputHtmlProps {
4
5
  elementType: 'input';
5
6
  placeholder?: string;
6
- min?: string;
7
- max?: string;
7
+ min?: string | number;
8
+ max?: string | number;
8
9
  type?: HTMLInputTypeAttribute;
9
10
  }
10
11
  export interface IMakeInputProps extends IFieldProps {
11
12
  element: Omit<IMakeInput, 'elementType'>;
12
13
  }
14
+ export {};
@@ -1,7 +1,9 @@
1
+ import { SelectHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
3
  import { IModalLiveDataCondition } from './modal';
3
4
  import { IOption } from './option';
4
- export interface IMakeSelect extends IField {
5
+ type ISelectHtmlProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'defaultValue' | 'disabled'>;
6
+ export interface IMakeSelect extends IField, ISelectHtmlProps {
5
7
  elementType: 'select';
6
8
  options: Array<IOption>;
7
9
  liveData?: IModalLiveDataCondition;
@@ -11,3 +13,4 @@ export interface IMakeSelect extends IField {
11
13
  export interface IMakeSelectProps extends IFieldProps {
12
14
  element: Omit<IMakeSelect, 'elementType'>;
13
15
  }
16
+ export {};
@@ -1,4 +1,4 @@
1
- import { CSSProperties, JSX } from 'react';
1
+ import { CSSProperties, JSX, TableHTMLAttributes } from 'react';
2
2
  import { IField, IFieldProps } from './field';
3
3
  import { IModalLiveDataCondition } from './modal';
4
4
  export interface ITableColumn extends Pick<IField, 'style'> {
@@ -9,7 +9,8 @@ export interface ITableColumn extends Pick<IField, 'style'> {
9
9
  Icon?: (props: any) => JSX.Element;
10
10
  action?: (row: any) => void;
11
11
  }
12
- export interface IMakeTable extends Omit<IField, 'label' | 'defaultValue' | 'validation' | 'disabled' | 'enableIf' | 'id'> {
12
+ type ITableHtmlProps = Omit<TableHTMLAttributes<HTMLTableElement>, 'style'>;
13
+ export interface IMakeTable extends Omit<IField, 'label' | 'defaultValue' | 'validation' | 'disabled' | 'enableIf' | 'id'>, ITableHtmlProps {
13
14
  elementType: 'table';
14
15
  selectValueName: string;
15
16
  selectTitleName: string;
@@ -24,3 +25,4 @@ export interface IMakeTable extends Omit<IField, 'label' | 'defaultValue' | 'val
24
25
  export interface IMakeTableProps extends Omit<IFieldProps, 'unregister'> {
25
26
  element: Omit<IMakeTable, 'elementType'>;
26
27
  }
28
+ export {};
@@ -1,5 +1,7 @@
1
+ import { TextareaHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
- export interface IMakeTextarea extends IField {
3
+ type ITextareaHtmlProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'placeholder' | 'defaultValue' | 'disabled' | 'cols' | 'rows'>;
4
+ export interface IMakeTextarea extends IField, ITextareaHtmlProps {
3
5
  elementType: 'textarea';
4
6
  cols?: number;
5
7
  rows?: number;
@@ -7,3 +9,4 @@ export interface IMakeTextarea extends IField {
7
9
  export interface IMakeTextareaProps extends IFieldProps {
8
10
  element: Omit<IMakeTextarea, 'elementType'>;
9
11
  }
12
+ export {};
@@ -1,7 +1,10 @@
1
+ import { InputHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
- export interface IMakeToggle extends IField {
3
+ type IToggleHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'checked' | 'defaultValue' | 'disabled' | 'type'>;
4
+ export interface IMakeToggle extends IField, IToggleHtmlProps {
3
5
  elementType: 'toggle';
4
6
  }
5
7
  export interface IMakeToggleProps extends IFieldProps {
6
8
  element: Omit<IMakeToggle, 'elementType'>;
7
9
  }
10
+ export {};
@@ -1,5 +1,7 @@
1
+ import { InputHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
- export interface IMakeUpload extends Omit<IField, 'defaultValue'> {
3
+ type IUploadHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'defaultValue' | 'disabled' | 'type' | 'accept'>;
4
+ export interface IMakeUpload extends Omit<IField, 'defaultValue'>, IUploadHtmlProps {
3
5
  elementType: 'upload';
4
6
  helpText?: string;
5
7
  read: boolean;
@@ -10,3 +12,4 @@ export interface IMakeUpload extends Omit<IField, 'defaultValue'> {
10
12
  export interface IMakeUploadProps extends IFieldProps {
11
13
  element: Omit<IMakeUpload, 'elementType'>;
12
14
  }
15
+ export {};
@@ -1,8 +1,12 @@
1
+ import { InputHTMLAttributes } from 'react';
1
2
  import { IField, IFieldProps } from './field';
2
- export interface IMakeWatcher extends Pick<IField, 'style' | 'label'> {
3
+ type IWatcherBaseProps = Pick<IField, 'style' | 'label' | 'customProperties'>;
4
+ type IWatcherHtmlProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'id' | 'style' | 'onChange' | 'value' | 'defaultValue' | 'disabled' | 'type'>;
5
+ export interface IMakeWatcher extends IWatcherBaseProps, IWatcherHtmlProps {
3
6
  elementType: 'watcher';
4
7
  watchList: Array<string>;
5
8
  }
6
9
  export interface IMakeWatcherProps extends IFieldProps {
7
10
  element: Omit<IMakeWatcher, 'elementType'>;
8
11
  }
12
+ export {};
@@ -23,9 +23,25 @@ export type IModalLiveDataCondition = {
23
23
  action: (data: string, ...args: any[]) => Promise<Array<IOption>>;
24
24
  condition: Array<string>;
25
25
  };
26
+ export interface IModalSectionStyleConfig {
27
+ className?: string;
28
+ style?: CSSProperties;
29
+ }
30
+ export interface IModalLayoutConfig {
31
+ container?: IModalSectionStyleConfig;
32
+ header?: IModalSectionStyleConfig & {
33
+ showDivider?: boolean;
34
+ };
35
+ title?: IModalSectionStyleConfig;
36
+ body?: IModalSectionStyleConfig;
37
+ footer?: IModalSectionStyleConfig & {
38
+ showDivider?: boolean;
39
+ };
40
+ }
26
41
  export interface IModalConfigProps {
27
42
  reservedData?: Record<string, any>;
28
43
  title: string;
44
+ layout?: IModalLayoutConfig;
29
45
  fields: Array<IModalField>;
30
46
  out: (data: any) => void;
31
47
  onClose?: () => void;
package/dist/src/modal.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { createElement as _createElement } from "react";
4
4
  import { useContext, useEffect, useMemo, useState } from 'react';
5
5
  import { useForm } from 'react-hook-form';
@@ -35,12 +35,8 @@ const renderModalField = (field, index, { fieldProps, getValues }) => {
35
35
  const fieldKey = getFieldKey(field, index);
36
36
  return elementType === 'input' ? (_createElement(MakeInput, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'select' ? (_createElement(MakeSelect, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'textarea' ? (_createElement(MakeTextarea, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'toggle' ? (_createElement(MakeToggle, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'text' ? (_createElement(MakeDescription, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'upload' ? (_createElement(MakeUpload, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'custom-upload' ? (_createElement(MakeCustomUpload, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'watcher' ? (_createElement(MakeWatcher, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'button' ? (_createElement(MakeButton, { ...fieldProps, key: fieldKey, element: element, getValues: getValues })) : elementType === 'table' ? (_createElement(MakeTable, { ...fieldProps, key: fieldKey, element: element })) : null;
37
37
  };
38
- const ModalFields = ({ modalReady, fieldProps, getValues, }) => {
39
- return (_jsx("div", { className: "flex flex-col gap-4 py-4", style: {
40
- overflowY: modalReady.overFlowBody ? 'auto' : undefined,
41
- height: modalReady.overFlowBody,
42
- minHeight: modalReady.minHeightBody,
43
- }, children: modalReady.fields.map((element, index) => {
38
+ const ModalFields = ({ modalReady, fieldProps, getValues, BodyComponent, bodyClassName, bodyStyle, }) => {
39
+ const bodyContent = (_jsx(_Fragment, { children: modalReady.fields.map((element, index) => {
44
40
  if (element.elementType !== 'group') {
45
41
  return renderModalField(element, index, { fieldProps, getValues });
46
42
  }
@@ -50,15 +46,36 @@ const ModalFields = ({ modalReady, fieldProps, getValues, }) => {
50
46
  const hideDiv = groupElements.every((component) => component === null);
51
47
  return (_jsxs("div", { className: `flex flex-col w-full gap-2 ${hideDiv && 'hidden'}`, children: [element.title && (_jsx("h3", { className: "font-bold border-b-2 pb-2 mb-2", children: element.title })), _jsx("div", { className: "flex gap-4 w-full", style: element.style, children: groupElements })] }, getFieldKey(element, index)));
52
48
  }) }));
49
+ const currentBodyClassName = `flex flex-col gap-4 py-4${bodyClassName ? ` ${bodyClassName}` : ''}`;
50
+ const currentBodyStyle = {
51
+ overflowY: modalReady.overFlowBody ? 'auto' : undefined,
52
+ height: modalReady.overFlowBody,
53
+ minHeight: modalReady.minHeightBody,
54
+ ...bodyStyle,
55
+ };
56
+ if (BodyComponent) {
57
+ return (_jsx(BodyComponent, { className: currentBodyClassName, style: currentBodyStyle, children: bodyContent }));
58
+ }
59
+ return (_jsx("div", { className: currentBodyClassName, style: currentBodyStyle, children: bodyContent }));
53
60
  };
54
- const ModalActions = ({ modalReady, closeHandler, manualSubmit, }) => {
61
+ const ModalActions = ({ modalReady, closeHandler, manualSubmit, FooterComponent, footerClassName, footerStyle, showFooterDivider = true, }) => {
55
62
  const { ModalButtonAction, ModalButtonCancel } = useContext(ComponentStateContext);
56
- return (_jsxs("div", { className: "flex gap-4 items-center justify-center border-t p-2", style: modalReady.actions.containerStyle, children: [modalReady.actions.cancel && (_jsx(ModalButtonCancel, { ...modalReady.actions.cancel, onClick: closeHandler })), getUseSubmit(modalReady) ? (_jsx(ModalButtonAction, { ...modalReady.actions.action, type: "submit" })) : (_jsx(ModalButtonAction, { ...modalReady.actions.action, onClick: manualSubmit, type: "button" }))] }));
63
+ const footerContent = (_jsxs(_Fragment, { children: [modalReady.actions.cancel && (_jsx(ModalButtonCancel, { ...modalReady.actions.cancel, onClick: closeHandler })), getUseSubmit(modalReady) ? (_jsx(ModalButtonAction, { ...modalReady.actions.action, type: "submit" })) : (_jsx(ModalButtonAction, { ...modalReady.actions.action, onClick: manualSubmit, type: "button" }))] }));
64
+ const currentFooterClassName = `flex gap-4 items-center justify-center p-2${showFooterDivider ? ' border-t' : ''}${footerClassName ? ` ${footerClassName}` : ''}`;
65
+ const currentFooterStyle = {
66
+ ...footerStyle,
67
+ ...modalReady.actions.containerStyle,
68
+ };
69
+ if (FooterComponent) {
70
+ return (_jsx(FooterComponent, { className: currentFooterClassName, style: currentFooterStyle, children: footerContent }));
71
+ }
72
+ return (_jsx("div", { className: currentFooterClassName, style: currentFooterStyle, children: footerContent }));
57
73
  };
58
74
  export const Modal = ({ open, close, config }) => {
59
75
  const [modalReady, setModalReady] = useState(undefined);
60
76
  const [defaultLoaded, setDefaultLoaded] = useState(false);
61
77
  const { control, handleSubmit, getValues, unregister, setValue, watch, trigger, reset, getFieldState, } = useForm();
78
+ const { ModalContainer, ModalHeader, ModalTitle, ModalBody, ModalFooter } = useContext(ComponentStateContext);
62
79
  const formValueHandler = (element) => {
63
80
  if ([
64
81
  'group',
@@ -148,6 +165,14 @@ export const Modal = ({ open, close, config }) => {
148
165
  }, [defaultLoaded, modalReady]);
149
166
  if (!modalReady)
150
167
  return null;
151
- return (_jsx(Portal, { closeTime: 200, portalOpen: open, portalTag: '#modal-portal', useBlur: modalReady.useBlur, children: _jsx("div", { className: `rounded bg-white relative w-auto h-auto min-h-[200px] min-w-[500px] ${modalReady.useBlur && 'shadow-md border border-gray-200'}`, style: modalReady.style, children: _jsxs("form", { className: "flex flex-col p-4 gap-4", autoComplete: "off", onSubmit: handleSubmit(actionHandler), children: [_jsx("h2", { className: "text-bold text-center border-b pb-4 font-semibold", children: modalReady.title }), _jsx(ModalFields, { modalReady: modalReady, fieldProps: fieldProps, getValues: getValues }), _jsx(ModalActions, { modalReady: modalReady, closeHandler: closeHandler, manualSubmit: manualSubmit })] }) }) }));
168
+ const { container: containerLayout, header: headerLayout, title: titleLayout, body: bodyLayout, footer: footerLayout, } = modalReady.layout ?? {};
169
+ const showHeaderDivider = headerLayout?.showDivider !== false;
170
+ const showFooterDivider = footerLayout?.showDivider !== false;
171
+ const headerClassName = `w-full text-center${showHeaderDivider ? ' border-b pb-4' : ''}${headerLayout?.className ? ` ${headerLayout.className}` : ''}`;
172
+ const titleClassName = `font-semibold${titleLayout?.className ? ` ${titleLayout.className}` : ''}`;
173
+ const titleContent = ModalTitle ? (_jsx(ModalTitle, { className: titleClassName, style: titleLayout?.style, children: modalReady.title })) : (_jsx("h2", { className: titleClassName, style: titleLayout?.style, children: modalReady.title }));
174
+ const headerContent = ModalHeader ? (_jsx(ModalHeader, { className: headerClassName, style: headerLayout?.style, children: titleContent })) : (_jsx("div", { className: headerClassName, style: headerLayout?.style, children: titleContent }));
175
+ const modalContent = (_jsxs("form", { className: "flex flex-col p-4 gap-4", autoComplete: "off", onSubmit: handleSubmit(actionHandler), children: [headerContent, _jsx(ModalFields, { modalReady: modalReady, fieldProps: fieldProps, getValues: getValues, BodyComponent: ModalBody, bodyClassName: bodyLayout?.className, bodyStyle: bodyLayout?.style }), _jsx(ModalActions, { modalReady: modalReady, closeHandler: closeHandler, manualSubmit: manualSubmit, FooterComponent: ModalFooter, footerClassName: footerLayout?.className, footerStyle: footerLayout?.style, showFooterDivider: showFooterDivider })] }));
176
+ return (_jsx(Portal, { closeTime: 200, portalOpen: open, portalTag: '#modal-portal', useBlur: modalReady.useBlur, children: ModalContainer ? (_jsx(ModalContainer, { className: `rounded bg-white relative w-auto h-auto min-h-[200px] min-w-[500px]${modalReady.useBlur ? ' shadow-md border border-gray-200' : ''}${containerLayout?.className ? ` ${containerLayout.className}` : ''}`, style: { ...modalReady.style, ...containerLayout?.style }, children: modalContent })) : (_jsx("div", { className: `rounded bg-white relative w-auto h-auto min-h-[200px] min-w-[500px]${modalReady.useBlur ? ' shadow-md border border-gray-200' : ''}${containerLayout?.className ? ` ${containerLayout.className}` : ''}`, style: { ...modalReady.style, ...containerLayout?.style }, children: modalContent })) }));
152
177
  };
153
178
  export default Modal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamic-modal",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "The dynamic-modal is a solution of creation different modals into project using a json configuration file",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",