diff --git a/docs/4.0/components/forms.md b/docs/4.0/components/forms.md index c69bfe2ae2f93ec6c5f2b1a42eb78183673e10ae..e5a568b67b05796a75c5e365410c830b2a26ea43 100644 --- a/docs/4.0/components/forms.md +++ b/docs/4.0/components/forms.md @@ -899,31 +899,37 @@ Our example forms show native textual `<input>`s above, but form validation styl {% example html %} <form class="was-validated"> - <div class="custom-control custom-checkbox"> + <div class="custom-control custom-checkbox mb-3"> <input type="checkbox" class="custom-control-input" id="customControlValidation1" required> <label class="custom-control-label" for="customControlValidation1">Check this custom checkbox</label> + <div class="invalid-feedback">Example invalid feedback text</div> </div> <div class="custom-control custom-radio"> <input type="radio" class="custom-control-input" id="customControlValidation2" name="radio-stacked" required> <label class="custom-control-label" for="customControlValidation2">Toggle this custom radio</label> </div> - <div class="custom-control custom-radio"> + <div class="custom-control custom-radio mb-3"> <input type="radio" class="custom-control-input" id="customControlValidation3" name="radio-stacked" required> <label class="custom-control-label" for="customControlValidation3">Or toggle this other custom radio</label> + <div class="invalid-feedback">More example invalid feedback text</div> </div> - <select class="custom-select d-block my-3" required> - <option value="">Open this select menu</option> - <option value="1">One</option> - <option value="2">Two</option> - <option value="3">Three</option> - </select> + <div class="form-group"> + <select class="custom-select" required> + <option value="">Open this select menu</option> + <option value="1">One</option> + <option value="2">Two</option> + <option value="3">Three</option> + </select> + <div class="invalid-feedback">Example invalid custom select feedback</div> + </div> - <label class="custom-file"> - <input type="file" id="file" class="custom-file-input" required> - <span class="custom-file-control"></span> - </label> + <div class="custom-file"> + <input type="file" class="custom-file-input" id="validatedCustomFile" required> + <label class="custom-file-label" for="validatedCustomFile">Choose file...</label> + <div class="invalid-feedback">Example invalid custom file feedback</div> + </div> </form> {% endexample %} @@ -1062,24 +1068,16 @@ As is the `size` attribute: ### File browser -The file input is the most gnarly of the bunch and require additional JavaScript if you'd like to hook them up with functional *Choose file...* and selected file name text. +The file input is the most gnarly of the bunch and requires additional JavaScript if you'd like to hook them up with functional *Choose file...* and selected file name text. {% example html %} -<label class="custom-file"> - <input type="file" id="file2" class="custom-file-input"> - <span class="custom-file-control"></span> -</label> +<div class="custom-file"> + <input type="file" class="custom-file-input" id="customFile"> + <label class="custom-file-label" for="customFile">Choose file</label> +</div> {% endexample %} -Here's how it works: - -- We wrap the `<input>` in a `<label>` so the custom control properly triggers the file browser. -- We hide the default file `<input>` via `opacity`. -- We use `::after` to generate a custom background and directive (*Choose file...*). -- We use `::before` to generate and position the *Browse* button. -- We declare a `height` on the `<input>` for proper spacing for surrounding content. - -In other words, it's an entirely custom element, all generated via CSS. +We hide the default file `<input>` via `opacity` and instead style the `<label>`. The button is generated and positioned with `::after`. Lastly, we declare a `width` and `height` on the `<input>` for proper spacing for surrounding content. #### Translating or customizing the strings diff --git a/docs/4.0/migration.md b/docs/4.0/migration.md index 346ef84ab0daadecd1d9a45cf4d6225745ba7859..39b7dfa1685bc3958b5b233f8a0c3df8a24222c9 100644 --- a/docs/4.0/migration.md +++ b/docs/4.0/migration.md @@ -35,8 +35,6 @@ While Beta 2 saw the bulk of our breaking changes during the beta phase, but we - Sizing classes must be on the parent `.input-group` and not the individual form elements. -- Due to limitations in how CSS selectors work, all buttons must be the same element (e.g., `<a>` or `<button>`). - ## Beta 2 changes While in beta, we aim to have no breaking changes. However, things don't always go as planned. Below are the breaking changes to bear in mind when moving from Beta 1 to Beta 2. diff --git a/scss/_button-group.scss b/scss/_button-group.scss index 83234f948a57faed4e9f7b8a35059eff8c7b626e..c1b826536c509fffee023000f780818ef3c541d3 100644 --- a/scss/_button-group.scss +++ b/scss/_button-group.scss @@ -44,46 +44,18 @@ } .btn-group { - > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; - } - - // Set corners individual because sometimes a single button can be in a .btn-group - // and we need :first-child and :last-child to both match > .btn:first-child { margin-left: 0; - - &:not(:last-child):not(.dropdown-toggle) { - @include border-right-radius(0); - } - } - - - // Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu - // immediately after it - > .btn:last-child:not(:first-child), - > .dropdown-toggle:not(:first-child) { - @include border-left-radius(0); - } - - // Custom edits for including btn-groups within btn-groups (useful for including - // dropdown buttons within a btn-group) - > .btn-group { - float: left; - } - - > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; } - > .btn-group:first-child:not(:last-child) { - > .btn:last-child, - > .dropdown-toggle { - @include border-right-radius(0); - } + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-right-radius(0); } - > .btn-group:last-child:not(:first-child) > .btn:first-child { + > .btn:not(:first-child), + > .btn-group:not(:first-child) > .btn { @include border-left-radius(0); } } @@ -154,32 +126,14 @@ margin-left: 0; } - > .btn { - &:not(:first-child):not(:last-child) { - border-radius: 0; - } - - &:first-child:not(:last-child) { - @include border-bottom-radius(0); - } - - &:last-child:not(:first-child) { - @include border-top-radius(0); - } - } - - > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; - } - - > .btn-group:first-child:not(:last-child) { - > .btn:last-child, - > .dropdown-toggle { - @include border-bottom-radius(0); - } + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-bottom-radius(0); } - > .btn-group:last-child:not(:first-child) > .btn:first-child { + > .btn:not(:first-child), + > .btn-group:not(:first-child) > .btn { @include border-top-radius(0); } } diff --git a/scss/_custom-forms.scss b/scss/_custom-forms.scss index 56093bc4841266094d76c3b903ab79841d3ce4f6..d99a86dc7f96b7f040b0978f4ea62b5e39787cc1 100644 --- a/scss/_custom-forms.scss +++ b/scss/_custom-forms.scss @@ -225,7 +225,9 @@ } .custom-file-input { - max-width: 100%; + position: relative; + z-index: 2; + width: 100%; height: $custom-file-height; margin: 0; opacity: 0; @@ -238,49 +240,43 @@ border-color: $custom-file-focus-border-color; } } + + @each $lang, $value in $custom-file-text { + &:lang(#{$lang}) ~ .custom-file-label::after { + content: $value; + } + } } -.custom-file-control { +.custom-file-label { position: absolute; top: 0; right: 0; left: 0; + z-index: 1; height: $custom-file-height; padding: $custom-file-padding-y $custom-file-padding-x; line-height: $custom-file-line-height; color: $custom-file-color; - pointer-events: none; - user-select: none; background-color: $custom-file-bg; border: $custom-file-border-width solid $custom-file-border-color; @include border-radius($custom-file-border-radius); @include box-shadow($custom-file-box-shadow); - @each $lang, $text in map-get($custom-file-text, placeholder) { - &:lang(#{$lang}):empty::after { - content: $text; - } - } - - &::before { + &::after { position: absolute; - top: -$custom-file-border-width; - right: -$custom-file-border-width; - bottom: -$custom-file-border-width; - z-index: 1; + top: 0; + right: 0; + bottom: 0; + z-index: 3; display: block; - height: $custom-file-height; + height: calc(#{$custom-file-height} - #{$custom-file-border-width} * 2); padding: $custom-file-padding-y $custom-file-padding-x; line-height: $custom-file-line-height; color: $custom-file-button-color; + content: "Browse"; @include gradient-bg($custom-file-button-bg); - border: $custom-file-border-width solid $custom-file-border-color; + border-left: $custom-file-border-width solid $custom-file-border-color; @include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0); } - - @each $lang, $text in map-get($custom-file-text, button-label) { - &:lang(#{$lang})::before { - content: $text; - } - } } diff --git a/scss/_input-group.scss b/scss/_input-group.scss index f1d3c9dea1a854bd4dc8c8ab8daddcde01b235be..7ef0267cc38691d832d57eafee881e9336834986 100644 --- a/scss/_input-group.scss +++ b/scss/_input-group.scss @@ -33,9 +33,8 @@ .form-control, .custom-select { - &:not(:first-child):not(:last-of-type) { @include border-radius(0); } - &:first-child { @include border-right-radius(0); } - &:last-of-type:not(:first-child) { @include border-left-radius(0); } + &:not(:last-child) { @include border-right-radius(0); } + &:not(:first-child) { @include border-left-radius(0); } } // Custom file inputs have more complex markup, thus requiring different @@ -44,12 +43,10 @@ display: flex; align-items: center; - &:not(:first-child):not(:last-of-type) .custom-file-control, - &:not(:first-child):not(:last-of-type) .custom-file-control::before { @include border-radius(0); } - &:first-child .custom-file-control, - &:first-child .custom-file-control::before { @include border-right-radius(0); } - &:last-of-type:not(:first-child) .custom-file-control, - &:last-of-type:not(:first-child) .custom-file-control::before { @include border-left-radius(0); } + &:not(:last-child) .custom-file-control, + &:not(:last-child) .custom-file-control::before { @include border-right-radius(0); } + &:not(:first-child) .custom-file-control, + &:not(:first-child) .custom-file-control::before { @include border-left-radius(0); } } } @@ -139,28 +136,21 @@ // border-radius values when extending. They're more specific than we'd like // with the `.input-group >` part, but without it, we cannot override the sizing. + .input-group > .input-group-prepend > .btn, -.input-group > .input-group-prepend > .input-group-text { - // All prepended buttons have no right border-radius +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { @include border-right-radius(0); - - + .btn, - + .input-group-text { - @include border-left-radius(0); - } } -// We separate out the button and input resets here because `.input-group-text` -// can be any HTML element, but buttons are always inputs, buttons, or anchors. -.input-group > .input-group-append { - // Everything but the last one have no rounded corners - .btn:not(:last-of-type), - .input-group-text:not(:last-child) { - @include border-radius(0); - } - - .btn:last-of-type, - .input-group-text:last-child { - @include border-left-radius(0); - } +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + @include border-left-radius(0); } diff --git a/scss/_variables.scss b/scss/_variables.scss index b9d963e746a3969a54eff41723965dbe31cd37e2..1c6ca3abb50b37c2eb348a30d4a53a4d64d40bef 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -509,12 +509,7 @@ $custom-file-box-shadow: $input-box-shadow !default; $custom-file-button-color: $custom-file-color !default; $custom-file-button-bg: $input-group-addon-bg !default; $custom-file-text: ( - placeholder: ( - en: "Choose file..." - ), - button-label: ( - en: "Browse" - ) + en: "Browse" ) !default; diff --git a/scss/mixins/_forms.scss b/scss/mixins/_forms.scss index ba1b16d6a1a08d883af40db6d905b09f7d178a72..d25df182dfa2892deb4ee8ba3c9156dfa51c49e2 100644 --- a/scss/mixins/_forms.scss +++ b/scss/mixins/_forms.scss @@ -88,11 +88,18 @@ background-color: lighten($color, 25%); } } + + ~ .#{$state}-feedback, + ~ .#{$state}-tooltip { + display: block; + } + &:checked { ~ .custom-control-label::before { @include gradient-bg(lighten($color, 10%)); } } + &:focus { ~ .custom-control-label::before { box-shadow: 0 0 0 1px $body-bg, 0 0 0 $input-focus-width rgba($color, .25); @@ -105,13 +112,19 @@ .custom-file-input { .was-validated &:#{$state}, &.is-#{$state} { - ~ .custom-file-control { + ~ .custom-file-label { border-color: $color; &::before { border-color: inherit; } } + + ~ .#{$state}-feedback, + ~ .#{$state}-tooltip { + display: block; + } + &:focus { - ~ .custom-file-control { + ~ .custom-file-label { box-shadow: 0 0 0 $input-focus-width rgba($color, .25); } }