Commit 11ccd0eb authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(plugins): add group field type + multiple option to render field arrays

- update docs
- render hint and helper options for all fields
- replace option's hint with
description
parent f50098ec
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import "./modules/video-clip-previewer";
import VideoClipBuilder from "./modules/VideoClipBuilder";
import "./modules/xml-editor";
import "@patternfly/elements/pf-tabs/pf-tabs.js";
import FieldArray from "./modules/FieldArray";

Dropdown();
Tooltip();
@@ -39,3 +40,4 @@ PublishMessageWarning();
HotKeys();
ValidateFileSize();
VideoClipBuilder();
FieldArray();
+159 −0
Original line number Diff line number Diff line
import Tooltip from "./Tooltip";

const FieldArray = (): void => {
  const fieldArrays: NodeListOf<HTMLElement> =
    document.querySelectorAll("[data-field-array]");

  for (let i = 0; i < fieldArrays.length; i++) {
    const fieldArray = fieldArrays[i];
    const fieldArrayContainer = fieldArray.querySelector(
      "[data-field-array-container]"
    );
    const items: NodeListOf<HTMLElement> = fieldArray.querySelectorAll(
      "[data-field-array-item]"
    );
    const addButton = fieldArray.querySelector(
      "button[data-field-array-add]"
    ) as HTMLButtonElement;

    const deleteButtons: NodeListOf<HTMLButtonElement> =
      fieldArray.querySelectorAll("[data-field-array-delete]");

    deleteButtons.forEach((deleteBtn) => {
      deleteBtn.addEventListener("click", (e) => {
        e.preventDefault();
        deleteBtn.blur();
        fieldArrayContainer
          ?.querySelector(
            `[data-field-array-item="${deleteBtn.dataset.fieldArrayDelete}"]`
          )
          ?.remove();
      });
    });

    // create base element to clone
    const baseItem = items[0].cloneNode(true) as HTMLElement;

    const elements: NodeListOf<HTMLFormElement> = baseItem.querySelectorAll(
      "input, select, textarea"
    );

    elements.forEach((element) => {
      element.value = "";
    });

    if (fieldArrayContainer && addButton) {
      addButton.addEventListener("click", (event) => {
        event.preventDefault();

        const newItem = baseItem.cloneNode(true) as HTMLElement;

        const deleteBtn: HTMLButtonElement | null = newItem.querySelector(
          "button[data-field-array-delete]"
        );

        if (deleteBtn) {
          deleteBtn.addEventListener("click", () => {
            deleteBtn.blur();
            newItem.remove();
          });

          fieldArrayContainer.appendChild(newItem);
          newItem.scrollIntoView({
            behavior: "auto",
            block: "center",
            inline: "center",
          });

          // reload tooltip module for showing remove button label
          Tooltip();

          // focus to first form element if mouse click
          if (event.screenX !== 0 && event.screenY !== 0) {
            const elements: NodeListOf<HTMLFormElement> =
              newItem.querySelectorAll("input, select, textarea");

            if (elements.length > 0) {
              elements[0].focus();
            }
          }
        }
      });

      const updateIndexes = () => {
        // get last child item to set item count
        const items: NodeListOf<HTMLElement> =
          fieldArrayContainer.querySelectorAll("[data-field-array-item]");

        let itemIndex = 0;
        items.forEach((item) => {
          const itemNumber: HTMLElement | null = item.querySelector(
            "[data-field-array-number]"
          );

          if (itemNumber) {
            itemNumber.innerHTML = "#";
            const indexNum = itemIndex + 1;
            if (item.dataset.fieldArrayItem !== itemIndex.toString()) {
              item.classList.add("motion-safe:animate-single-pulse");
              setTimeout(() => {
                item.classList.remove("motion-safe:animate-single-pulse");
                itemNumber.innerHTML = indexNum.toString();
              }, 300);
            } else {
              itemNumber.innerHTML = indexNum.toString();
            }
          }

          item.dataset.fieldArrayItem = itemIndex.toString();
          const deleteBtn = item.querySelector(
            "button[data-field-array-delete]"
          ) as HTMLButtonElement | null;

          if (deleteBtn) {
            deleteBtn.dataset.fieldArrayDelete = itemIndex.toString();
          }

          const itemElements: NodeListOf<HTMLFormElement> =
            item.querySelectorAll("input, select, textarea");

          itemElements.forEach((element) => {
            const label: HTMLLabelElement | null = item.querySelector(
              `label[for="${element.id}"]`
            );

            const elementID = element.name.replace(
              /(.*\[)\d+?(\].*)/g,
              `$1${itemIndex}$2`
            );

            if (label) {
              label.htmlFor = elementID;
            }

            element.id = elementID;
            element.name = elementID;
          });

          itemIndex++;
        });
      };

      // add mutation observer to run index updates when field array
      // items are added or removed
      const callback = function (mutationList: MutationRecord[]) {
        for (const mutation of mutationList) {
          if (mutation.type === "childList") {
            updateIndexes();
          }
        }
      };

      const observer = new MutationObserver(callback);

      observer.observe(fieldArrayContainer, { childList: true });
    }
  }
};

