Input - a11y
-+
+
+
+
+
diff --git a/core/src/components/input/input.md.outline.scss b/core/src/components/input/input.md.outline.scss new file mode 100644 index 00000000000..9581300459b --- /dev/null +++ b/core/src/components/input/input.md.outline.scss @@ -0,0 +1,158 @@ +// Input Fill: Outline +// ---------------------------------------------------------------- + +:host(.input-fill-outline) { + --border-color: #{$background-color-step-300}; + --border-radius: 4px; + --padding-start: 16px; + --padding-end: 16px; +} + +:host(.input-fill-outline.input-shape-round) { + --border-radius: 9999px; + --padding-start: 32px; + --padding-end: 32px; +} + +/** + * The border should get thicker when + * the input is focused. + */ +:host(.input-fill-outline.has-focus) { + --border-width: 2px; +} + +/** + * The bottom content should never have + * a border with the outline style. + */ +:host(.input-fill-outline) .input-bottom { + border-top: none; +} + +/** + * Border should be + * slightly darker on hover. + */ +@media (any-hover: hover) { + :host(.input-fill-outline:hover) { + --border-color: #{$background-color-step-750}; + } +} + +/** + * Outline inputs do not have a bottom border. + * Instead, they have a border that wraps the + * input + label. + */ +:host(.input-fill-outline) .input-wrapper { + border-bottom: none; +} + +:host(.input-fill-outline.input-label-placement-stacked) label, +:host(.input-fill-outline.input-label-placement-floating) label { + @include transform-origin(center, top); +} + +/** + * The label should appear on top of an outline + * container that overlaps it so it is always clickable. + */ +:host(.input-fill-outline) label, +:host(.input-fill-outline) label { + position: relative; + + z-index: 1; +} + +/** + * This makes the label sit above the input. + */ +:host(.has-focus.input-fill-outline.input-label-placement-floating) label, +:host(.has-value.input-fill-outline.input-label-placement-floating) label, +:host(.input-fill-outline.input-label-placement-stacked) label, +:host(.input-fill-outline.input-label-placement-stacked) label { + @include transform(translateY(-32%), scale(.75)); + @include margin(0); +} + +/** + * This ensures that the input does not + * overlap the floating label while still + * remaining visually centered. + */ +:host(.input-fill-outline.input-label-placement-stacked) input, +:host(.input-fill-outline.input-label-placement-floating) input { + @include margin(6px, 0, 6px, 0); +} + +// Input Fill: Outline Outline Container +// ---------------------------------------------------------------- + +:host(.input-fill-outline) .input-outline-container { + @include position(0, 0, 0, 0); + + display: flex; + + position: absolute; + + width: 100%; + height: 100%; +} + +:host(.input-fill-outline) .input-outline-start, +:host(.input-fill-outline) .input-outline-end { + pointer-events: none; +} + +/** + * By default, each piece of the container should have + * a top and bottom border. This gives the appearance + * of a unified container with a border. + */ +:host(.input-fill-outline) .input-outline-start, +:host(.input-fill-outline) .input-outline-notch, +:host(.input-fill-outline) .input-outline-end { + border-top: var(--border-width) var(--border-style) var(--border-color); + border-bottom: var(--border-width) var(--border-style) var(--border-color); +} + +:host(.input-fill-outline) .input-outline-start { + @include border-radius(var(--border-radius), 0px, 0px, var(--border-radius)); + @include border(null, null, null, var(--border-width) var(--border-style) var(--border-color)); + + width: 12px; +} + +/** + * When shape="round", the starting outline fragment + * should appear with a pill shape. + */ +:host(.input-fill-outline.input-shape-round) .input-outline-start { + @include border-radius(28px, 0px, 0px, 28px); + width: 28px; +} + +:host(.input-fill-outline) .input-outline-end { + @include border(null, var(--border-width) var(--border-style) var(--border-color), null, null); + @include border-radius(0px, var(--border-radius), var(--border-radius), 0px); + + /** + * The ending outline fragment + * should take up the remaining free space. + */ + flex-grow: 1; +} + +/** + * When the input either has focus or a value, + * there should be a "cut out" at the top for + * the floating/stacked label. We simulate this "cut out" + * by removing the top border from the notch fragment. + */ +:host(.has-focus.input-fill-outline.input-label-placement-floating) .input-outline-notch, +:host(.has-value.input-fill-outline.input-label-placement-floating) .input-outline-notch, +:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch, +:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch { + border-top: none; +} diff --git a/core/src/components/input/input.md.scss b/core/src/components/input/input.md.scss index d8dfa9fbf5a..b07c0bf9473 100644 --- a/core/src/components/input/input.md.scss +++ b/core/src/components/input/input.md.scss @@ -1,6 +1,7 @@ @import "./input"; @import "./input.md.vars"; - +@import "./input.md.solid.scss"; +@import "./input.md.outline.scss"; // Material Design Input // -------------------------------------------------- @@ -47,3 +48,10 @@ .input-wrapper { min-height: 56px; } + +// Input Shape Rounded +// ---------------------------------------------------------------- + +:host(.input-shape-round) { + --border-radius: 16px; +} diff --git a/core/src/components/input/input.md.solid.scss b/core/src/components/input/input.md.solid.scss new file mode 100644 index 00000000000..280c835cea0 --- /dev/null +++ b/core/src/components/input/input.md.solid.scss @@ -0,0 +1,52 @@ +// Input Fill: Solid +// ---------------------------------------------------------------- + +:host(.input-fill-solid) { + --background: #{$background-color-step-50}; + --border-color: #{$background-color-step-500}; + --border-radius: 4px; + --padding-start: 16px; + --padding-end: 16px; +} + +/** + * The solid fill style has a border + * at the bottom of the input wrapper. + * As a result, the border on the "bottom + * content" is not needed. + */ +:host(.input-fill-solid) .input-wrapper { + border-bottom: var(--border-width) var(--border-style) var(--border-color); +} + +:host(.input-fill-solid) .input-bottom { + border-top: none; +} + +/** + * Background and border should be + * slightly darker on hover. + */ +@media (any-hover: hover) { + :host(.input-fill-solid:hover) { + --background: #{$background-color-step-100}; + --border-color: #{$background-color-step-750}; + } +} + +/** + * Background and border should be + * much darker on focus. + */ +:host(.input-fill-solid.has-focus) { + --background: #{$background-color-step-150}; + --border-color: #{$background-color-step-750}; +} + +:host(.input-fill-solid) .input-wrapper { + /** + * Only the top left and top right borders should. + * have a radius when using a solid fill. + */ + @include border-radius(var(--border-radius), var(--border-radius), 0px, 0px); +} diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 619bffd9f3f..51fed885a2e 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -52,7 +52,6 @@ /* stylelint-disable-next-line all */ padding: 0 !important; - background: var(--background); color: var(--color); font-family: $font-family-base; @@ -65,10 +64,13 @@ flex: 1; align-items: center; + + background: var(--background); } :host(.legacy-input) .native-input { @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); + @include border-radius(var(--border-radius)); } :host-context(ion-item:not(.item-label)) { @@ -83,12 +85,13 @@ // -------------------------------------------------- .native-input { - @include border-radius(var(--border-radius)); @include padding(0, 0, 0, 0); @include text-inherit(); display: inline-block; + position: relative; + flex: 1; width: 100%; @@ -104,6 +107,17 @@ box-sizing: border-box; appearance: none; + /** + * This ensures the input + * remains on top of any decoration + * that we render (particularly the + * outline border when fill="outline"). + * If we did not do this then Axe would + * be unable to determine the color + * contrast of the input. + */ + z-index: 1; + &::placeholder { color: var(--placeholder-color); @@ -220,23 +234,33 @@ .input-wrapper { @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); + @include border-radius(var(--border-radius)); display: flex; + position: relative; + flex-grow: 1; align-items: stretch; height: 100%; - background: inherit; + transition: background-color 15ms linear; + + background: var(--background); } // Input Bottom Content // ---------------------------------------------------------------- .input-bottom { - @include padding(5px, 0, 0, 0); + /** + * The bottom content should take on the start and end + * padding so it is always aligned with either the label + * or the start of the text input. + */ + @include padding(5px, var(--padding-end), 0, var(--padding-start)); display: flex; diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 6396fb58374..6ec349c6356 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -554,8 +554,54 @@ export class Input implements ComponentInterface { ); } + private renderLabel() { + const { label } = this; + if (label === undefined) { + return; + } + + return ; + } + + /** + * Renders the border container + * when fill="outline". + */ + private renderLabelContainer() { + const { labelPlacement } = this; + const hasOutlineFill = this.fill === 'outline'; + const needsNotch = labelPlacement === 'floating' || labelPlacement === 'stacked'; + + if (hasOutlineFill) { + /** + * The outline fill has a special outline + * that appears around the input and the label. + * Certain label placements cause the + * label to translate up and create a "cut out" + * inside of that border. When this happens, we need + * to render the label inside of the input-outline-notch + * element. Otherwise, we can render it as a sibling + * of the outline container. + */ + return [ +