/* Form layout */

@import "./variables.less";

:root {
  // CSS variable to define the number of (label + control) columns.
  // It dynamically changes on narrow pages (see media query below).
  --ct-form-layout-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-layout`:
//
// `ct-form-layout-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.
//
// `ct-form-layout-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-layout-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.
//
// 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.
//
// <hr>: While this is an element, it has a special meaning and is used
// to add some vertical spacing to a form.
//
// Most of the time, you can ignore all the optional classes (and
// attribute and hr element) and simply wrap your labels & controls in
// a <form class="ct-form-layout"> and layout magic happens.

.ct-form-layout {
  // Locally redefine padding to Bootstrap values for this LESS block
  @padding-large-vertical: 10px;
  @padding-small-vertical: 5px;
  @padding-small-horizontal: 10px;
  // Bootstrap & PatternFly use a 1px border around widgets
  @border-width: 1px;
  // Compute widget height based on (font-size * line height) + extra
  @widget-height: @line-height-computed + (@padding-base-vertical * 2) + (@border-width * 2);

  display: grid;
  grid-gap: @padding-small-vertical @padding-small-horizontal;
  // 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-layout-columns), max-content 1fr);
  place-items: baseline stretch;

  // All <label> elements describing form elements in PatternFly are
  // supposed to have a `control-label` class.
  // These precede control elements.
  > .control-label {
    align-self: flex-start;
    text-align: right;
  }

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

  // Auto-stretch elements to the grid (except when relaxed)
  > :not(.ct-form-layout-relax) {
    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;
    // LESS needs this to be escaped with ~"". You'll see it below too.
    // CSS wants the string to be 1 / -1 without escaping.
    grid-column: ~"1 / -1";
    height: @padding-small-vertical * 2;
    // 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;
  }

  // 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 LESS mixin that will not be in the compiled CSS.
  .widget-rules() {
    > input,
    > textarea,
    > select,
    > .bootstrap-select,
    > .dropdown,
    > .combobox-container,
    > fieldset,
    > [role=group],
    > [data-field],
    > .form-group,
    > .btn-group,
    > .checkbox-inline,
    > .radio-inline {
      // Vertically align elements with text
      margin-top: -(@padding-small-vertical - @padding-base-vertical);
      margin-bottom: @padding-small-vertical - @padding-base-vertical;
      // Ensure widgets are a at least a minimum widget size
      min-height: @widget-height;
    }
  }

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

  // Some elements need special width considerations
  // as PatternFly normally fixes the width
  > :not(.ct-form-layout-relax) {
    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-content: center;
    align-self: start;
    display: grid;
    grid-gap: @padding-small-vertical;
    justify-content: start;
    // Only support 2 splits for now (can change to 3 later, if needed)
    grid-template-columns: repeat(2, auto);

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

    // Allow dropdowns to be less wide
    &:not(.ct-form-layout-relax) > .dropdown {
      width: auto !important;
    }

    > .form-control {
      width: auto;
    }

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

  // Vertically align checkboxes and radios properly using flex
  .checkbox > label,
  .radio > label,
  .checkbox-inline,
  .radio-inline {
    display: inline-flex;
    padding-left: 0;
    padding-right: @padding-small-horizontal;
    align-items: center;

    > input[type="checkbox"],
    > input[type="radio"] {
      // Typical browser widget height for radios & checks; standardized here
      height: 16px;
      margin: @padding-small-vertical 0;
      margin-right: 0.5rem;
      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: @padding-small-vertical;
  }

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

// Apply left-padding to control-labels when in a modal dialog
.modal .ct-form-layout > .control-label {
  padding-left: 0.5rem;
}


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

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

// Reset .ct-form-layout-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-layout > .ct-form-layout-split {
  grid-column: ~"2 / -1";
}

@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-layout-columns: 1;
  }
}

@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-layout block, so they default to the global PatternFly
  // values.

  .listing-ct-body,
  .modal {
    .ct-form-layout {
      // 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 {
        padding: 0;
        text-align: left;

        // Everything but the first label should have space to breathe
        &:not(:first-child) {
          margin: @padding-large-vertical 0 0;
        }
      }

      // Reduce vertical height spacing between groups of elements
      > hr {
        height: @padding-large-vertical * 2;
      }
    }
  }
}
