Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite custom file input #25068

Merged
merged 1 commit into from
Dec 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 24 additions & 26 deletions docs/4.0/components/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}

Expand Down Expand Up @@ -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

Expand Down
42 changes: 19 additions & 23 deletions scss/_custom-forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
}
7 changes: 1 addition & 6 deletions scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
17 changes: 15 additions & 2 deletions scss/mixins/_forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
Expand Down