icon-selector/custom-icon/add-custom-icon-modal/add-custom-icon-modal.component.ts
<c8y-modal
  [title]="modalTitle() | translate"
  [headerClasses]="'dialog-header'"
  (onDismiss)="cancel()"
  (onClose)="save()"
  [close]="keepOpenOnOk"
  [labels]="{ cancel: 'Cancel', ok: okLabel() | translate }"
>
  <ng-container c8y-modal-title>
    <span
      [c8yIcon]="isOverrideMode() ? 'replace' : isEditMode() ? 'pencil' : 'plus-circle-o'"
      aria-hidden="true"
    ></span>
  </ng-container>

  @if (!isReady()) {
    <!-- min-height matches the approximate form height so Bootstrap animates
         the modal at its full size before the form is rendered. -->
    <div
      class="p-32 d-flex a-i-center j-c-center"
      style="min-height: 380px"
      [attr.aria-label]="'Loading…' | translate"
      role="status"
    >
      <c8y-loading></c8y-loading>
    </div>
  }

  @if (isReady()) {
    <div
      class="p-t-16 p-b-24 p-l-32 p-r-32"
      #modalBody
    >
      @if (isOverrideMode()) {
        <p class="text-center text-16 text-bold text-balance m-b-16">
          {{ 'Upload a replacement SVG for {{iconName}}' | translate: { iconName:
          editingIcon?.overriddenIconName } }}
        </p>
      }

      <!-- Name, collection and icon type (hidden in override mode) -->
      @if (!isOverrideMode()) {
        @if (!isEditMode()) {
          <p
            class="text-muted m-b-16"
            translate
          >
            Upload a custom SVG icon and assign it to a collection to keep your icons organized.
          </p>
        }
        <div
          class="m-b-16"
          [formGroup]="form"
        >
          <c8y-form-group class="p-b-16">
            <label
              for="iconName"
              translate
              >Icon name</label
            >
            <input
              class="form-control"
              id="iconName"
              aria-required="true"
              aria-describedby="iconName-messages"
              type="text"
              [placeholder]="'e.g. {{ example }}' | translate: { example: 'acme-logo' }"
              formControlName="name"
            />
            <c8y-messages
              id="iconName-messages"
              role="status"
              [helpMessage]="'Lowercase letters, numbers and hyphens only' | translate"
            >
              @for (message of nameMessages; track message.name) {
                <c8y-message
                  [name]="message.name"
                  [text]="message.text | translate"
                ></c8y-message>
              }
            </c8y-messages>
          </c8y-form-group>

          <c8y-form-group [hasError]="hasCategoryError()">
            <label
              for="iconCategory"
              translate
              >Collection</label
            >
            <div class="p-relative">
              <c8y-typeahead
                [attr.aria-label]="'Icon collection' | translate"
                id="iconCategory"
                #categoryTypeahead
                [allowFreeEntries]="true"
                [hideNew]="true"
                [placeholder]="'Select or enter a collection name' | translate"
                (onSearch)="onCategorySearch($event)"
              >
                @for (cat of filteredCategories(); track cat) {
                  <c8y-li
                    class="p-l-8 p-r-8 c8y-list__item--link"
                    (click)="selectCategory(cat)"
                    [active]="form.value.category === cat"
                    >{{ cat | translate }}</c8y-li
                  >
                }
                @if (showAddNew()) {
                  <c8y-li class="bg-level-2 p-8">
                    @if (hasNoMatch()) {
                      <span
                        class="m-r-8"
                        translate
                        >No existing collection found.</span
                      >
                    }
                    <button
                      class="btn btn-default btn-xs"
                      type="button"
                      (click)="selectCategory(categorySearch())"
                    >
                      {{ 'Add new collection`collection`' | translate }}
                    </button>
                  </c8y-li>
                }
              </c8y-typeahead>
              @if (categorySearch()) {
                <button
                  class="btn btn-dot p-absolute"
                  style="right: 36px; z-index: 10"
                  [attr.aria-label]="'Clear collection' | translate"
                  type="button"
                  (click)="clearCategory()"
                >
                  <i
                    [c8yIcon]="'times'"
                    aria-hidden="true"
                  ></i>
                </button>
              }
            </div>
            <c8y-messages
              role="alert"
              [show]="categoryErrors()"
            >
              <c8y-message
                [name]="'required'"
                [text]="'Collection is required.' | translate"
              ></c8y-message>
            </c8y-messages>
          </c8y-form-group>

          @if (!isEditMode()) {
            <fieldset class="c8y-fieldset m-b-24">
              <legend translate>Icon type</legend>
              <div class="d-flex gap-16 p-b-16">
                <label class="c8y-radio a-i-start">
                  <input
                    name="iconFamily"
                    type="radio"
                    [checked]="selectedFamily() === 'dlt'"
                    (change)="onFamilyChange('dlt')"
                  />
                  <span></span>
                  <span
                    >{{ 'Single color' | translate }}<br />
                    <small
                      class="text-muted d-block"
                      translate
                    >
                      Single SVG, inherits text color via currentColor.
                    </small>
                  </span>
                </label>

                <label class="c8y-radio a-i-start">
                  <input
                    name="iconFamily"
                    type="radio"
                    [checked]="selectedFamily() === 'c8y'"
                    (change)="onFamilyChange('c8y')"
                  />
                  <span></span>
                  <span>
                    {{ 'Duocolor' | translate }}<br />
                    <small
                      class="text-muted d-block"
                      translate
                    >
                      Two SVGs required: a dark layer and a light accent layer.
                    </small>
                  </span>
                </label>
              </div>
            </fieldset>
          }
        </div>
      }

      <!-- SVG uploads -->
      <div
        class="m-b-16"
        [class.row]="selectedFamily() === 'c8y'"
      >
        @if (selectedFamily() === 'dlt') {
          <c8y-form-group [hasError]="hasPrimarySvgError()">
            <label translate>Icon SVG</label>
            @if (!primarySvg()) {
              <c8y-drop-area
                class="drop-area-sm"
                [icon]="'upload'"
                [title]="'Upload SVG icon' | translate"
                [attr.aria-label]="'Upload SVG icon' | translate"
                [accept]="'.svg'"
                [maxAllowedFiles]="1"
                [maxFileSizeInMegaBytes]="5"
                [message]="'Drop SVG file here or click to browse' | translate"
                (dropped)="onPrimaryDropped($event)"
              ></c8y-drop-area>
              <c8y-messages
                class="m-t-8"
                role="status"
                [show]="primarySvgErrors()"
                [helpMessage]="
                  hasPrimarySvgError()
                    ? ''
                    : ('Square viewBox (e.g. 24×24) · currentColor fills · no external refs or scripts'
                      | translate)
                "
              >
                <c8y-message
                  [name]="'required'"
                  [text]="'Upload an SVG file.' | translate"
                ></c8y-message>
                @if (primaryValidation(); as pv) {
                  @for (error of pv.errors; track error) {
                    <div class="d-block text-danger">{{ error }}</div>
                  }
                  @for (warning of pv.warnings; track warning) {
                    <div class="d-block text-warning">{{ warning }}</div>
                  }
                }
              </c8y-messages>
            } @else {
              <fieldset class="c8y-fieldset m-b-8">
                <legend class="d-flex a-i-center">
                  <span translate>Preview</span>
                  <button
                    class="btn btn-dot btn-dot--danger m-l-8"
                    [attr.aria-label]="'Remove SVG preview' | translate"
                    [tooltip]="'Remove SVG preview' | translate"
                    placement="top"
                    container="body"
                    [delay]="500"
                    (click)="clearPrimary()"
                  >
                    <i
                      [c8yIcon]="'minus-circle'"
                      aria-hidden="true"
                    ></i>
                  </button>
                </legend>
                <div class="row d-flex a-i-center gap-16 p-b-16">
                  <div
                    class="col-sm-6 d-flex a-i-end gap-8 j-c-around p-16 c8y-light-theme"
                    style="
                      background: var(--c8y-root-component-background-default);
                      border: 1px solid var(--c8y-root-component-border-color);
                      border-radius: var(--c8y-component-border-radius-base, 4px);
                    "
                  >
                    @for (size of previewSizes; track size) {
                      <div class="d-flex d-col a-i-center">
                        <div
                          class="d-flex a-i-center j-c-center text-default"
                          [style.width.px]="size"
                          [style.height.px]="size"
                          aria-hidden="true"
                          [innerHTML]="primaryPreviewHtml()"
                        ></div>
                        <small class="text-muted">{{ size }}</small>
                      </div>
                    }
                  </div>
                  <div
                    class="col-sm-6 d-flex a-i-end gap-16 j-c-around p-16 c8y-dark-theme"
                    style="
                      background: var(--c8y-root-component-background-default);
                      border: 1px solid var(--c8y-root-component-border-color);
                      border-radius: var(--c8y-component-border-radius-base, 4px);
                    "
                  >
                    @for (size of previewSizes; track size) {
                      <div class="d-flex d-col a-i-center">
                        <div
                          class="d-flex a-i-center j-c-center text-default"
                          [style.width.px]="size"
                          [style.height.px]="size"
                          aria-hidden="true"
                          [innerHTML]="primaryPreviewHtml()"
                        ></div>
                        <small class="text-muted">{{ size }}</small>
                      </div>
                    }
                  </div>
                </div>
                @if (primaryValidation(); as pv) {
                  @if (pv.errors.length || pv.warnings.length) {
                    <small
                      class="form-control-feedback-message"
                      role="status"
                    >
                      @for (error of pv.errors; track error) {
                        <div class="d-block text-danger">{{ error }}</div>
                      }
                      @for (warning of pv.warnings; track warning) {
                        <div class="d-block text-warning">{{ warning }}</div>
                      }
                    </small>
                  }
                }
              </fieldset>
            }
          </c8y-form-group>
        } @else {
          <!-- C8Y duocolor: dark + light uploads side by side -->
          <div class="row d-flex-md gap-16">
            <div class="col-sm-6">
              <c8y-form-group [hasError]="hasPrimarySvgError()">
                <label translate>Dark layer SVG</label>
                @if (!primarySvg()) {
                  <c8y-drop-area
                    class="drop-area-sm"
                    [icon]="'upload'"
                    [attr.aria-label]="'Upload dark layer SVG' | translate"
                    [accept]="'.svg'"
                    [maxAllowedFiles]="1"
                    [maxFileSizeInMegaBytes]="5"
                    [message]="'Drop dark layer SVG' | translate"
                    (dropped)="onPrimaryDropped($event)"
                  ></c8y-drop-area>
                  <c8y-messages
                    class="m-t-8"
                    role="status"
                    [show]="primarySvgErrors()"
                    [helpMessage]="
                      hasPrimarySvgError()
                        ? ''
                        : ('Square viewBox · currentColor fills · main icon shape' | translate)
                    "
                  >
                    <c8y-message
                      [name]="'required'"
                      [text]="'Upload an SVG file.' | translate"
                    ></c8y-message>
                    @if (primaryValidation(); as pv) {
                      @for (error of pv.errors; track error) {
                        <div class="d-block text-danger">{{ error }}</div>
                      }
                      @for (warning of pv.warnings; track warning) {
                        <div class="d-block text-warning">{{ warning }}</div>
                      }
                    }
                  </c8y-messages>
                } @else {
                  <div class="d-flex a-i-center gap-8 p-8 separator m-b-4">
                    <div
                      class="d-flex a-i-center j-c-center"
                      [style.width.px]="32"
                      [style.height.px]="32"
                      aria-hidden="true"
                      [innerHTML]="primaryPreviewHtml()"
                    ></div>
                    <span
                      class="text-muted text-12 flex-grow"
                      translate
                      >Dark layer</span
                    >
                    <button
                      class="btn btn-dot btn-dot--danger"
                      [attr.aria-label]="'Remove dark layer SVG' | translate"
                      [tooltip]="'Remove dark layer SVG' | translate"
                      placement="top"
                      container="body"
                      [delay]="500"
                      (click)="clearPrimary()"
                    >
                      <i
                        [c8yIcon]="'minus-circle'"
                        aria-hidden="true"
                      ></i>
                    </button>
                  </div>
                  @if (primaryValidation(); as pv) {
                    @if (pv.errors.length || pv.warnings.length) {
                      <small
                        class="form-control-feedback-message"
                        role="status"
                      >
                        @for (error of pv.errors; track error) {
                          <div class="d-block text-danger">{{ error }}</div>
                        }
                        @for (warning of pv.warnings; track warning) {
                          <div class="d-block text-warning">{{ warning }}</div>
                        }
                      </small>
                    }
                  }
                }
              </c8y-form-group>
            </div>
            <div class="col-sm-6">
              <c8y-form-group [hasError]="hasLightSvgError()">
                <label translate>Light layer SVG</label>
                @if (!lightSvg()) {
                  <c8y-drop-area
                    class="drop-area-sm"
                    [icon]="'upload'"
                    [attr.aria-label]="'Upload light layer SVG' | translate"
                    [accept]="'.svg'"
                    [maxAllowedFiles]="1"
                    [maxFileSizeInMegaBytes]="5"
                    [message]="'Drop light layer SVG' | translate"
                    (dropped)="onLightDropped($event)"
                  ></c8y-drop-area>
                  <c8y-messages
                    class="m-t-8"
                    role="status"
                    [show]="lightSvgErrors()"
                    [helpMessage]="
                      hasLightSvgError()
                        ? ''
                        : ('Same viewBox as dark layer · accent shape for the light tint'
                          | translate)
                    "
                  >
                    <c8y-message
                      [name]="'required'"
                      [text]="'Upload an SVG file.' | translate"
                    ></c8y-message>
                    @if (lightValidation(); as lv) {
                      @for (error of lv.errors; track error) {
                        <div class="d-block text-danger">{{ error }}</div>
                      }
                      @for (warning of lv.warnings; track warning) {
                        <div class="d-block text-warning">{{ warning }}</div>
                      }
                    }
                  </c8y-messages>
                } @else {
                  <div class="d-flex a-i-center gap-8 p-8 separator m-b-4">
                    <div
                      class="d-flex a-i-center j-c-center"
                      [style.width.px]="32"
                      [style.height.px]="32"
                      aria-hidden="true"
                      [innerHTML]="lightPreviewHtml()"
                    ></div>
                    <span
                      class="text-muted text-12 flex-grow"
                      translate
                      >Light layer</span
                    >
                    <button
                      class="btn btn-dot btn-dot--danger"
                      [attr.aria-label]="'Remove light layer SVG' | translate"
                      [tooltip]="'Remove light layer SVG' | translate"
                      placement="top"
                      container="body"
                      [delay]="500"
                      (click)="clearLight()"
                    >
                      <i
                        [c8yIcon]="'minus-circle'"
                        aria-hidden="true"
                      ></i>
                    </button>
                  </div>
                  @if (lightValidation(); as lv) {
                    @if (lv.errors.length || lv.warnings.length) {
                      <small
                        class="form-control-feedback-message"
                        role="status"
                      >
                        @for (error of lv.errors; track error) {
                          <div class="d-block text-danger">{{ error }}</div>
                        }
                        @for (warning of lv.warnings; track warning) {
                          <div class="d-block text-warning">{{ warning }}</div>
                        }
                      </small>
                    }
                  }
                }
              </c8y-form-group>
            </div>
          </div>

          @if (showCompositePreview()) {
            <fieldset
              class="c8y-fieldset m-t-16"
              aria-hidden="true"
            >
              <legend translate>Composited preview</legend>
              <div class="row d-flex-md gap-16 p-b-16">
                <!-- Light background -->
                <div
                  class="col-sm-6 d-flex a-i-end gap-8 j-c-around p-16 c8y-light-theme"
                  style="
                    background: var(--c8y-root-component-background-default);
                    border: 1px solid var(--c8y-root-component-border-color);
                    border-radius: var(--c8y-component-border-radius-base, 4px);
                  "
                >
                  @for (size of previewSizes; track size) {
                    <div class="d-flex d-col a-i-center">
                      <div
                        class="p-relative"
                        [style.width.px]="size"
                        [style.height.px]="size"
                      >
                        <div
                          class="d-flex a-i-center j-c-center fit-w fit-h"
                          style="color: var(--c8y-root-component-icon-dark-color-dark)"
                          [innerHTML]="primaryPreviewHtml()"
                        ></div>
                        <div
                          class="d-flex a-i-center j-c-center fit-w fit-h p-absolute"
                          style="
                            top: 0;
                            left: 0;
                            color: var(--c8y-root-component-icon-dark-color-light);
                          "
                          [innerHTML]="lightPreviewHtml()"
                        ></div>
                      </div>
                      <small class="text-muted">{{ size }}</small>
                    </div>
                  }
                </div>
                <!-- Dark background -->
                <div
                  class="col-sm-6 d-flex a-i-end gap-8 j-c-around p-16 c8y-dark-theme"
                  style="
                    background: var(--c8y-root-component-background-default);
                    border-radius: var(--c8y-component-border-radius-base, 4px);
                  "
                >
                  @for (size of previewSizes; track size) {
                    <div class="d-flex d-col a-i-center">
                      <div
                        class="p-relative"
                        [style.width.px]="size"
                        [style.height.px]="size"
                      >
                        <div
                          class="d-flex a-i-center j-c-center fit-w fit-h"
                          style="color: var(--c8y-root-component-icon-dark-color-dark)"
                          [innerHTML]="primaryPreviewHtml()"
                        ></div>
                        <div
                          class="d-flex a-i-center j-c-center fit-w fit-h p-absolute"
                          style="
                            top: 0;
                            left: 0;
                            color: var(
                              --c8y-component-icon-dark-color-light,
                              var(--c8y-root-component-icon-dark-color-light)
                            );
                          "
                          [innerHTML]="lightPreviewHtml()"
                        ></div>
                      </div>
                      <small class="text-muted">{{ size }}</small>
                    </div>
                  }
                </div>
              </div>
            </fieldset>
          }
        }
      </div>
    </div>
  }
</c8y-modal>

results matching ""

    No results matching ""