/* Form layout */

@import "./variables.scss";

:root {
  // CSS variable to define the number of (label + control) columns.
  // It dynamically changes on narrow pages (see media query below).
  --ct-form-columns: 2;
}

// Cockpit Form Layout: Automatically have Cockpit display your form in
// an optimal layout.
//
// By default, all labels are aligned and sized properly and form elements
// stretch to take up the remaining space.
//
//
// There are additional classes and attributes you can add to each
// control directly under `ct-form`:
//
// `ct-form-split`: The grid can be split on a `form-control`
// level by adding a this class. If you want two elements next to each
// other, both should have this class. Widths are equal by default.
// See ct-form-minmax & ct-form-maxmin for alternate sizing.
//
// `ct-form-relax`: Form elements normally stretch to take up the
// full space. You can relax their width by adding this class to the
// control. Inputs with a size attribute are auto-relaxed and do not
// need this class.
//
// `ct-form-stretch`: If a control has a width specified
// elsewhere, you can force it to stretch. This is mainly useful when
// using <div role="group"> to group elements.
//
// `ct-form-full`: Force a widget to be the full width of the form,
// invading the label space.
//
// role="group": When there are two related elements, such as a text
// input and a dropdown, you can group them together using this HTML
// attribute. It's similar in purpose to a <fieldset>, but works for
// layouts in Chrome (unlike fieldset). This can be attached to any
// container element, but will most likely be used with <div>. The role
// adds semantic meaning to the element for screen readers, and we key
// the CSS off of the role.
//
// `ct-form-box`: Visual styling for encapsulating a block of sub-options.
// Creates a gray box around elements.
//
// <hr>: While this is an element, it has a special meaning and is used
// to add some vertical spacing to a form.
//
//
// Alternate grid sizing:
// You can override division of space for controls by adding a class
// at grid level (.ct-form) to adjust size for "split" widgets:
// `ct-form-maxmin: First widget is wide; second is small.
// `ct-form-minmax`: First widget is small; second is wide.
//
//
// Most of the time, you can simply ignore all the optional classes (and
// attribute and hr element) and simply wrap your labels & controls in
// a <form class="ct-form"> and layout magic happens.

.ct-form {
  // Locally redefine padding to Bootstrap values for this SASS block
  --padding-y: var(--pf-global--spacer--md);
  --padding-x: var(--pf-global--spacer--lg);
  // Bootstrap & PatternFly use a 1px border around widgets
  --border-width: 1px;
  --widget-height: 2.25rem; // (36px for PF4 widgets)

  align-self: start; // Don't vertically fill content by default
  display: grid;
  grid-gap: var(--padding-y) var(--padding-x);
  // Repeat a label that is a minimum of 4em and its control that
  // fills the remaining space by a CSS variable (default: 2)
  grid-template-columns: repeat(var(--ct-form-columns), max-content 1fr);
  justify-items: stretch;
  align-content: baseline;

  // All <label> elements describing form elements in PatternFly are
  // supposed to have a `control-label` class (PF3) or `pf-c-form__label` (PF4).
  // These precede control elements.
  > .control-label,
  > .pf-c-form__label {
    padding: 0;
    margin: 0;
    text-align: left;
    font-weight: var(--pf-global--FontWeight--bold);
    font-size: var(--pf-global--FontSize--sm);
  }

  > :not(hr):not(p) {
    line-height: var(--widget-height);
  }

  > p {
    margin: 0;
  }

  // Put all control elements to the right of the labels,
  // stretching to the rightmost column
  > :not(.control-label):not(.pf-c-form__label):not(hr):not(.ct-form-split):not(.ct-form-full) {
    grid-column: 2 / -1;
  }

  // Auto-stretch elements to the grid (except when relaxed)
  > :not(.ct-form-relax):not(.spinner) {
    width: auto;
  }

  // Horizontal rules directly under a form-layout container serve to
  // add some vertical space in forms. This is useful for visually
  // grouping similar elements with whitespace.
  //
  // It's not the same as actually grouping elements (which can be done
  // in the usual ways as well as adding a role="group".
  > hr {
    border: none;
    grid-column: 1 / -1;
    height: 0;
    // Reset padding to ensure all browsers treat this the same
    margin: 0;
    padding: 0;
  }

  // Auto-relax inputs with size
  > input[size],
  > .ct-validation-wrapper > input[size] {
    justify-self: start;
  }

  > .ct-validation-wrapper {
    display: flex;
    flex-direction: column;
  }

  // Hack to allow number inputs to be sized on WebKit-based browsers
  input[type=number] {
    -webkit-appearance: textarea;
  }

  // Special considerations for widgets (and widget-like elements)
  // This is a SASS mixin that will not be in the compiled CSS.
  @mixin widget-rules() {
    > input,
    > textarea,
    > select,
    > .bootstrap-select,
    > .ct-select,
    > .dropdown,
    > .combobox-container,
    > fieldset,
    > [role=group],
    > [data-field],
    > .form-group,
    > .btn-group,
    > .pf-c-form__group-control,
    > label.checkbox,
    > label.radio,
    > label.pf-c-radio,
    > label.pf-c-check,
    > .checkbox-inline,
    > .radio-inline {
      line-height: var(--pf-global--LineHeight--md);
    }
  }

  &, > .ct-validation-wrapper {
    @include widget-rules();
  }

  // Some elements need special width considerations
  // as PatternFly normally fixes the width
  > :not(.ct-form-relax):not(.spinner) {
    width: auto !important;
  }

  // Elements with role="group" are used to group elements —
  // fieldset was going to be used, but Chrome doesn't allow
  // grid or flex placement for fieldsets (yet).
  //
  // Adding a group role is the same thing accessibilty-wise
  // and lets us target all browsers properly.
  //
  // You can use this like:
  // <div role="group">
  //
  // And non-div elements are also supported.
  > [role=group],
  > .ct-validation-wrapper > [role=group] {
    align-self: start;
    align-content: center;
    display: grid;
    grid-gap: var(--padding-y);
    min-height: var(--widget-height);
    justify-content: start;
    // Only support 2 splits for now (can change to 3 later, if needed)
    grid-template-columns: repeat(2, auto);

    &.ct-form-vertical {
      > :not(.ct-form-split) {
        // Stretch across the grid (unless it's a split)
        grid-column: 1 / -1;
      }
    }

    > .checkbox,
    > .radio {
        // Spacing is handled by grid, not margin
        margin: 0;

        &:first-child {
          margin-top: 0.5rem;
        }
    }
  }

  > [role=group],
  > .ct-validation-wrapper > [role=group],
  > .ct-validation-wrapper > [data-field] {
    // Allow dropdowns to expand as needed
    &:not(.ct-form-relax) {
      > .dropdown {
        width: auto !important;
      }

      // <select>s need to be coaxed to be 100%
      > .ct-select {
        width: 100%;
      }
    }
  }

  // Vertically align checkboxes and radios properly using flex
  label.checkbox,
  label.radio,
  .checkbox > label,
  .radio > label,
  .checkbox-inline,
  .radio-inline {
    display: inline-flex;
    padding-left: 0;
    padding-right: var(--padding-x);
    align-items: center;

    > input[type="checkbox"],
    > input[type="radio"] {
      margin: 0 0.5em 0 0;
      position: static;
    }
  }

  // Remove vertical spacing for fieldsets,
  // as this is handled by the grid gap
  fieldset {
    > .checkbox,
    > .radio {
      &:first-child {
        margin-top: 0;
      }

      &:last-child {
        margin-bottom: 0;
      }
    }
  }

  // List groups override the grid gap, so we're adding it manually
  .list-group {
    margin-bottom: var(--padding-y);
  }

  // Relax split elements to only take up one column
  > .ct-form-split {
    grid-column: auto / auto;
  }

  // Stretch to full width
  > .ct-form-full {
    grid-column: 1 / -1;
  }

  // Move warnings, errors, info, etc. up a bit to associate with previous field
  > .help-block {
    position: relative;
    margin-top: -0.5rem;
  }

  .help-block {
    --help-line-height: calc(var(--pf-global--LineHeight--md) * 1rem);
    line-height: var(--help-line-height);

    &:empty {
      display: none;
    }

    .spinner {
      position: relative;
      // (baseline - height - border) / 2
      top: calc((var(--help-line-height) - 16px - 2px) / 2);
    }
  }

  .ct-form-box {
    background: var(--color-gray-1);
    border-width: 1px;
    border-style: solid;
    border-color: var(--color-gray-5);
    padding: 0.5rem;
    padding-top: 1rem;
    width: 100%;
  }
}

