Skip to content
Permalink
Browse files
feat(custom components): avoid using padding/margin utility classes w…
…here possible (closes #5117) (#5121)

Co-authored-by: Jacob Müller
  • Loading branch information
tmorehouse committed Apr 12, 2020
1 parent 6a5ff24 commit 8c6cfe0af919a4e54667bcb4b29d2ba6b6576b67
Showing 15 changed files with 132 additions and 86 deletions.
@@ -31,9 +31,11 @@

// Shared BVFormBtnLabelControl styling
// Currently used by BFormTimepicker and BFormDatepicker
// Does not apply to button-only styling
.b-form-btn-label-control {
// Remove background validation images from main wrapper
background-image: none;
padding: 0;

@at-root {
// Prevent the button/label from reversing order on in horizontal RTL mode
@@ -51,6 +53,7 @@
line-height: 1;
font-size: inherit;
box-shadow: none !important;
border: 0;

&:disabled {
pointer-events: none;
@@ -65,10 +68,15 @@
color: $form-feedback-invalid-color;
}

> .dropdown-menu {
padding: 0.5rem;
}

> label {
// Unfortunately this is not supported by all browsers :(
// text-align: end;
outline: 0;
padding-left: 0.25rem;
margin: 0;
border: 0;
@if $enable-pointer-cursor-for-buttons {
cursor: pointer;
}
@@ -86,6 +94,7 @@
}
}


// Disabled and read-only styling
&[aria-disabled="true"],
&[aria-readonly="true"] {
@@ -7,9 +7,8 @@
## Overview

Avatars are lightweight functional components, which render inline by default, so that they are
vertically centered beside any adjoining plain text. They also can be used as children of other
components.
Avatars are lightweight components, which render inline by default, so that they are vertically
centered beside any adjoining plain text. They also can be used as children of other components.

```html
<template>
@@ -661,9 +661,8 @@ the best possible accessibility to _all_ users.

## Implementation notes

`<b-calendar>` uses Bootstrap's margin, padding, border, and flex utility classes, along with button
(`btn-*`) classes and the `form-control` class. BootstrapVue's custom SCSS/CSS is also required for
proper styling.
`<b-calendar>` uses Bootstrap's border and flex utility classes, along with button (`btn-*`) classes
and the `form-control` class. BootstrapVue's custom SCSS/CSS is also required for proper styling.

Accessibility-wise, we chose _not_ to use the ARIA role `grid` for the calendar to minimize
verbosity and to provide consistency across various screen readers (NVDA, when encountering role
@@ -1,22 +1,48 @@
// BCalendar custom styles

.b-calendar {
display: inline-flex;

.b-calendar-inner {
// Prevent calendar from going below this width
min-width: 250px;
}

output.readonly {
background-color: $input-disabled-bg;
opacity: 1;
.b-calendar-header,
.b-calendar-nav {
margin-bottom: 0.25rem;
}

.b-calendar-nav .btn {
padding: 0.25rem;
}

.form-control[role="application"] {
output {
padding: 0.25rem;
font-size: 80%;

&.readonly {
background-color: $input-disabled-bg;
opacity: 1;
}
}

.b-calendar-footer {
margin-top: 0.5rem;
}

.b-calendar-grid {
padding: 0;
margin: 0;
// Easy rounded corners on contained elements,
// specifically the footer of the calendar grid
overflow: hidden;
}

.b-calendar-grid-caption {
padding: 0.25rem;
}

.b-calendar-grid-body {
.col[data-date] {
// We hard code the sizes in `px` to fit
@@ -734,15 +734,10 @@ export const BCalendar = Vue.extend({
return h()
}

const isRTL = this.isRTL
const { isLive, isRTL, activeYMD, selectedYMD, safeId } = this
const hideDecadeNav = !this.showDecadeNav
const todayYMD = formatYMD(this.getToday())
const selectedYMD = this.selectedYMD
const activeYMD = this.activeYMD
const highlightToday = !this.noHighlightToday
const safeId = this.safeId
// Flag for making the `aria-live` regions live
const isLive = this.isLive
// Pre-compute some IDs
// This should be computed props
const idValue = safeId()
@@ -757,7 +752,7 @@ export const BCalendar = Vue.extend({
let $header = h(
'output',
{
staticClass: 'd-block text-center rounded border small p-1 mb-1',
staticClass: 'form-control form-control-sm text-center',
class: { 'text-muted': this.disabled, readonly: this.readonly || this.disabled },
attrs: {
id: idValue,
@@ -784,20 +779,22 @@ export const BCalendar = Vue.extend({
// We use `bdi` elements here in case the label doesn't match the locale
// Although IE 11 does not deal with <BDI> at all (equivalent to a span)
h('bdi', { staticClass: 'sr-only' }, ` (${toString(this.labelSelected)}) `),
h('bdi', {}, this.formatDateString(this.selectedDate))
h('bdi', this.formatDateString(this.selectedDate))
]
: this.labelNoDateSelected || '\u00a0' // '&nbsp;'
)
$header = h(
'header',
{
class: this.hideHeader ? 'sr-only' : 'mb-1',
staticClass: 'b-calendar-header',
class: { 'sr-only': this.hideHeader },
attrs: { title: this.selectedDate ? this.labelSelectedDate || null : null }
},
[$header]
)

// Content for the date navigation buttons
// TODO: add slots for the nav button content
const $prevDecadeIcon = h(BIconChevronBarLeft, { props: { shiftV: 0.5, flipH: isRTL } })
const $prevYearIcon = h(BIconChevronDoubleLeft, { props: { shiftV: 0.5, flipH: isRTL } })
const $prevMonthIcon = h(BIconChevronLeft, { props: { shiftV: 0.5, flipH: isRTL } })
@@ -811,7 +808,7 @@ export const BCalendar = Vue.extend({
return h(
'button',
{
staticClass: 'btn btn-sm btn-outline-secondary border-0 flex-fill p-1 mx-1',
staticClass: 'btn btn-sm btn-outline-secondary border-0 flex-fill',
class: { disabled: btnDisabled },
attrs: {
title: label || null,
@@ -830,7 +827,7 @@ export const BCalendar = Vue.extend({
const $nav = h(
'div',
{
staticClass: 'b-calendar-nav d-flex mx-n1 mb-1',
staticClass: 'b-calendar-nav d-flex',
attrs: {
id: idNav,
role: 'group',
@@ -901,7 +898,7 @@ export const BCalendar = Vue.extend({
'header',
{
key: 'grid-caption',
staticClass: 'text-center font-weight-bold p-1 m-0',
staticClass: 'b-calendar-grid-caption text-center font-weight-bold',
class: { 'text-muted': this.disabled },
attrs: {
id: idGridCaption,
@@ -915,7 +912,10 @@ export const BCalendar = Vue.extend({
// Calendar weekday headings
const $gridWeekDays = h(
'div',
{ staticClass: 'row no-gutters border-bottom', attrs: { 'aria-hidden': 'true' } },
{
staticClass: 'b-calendar-grid-weekdays row no-gutters border-bottom',
attrs: { 'aria-hidden': 'true' }
},
this.calendarHeadings.map((d, idx) => {
return h(
'small',
@@ -1019,7 +1019,7 @@ export const BCalendar = Vue.extend({
const $gridHelp = h(
'footer',
{
staticClass: 'border-top small text-muted text-center bg-light',
staticClass: 'b-calendar-grid-help border-top small text-muted text-center bg-light',
attrs: {
id: idGridHelp
}
@@ -1031,7 +1031,7 @@ export const BCalendar = Vue.extend({
'div',
{
ref: 'grid',
staticClass: 'form-control h-auto text-center p-0 mb-0',
staticClass: 'b-calendar-grid form-control h-auto text-center',
attrs: {
id: idGrid,
role: 'application',
@@ -1057,13 +1057,12 @@ export const BCalendar = Vue.extend({

// Optional bottom slot
let $slot = this.normalizeSlot('default')
$slot = $slot ? h('footer', { staticClass: 'mt-2' }, $slot) : h()
$slot = $slot ? h('footer', { staticClass: 'b-calendar-footer' }, $slot) : h()

const $widget = h(
'div',
{
staticClass: 'b-calendar-inner',
class: this.block ? 'd-block' : 'd-inline-block',
style: this.block ? {} : { width: this.width },
attrs: {
id: idWidget,
@@ -1093,15 +1092,6 @@ export const BCalendar = Vue.extend({
)

// Wrap in an outer div that can be styled
return h(
'div',
{
staticClass: 'b-calendar',
// We use a style here rather than class `d-inline-block` so that users can
// override the display value (`d-*` classes use the `!important` flag)
style: this.block ? {} : { display: 'inline-block' }
},
[$widget]
)
return h('div', { staticClass: 'b-calendar', class: { 'd-block': this.block } }, [$widget])
}
})
@@ -590,10 +590,8 @@ details.
`<b-form-datepicker>` is based upon the components [`<b-calendar>`](/docs/components/calendar) and
[`<b-dropdown>`](/docs/components/dropdown).

`<b-form-datepicker>` uses Bootstrap's margin, padding, border, and flex utility classes, along with
button (`btn-*`) classes, dropdown (`dropdown*`) classes, and the `form-control*` (plus validation)
classes.

`<b-form-datepicker>` uses Bootstrap's border and flex utility classes, along with button (`btn-*`)
classes, dropdown (`dropdown*`) classes, and the `form-control*` (plus validation) classes.
BootstrapVue's Custom SCSS/CSS is also required for proper styling of the date picker and calendar.

## See also
@@ -403,7 +403,7 @@ Note the the `repeat-delay`, `repeat-threshold` and `repeat-interval` only appli

## Implementation notes

`<b-form-spinbutton>` uses a mixture of Bootstrap v4 utility classes (margin, padding, and flex),
`<b-form-spinbutton>` uses a mixture of Bootstrap v4 utility classes (border, alignment, flex),
form-control and button classes, along with additional custom BootstrapVue SCSS/CSS.

## See also
@@ -1,14 +1,10 @@
.b-form-spinbutton.form-control {
.b-form-spinbutton {
text-align: center;
// Quick way to get end buttons rounded on outside edges
overflow: hidden;
// Hide validation icon, as there is no room for it
background-image: none;

&.flex-column {
height: auto;
width: auto;
}
padding: 0;

@at-root {
// Prevent the buttons from reversing order on in horizontal RTL mode
@@ -23,6 +19,9 @@
outline: 0;
border: 0;
background-color: transparent;
width: auto;
margin: 0;
padding: 0 0.25rem;

> div,
> bdi {
@@ -33,13 +32,26 @@
}
}

&.d-inline-flex:not(.flex-column) {
&,
&.flex-column {
height: auto;
width: auto;

output {
width: auto;
margin: 0 0.25rem;
padding: 0.25rem 0;
}
}

&:not(.d-inline-flex):not(.flex-column) {
output: {
width: 100%;
}
}

&.d-inline-flex:not(.flex-column) {
width: auto;
}

.btn {
line-height: 1;
box-shadow: none !important;
@@ -467,7 +467,7 @@ export const BFormSpinbutton = /*#__PURE__*/ Vue.extend({
touchstart: handler
}
},
[h('div', {}, [this.normalizeSlot(slotName, scope) || $icon])]
[h('div', [this.normalizeSlot(slotName, scope) || $icon])]
)
}
// TODO: Add button disabled state when `wrap` is `false` and at value max/min
@@ -512,13 +512,9 @@ export const BFormSpinbutton = /*#__PURE__*/ Vue.extend({
key: 'output',
staticClass: 'flex-grow-1',
class: {
'w-100': !isVertical && !isInline,
'd-flex': isVertical,
'align-self-center': !isVertical,
'align-items-center': isVertical,
'py-1': isVertical,
'px-1': !isVertical,
'mx-1': isVertical,
'border-top': isVertical,
'border-bottom': isVertical,
'border-left': !isVertical,
@@ -545,13 +541,13 @@ export const BFormSpinbutton = /*#__PURE__*/ Vue.extend({
'aria-valuetext': hasValue ? formatter(value) : null
}
},
[h('bdi', { staticClass: 'w-100' }, hasValue ? formatter(value) : this.placeholder || '')]
[h('bdi', hasValue ? formatter(value) : this.placeholder || '')]
)

return h(
'div',
{
staticClass: 'b-form-spinbutton form-control p-0',
staticClass: 'b-form-spinbutton form-control',
class: {
disabled: isDisabled,
readonly: isReadonly,
@@ -426,10 +426,8 @@ Refer to the [`<b-time>`](/docs/components/time#accessibility) documentation for
`<b-form-timepicker>` is based upon the components [`<b-time>`](/docs/components/time) and
[`<b-dropdown>`](/docs/components/dropdown).
`<b-form-timepicker>` uses Bootstrap's margin, padding, border, and flex utility classes, along with
button (`btn-*`) classes, dropdown (`dropdown*`) classes, and the `form-control*` (plus validation)
classes.
`<b-form-timepicker>` uses Bootstrap's border and flex utility classes, along with button (`btn-*`)
classes, dropdown (`dropdown*`) classes, and the `form-control*` (plus validation) classes.
BootstrapVue's Custom SCSS/CSS is also required for proper styling of the time picker and popup.
## See also

0 comments on commit 8c6cfe0

Please sign in to comment.