export default FieldArray;
+19 −0
Original line number Diff line number Diff line
@layer base {
  html {
    scroll-behavior: smooth;
  }

  .form-helper {
    @apply text-skin-muted;
  }
}

@layer components {
  .post-content {
    & a {
@@ -78,4 +88,13 @@
      #facc15 20px
    );
  }

  .divide-fieldset-y > :not([hidden], legend) ~ :not([hidden], legend) {
    @apply pt-4;

    --tw-divide-y-reverse: 0;

    border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
    border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
  }
}
+16 −6
Original line number Diff line number Diff line
@layer components {
  .form-radio-btn {
    @apply absolute mt-3 ml-3 border-contrast border-3 text-accent-base;
    @apply absolute right-4 top-4 border-contrast border-3 text-accent-base;

    &:focus {
      @apply ring-accent;
    }

    &:checked {
      @apply ring-2 ring-contrast;

      & + label {
        @apply text-accent-contrast bg-accent-base;
        @apply text-accent-hover bg-base border-accent-base shadow-none;
      }

      & + label .form-radio-btn-description {
        @apply text-accent-base;
      }
    }

    & + label {
      @apply inline-flex items-center py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
      @apply h-full w-full inline-flex flex-col items-start py-3 px-4 text-sm font-bold rounded-lg cursor-pointer border-contrast bg-elevated border-3 transition-all;

      box-shadow: 2px 2px 0 hsl(var(--color-border-contrast));
    }

    & + label span {
      @apply pr-8;
    }

      color: hsl(var(--color-text-muted));
    & + label .form-radio-btn-description {
      @apply font-normal text-xs text-skin-muted text-balance;
    }
  }
}
+20 −2
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ class Checkbox extends FormComponent

    protected string $hint = '';

    protected string $helper = '';

    protected bool $isChecked = false;

    #[Override]
@@ -37,10 +39,26 @@ class Checkbox extends FormComponent
            'slot'  => $this->hint,
        ]))->render();

        $this->mergeClass('inline-flex items-center');
        $this->mergeClass('inline-flex items-start gap-x-2');

        $helperText = '';
        if ($this->helper !== '') {
            $helperId = $this->name . 'Help';
            $helperText = (new Helper([
                'id'    => $helperId,
                'slot'  => $this->helper,
                'class' => '-mt-1',
            ]))->render();
            $this->attributes['aria-describedby'] = $helperId;
        }

        return <<<HTML
            <label {$this->getStringifiedAttributes()}>{$checkboxInput}<span class="ml-2">{$this->slot}{$hint}</span></label>
            <label {$this->getStringifiedAttributes()}>{$checkboxInput}
                <div class="flex flex-col">
                    <span>{$this->slot}{$hint}</span>
                    {$helperText}
                </div>
            </label>
        HTML;
    }
}
Loading