// Force a form element to stretch. Add as a class to `form-control`.
.ct-form-stretch {
  justify-content: stretch !important;
}

// Instruct a `form-control` to not stretch.
.ct-form-relax {
  justify-self: start;
}

// Reset .ct-form-split for small dialogs, as they don't have
// much width. This allows for using the same HTML layout in both
// narrow and normal dialogs.
.modal-dialog.modal-sm .ct-form > .ct-form-split {
  grid-column: 2 / -1;
}

@mixin deconstruct() {
  // Only use one column
  --ct-form-columns: 1;
  // Don't set the line height for widgets;
  // setting this to an invalid CSS rule invalidates it, so it falls back
  --widget-height: not-needed;
  // Completely deconstruct the grid layout
  grid-template-columns: initial;

  > * {
    // Don't restrict grid placement
    grid-column: auto;
    max-width: 100%;
  }

  // As control labels fill the row, left align and remove padding
  > .control-label,
  > .pf-c-form__label {
    margin: 0 0 -.5rem;
    padding: 1rem 0 0;
    text-align: left;
  }
}

@media (max-width: 640px) {
  // When inside of lists or modals & the page isn't wide enough,
  // collapse (label + control) columns down to 1, to force splits on
  // their own lines
  .listing-ct-body,
  .modal {
    .ct-form {
      @include deconstruct();
    }
  }
}

// Alternate layout, for a split, used at ct-form grid-level:
// First form widget is as small as possible;
// Second takes up the rest of the space
.ct-form-minmax {
  grid-template-columns: max-content min-content max-content 1fr;
}

// Alternate layout, for a split, used at ct-form grid-level:
// First form widget takes up as much space as it can;
// Second form widget is as small as possible
.ct-form-maxmin {
  grid-template-columns: max-content 1fr max-content min-content;
}

// Alternate layout, vertical form approach, used at ct-form grid-level
// Similar to the responsive layout, but used even for large sizes.
.ct-form-vertical {
  @include deconstruct();
}

// Alternate to reduce spacing, used at ct-form grid-level
.ct-form-compact {
  --padding-y: var(--pf-global--spacer--xs);
}

// Compact form used for info tables
.ct-form-info {
  @extend .ct-form-compact;

  // Invalidate widget height, to fall back to normal line height
  --widget-height: invalidate-this;

  > .control-label,
  > .pf-c-form__label {
    font-weight: normal;
  }
}

@media (max-width: $screen-xs) {
  // When inside of lists or modals & the page is *very* narrow,
  // collapse the grid further, so labels are above controls
  //
  // Note: Padding variables below are outside the local scope of the
  // .ct-form block, so they default to the global PatternFly
  // values.

  .listing-ct-body,
  .modal {
    .ct-form {
      @include deconstruct();
    }
  }
}