Skip to content
Permalink
Browse files
feat(b-time, b-form-timepicker): new components b-time and `b-form-…
…timepicker` (#4783)

Co-authored-by: Jacob Müller
  • Loading branch information
tmorehouse committed Mar 3, 2020
1 parent 165f481 commit 417ef8f2165e68d182e942219d847511b0fd6e9c
Showing 38 changed files with 3,717 additions and 349 deletions.
@@ -3,7 +3,7 @@
key="_bv-icons-table_"
class="bv-icons-table notranslate"
role="group"
aria-labeledby="bv-icons-table-title"
aria-labelledby="bv-icons-table-title"
>
<b-row align-v="start">
<b-col md="5" lg="6">
@@ -26,8 +26,10 @@ Alternatives to BootstrapVue's [`b-icon-*`](/docs/icons) components:

### Date and Time Pickers

Alternatives to BootstrapVue's [`<b-form-datepicker>`](/docs/components/form-datepicker) and
[`<b-calendar>`](/docs/components/calendar) components:
Alternatives to BootstrapVue's [`<b-form-datepicker>`](/docs/components/form-datepicker),
[`<b-calendar>`](/docs/components/calendar),
[`<b-form-timepicker>`](/docs/components/form-timepicker), and [`<b-time>`](/docs/components/time)
components:

- [Vue AirBnB Style Datepicker](https://mikaeledebro.gitbooks.io/vue-airbnb-style-datepicker/)
- [Vue Datepicker](https://livelybone.github.io/vue/vue-datepicker/) _Note: Not WAI-ARIA compliant_
@@ -56,6 +58,9 @@ Alternatives to BootstrapVue's [`<b-form-datepicker>`](/docs/components/form-dat
- [VeeValidate](https://logaretm.github.io/vee-validate/)
- [Vuelidate](https://github.com/vuelidate/vuelidate/)

For examples of using these validation libraries, refer to our
[Validation reference section](/docs/reference/validation).

## Site generation

- [NuxtJS](https://nuxtjs.org) - Static + SPA + PWA + SSR
@@ -1,28 +1,60 @@
// Custom CSS for b-form-datepicker
// Special styling for some BootstrapVue custom form controls that do
// not have a native HTML input type root element (or tabindex)
// Used by BFormSpinbutton, BFormDatepicker, BFormTimepicker, BTime, BCalendar
.form-control {
// Adds focus styling to the form-control class (via the focus class)
// Specifically when we are using non focusable elements, or when true focus
// is within the `.form-control` element.
// Mimics the `.form-control:focus` styling
&.focus {
color: $input-focus-color;
background-color: $input-focus-bg;
border-color: $input-focus-border-color;
outline: 0;
@if $enable-shadows {
box-shadow: $input-box-shadow, $input-focus-box-shadow;
} @else {
box-shadow: $input-focus-box-shadow;
}

&.is-valid {
border-color: $form-feedback-valid-color;
box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-valid-color, 0.25);
}

&.is-invalid {
border-color: $form-feedback-invalid-color;
box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-invalid-color, 0.25);
}
}
}

.b-form-datepicker.form-control {
// Shared BVFormBtnLabelControl styling
// Currently used by BFormTimepicker and BFormDatepicker
.b-form-btn-label-control {
// Remove background validation images from main wrapper
background-image: none;

@at-root {
// Prevent the button/label from reversing order on in horizontal RTL mode
[dir="rtl"] &,
&[dir="rtl"] {
flex-direction: row-reverse;

> label {
text-align: right;
}
}
}

> .btn {
line-height: 1;
font-size: inherit;
box-shadow: none !important;

> div {
transition: transform 0.15s;
}

&:disabled {
pointer-events: none;
}

&:hover:not(:disabled),
&:focus:not(:disabled) {
> div {
transform: scale(1.125);
}
}
}

&.is-valid > .btn {
@@ -33,31 +65,24 @@
color: $form-feedback-invalid-color;
}

&.focus {
color: $input-focus-color;
background-color: $input-focus-bg;
border-color: $input-focus-border-color;
> label {
// Unfortunately this is not supported by all browsers :(
// text-align: end;
outline: 0;
@if $enable-shadows {
box-shadow: $input-box-shadow, $input-focus-box-shadow;
} @else {
box-shadow: $input-focus-box-shadow;
}

&.is-valid {
border-color: $form-feedback-valid-color;
box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-valid-color, 0.25);
@if $enable-pointer-cursor-for-buttons {
cursor: pointer;
}
// Set a minimum height, as we have height set to auto
// (to allow the content to wrap if needed)
// We subtract off the border, as we have border set to 0
min-height: calc(#{$input-height} - #{$input-height-border});

&.is-invalid {
border-color: $form-feedback-invalid-color;
box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-invalid-color, 0.25);
&.form-control-sm {
min-height: calc(#{$input-height-sm} - #{$input-height-border});
}
}

> label {
@if $enable-pointer-cursor-for-buttons {
cursor: pointer;
&.form-control-lg {
min-height: calc(#{$input-height-lg} - #{$input-height-border});
}
}

@@ -632,3 +632,5 @@ verbosity and to provide consistency across various screen readers (NVDA, when e
## See also

- [`<b-form-datepicker>` Date picker custom form input](/docs/components/form-datepicker)
- [`<b-form-timepicker>` Time picker custom form input](/docs/comonents/form-timepicker)
- [`<b-time>` Time date selection widget](/docs/components/calendar)
@@ -11,6 +11,12 @@
opacity: 1;
}

.form-control[role="application"] {
// Easy rounded corners on contained elements,
// specifically the footer of the calendar grid
overflow: hidden;
}

.b-calendar-grid-body {
.col[data-date] {
// We hard code the sizes in `px` to fit
@@ -20,6 +20,7 @@ import {
} from '../../utils/date'
import { requestAF } from '../../utils/dom'
import { isArray, isFunction, isPlainObject, isString } from '../../utils/inspect'
import { isLocaleRTL } from '../../utils/locale'
import { toInteger } from '../../utils/number'
import { toString } from '../../utils/string'
import idMixin from '../../mixins/id'
@@ -34,37 +35,6 @@ const NAME = 'BCalendar'
// Key Codes
const { UP, DOWN, LEFT, RIGHT, PAGEUP, PAGEDOWN, HOME, END, ENTER, SPACE } = KeyCodes

// Languages that are RTL
const RTL_LANGS = [
'ar',
'az',
'ckb',
'fa',
'he',
'ks',
'lrc',
'mzn',
'ps',
'sd',
'te',
'ug',
'ur',
'yi'
].map(locale => locale.toLowerCase())

// --- Helper utilities ---

export const isLocaleRTL = locale => {
// Determines if the locale is RTL (only single locale supported)
const parts = toString(locale)
.toLowerCase()
.replace(/-u-.+/, '')
.split('-')
const locale1 = parts.slice(0, 2).join('-')
const locale2 = parts[0]
return arrayIncludes(RTL_LANGS, locale1) || arrayIncludes(RTL_LANGS, locale2)
}

// --- BCalendar component ---

// @vue/component
@@ -667,7 +637,6 @@ export const BCalendar = Vue.extend({
},
onClickDay(day) {
// Clicking on a date "button" to select it
// TODO: Change to lookup the `data-data` attribute
const selectedDate = this.selectedDate
const activeDate = this.activeDate
const clickedDate = parseYMD(day.ymd)
@@ -702,6 +671,12 @@ export const BCalendar = Vue.extend({
},
gotoNextYear() {
this.activeYMD = formatYMD(this.constrainDate(oneYearAhead(this.activeDate)))
},
onHeaderClick() {
if (!this.disabled) {
this.activeYMD = this.selectedYMD || formatYMD(this.getToday())
this.focus()
}
}
},
render(h) {
@@ -719,8 +694,9 @@ export const BCalendar = Vue.extend({
// Flag for making the `aria-live` regions live
const isLive = this.isLive
// Pre-compute some IDs
const idWidget = safeId()
const idValue = safeId('_calendar-value_')
// Thes should be computed props
const idValue = safeId()
const idWidget = safeId('_calendar-wrapper_')
const idNav = safeId('_calendar-nav_')
const idGrid = safeId('_calendar-grid_')
const idGridCaption = safeId('_calendar-grid-caption_')
@@ -737,13 +713,20 @@ export const BCalendar = Vue.extend({
id: idValue,
for: idGrid,
role: 'status',
tabindex: this.disabled ? null : '-1',
// Mainly for testing purposes, as we do not know
// the exact format `Intl` will format the date string
'data-selected': toString(selectedYMD),
// We wait until after mount to enable `aria-live`
// to prevent initial announcement on page render
'aria-live': isLive ? 'polite' : 'off',
'aria-atomic': isLive ? 'true' : null
},
on: {
// Transfer focus/click to focus grid
// and focus active date (or today if no selection)
click: this.onHeaderClick,
focus: this.onHeaderClick
}
},
this.selectedDate
@@ -212,6 +212,48 @@ describe('calendar', () => {
wrapper.destroy()
})

it('clicking output header focuses grid', async () => {
const wrapper = mount(BCalendar, {
attachToDocument: true,
propsData: {
value: '2020-02-15' // Leap year
}
})

expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()

const $grid = wrapper.find('[role="application"]')
expect($grid.exists()).toBe(true)
expect($grid.is('div')).toBe(true)

expect(document.activeElement).not.toBe($grid.element)

const $output = wrapper.find('header > output')
expect($output.exists()).toBe(true)

$output.trigger('click')
await waitNT(wrapper.vm)
await waitRAF()

expect(document.activeElement).toBe($grid.element)

wrapper.vm.blur()
await waitNT(wrapper.vm)
await waitRAF()

expect(document.activeElement).not.toBe($grid.element)

$output.trigger('focus')
await waitNT(wrapper.vm)
await waitRAF()

expect(document.activeElement).toBe($grid.element)

wrapper.destroy()
})

it('keyboard navigation works', async () => {
const wrapper = mount(BCalendar, {
attachToDocument: true,
@@ -486,5 +486,7 @@ BootstrapVue's Custom SCSS/CSS is also required for proper styling of the date p

## See also

- [`<b-form-timepicker>` Time picker custom form input](/docs/components/form-timepicker)
- [`<b-calendar>` Calendar date selection widget](/docs/components/calendar)
- [`<b-time>` Time selection widget](/docs/components/time)
- [`<b-dropdown>` Dropdown component](/docs/components/dropdown)

0 comments on commit 417ef8f

Please sign in to comment.