Skip to content
Permalink
Browse files

feat(b-form-tags): new tagged input component (#4409)

  • Loading branch information
tmorehouse committed Dec 2, 2019
1 parent 833b028 commit 00eb9d9fd460adca8227b3b344284b5cc49a734f

Large diffs are not rendered by default.

@@ -0,0 +1,55 @@
.b-form-tags {
&.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);
}
}

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

.b-form-tag {
// Override default badge settings
// Due to using text-truncate on the inner content
font-size: 75%;
font-weight: normal;
line-height: $input-line-height;

&.disabled {
opacity: .75;
}

// Override default close button settings
> button.b-form-tag-remove {
color: inherit;
font-size: 125%;
line-height: 1;
float: none;
}
}

.form-control-sm .b-form-tag {
line-height: $input-line-height-sm;
}

.form-control-lg .b-form-tag {
line-height: $input-line-height-lg;
}
@@ -0,0 +1,71 @@
import Vue from '../../utils/vue'
import { getComponentConfig } from '../../utils/config'
import idMixin from '../../mixins/id'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import { BBadge } from '../badge/badge'
import { BButtonClose } from '../button/button-close'

const NAME = 'BFormTag'

export const BFormTag = /*#__PURE__*/ Vue.extend({
name: NAME,
mixins: [idMixin, normalizeSlotMixin],
props: {
variant: {
type: String,
default: () => getComponentConfig(NAME, 'variant')
},
disabled: {
type: Boolean,
default: false
},
title: {
type: String,
default: null
},
pill: {
type: Boolean,
default: false
},
removeLabel: {
type: String,
default: () => getComponentConfig(NAME, 'removeLabel')
},
tag: {
type: String,
default: 'span'
}
},
methods: {
onClick() {
this.$emit('remove')
}
},
render(h) {
const tagId = this.safeId()
let $remove = h()
if (!this.disabled) {
$remove = h(BButtonClose, {
staticClass: 'b-form-tag-remove ml-1',
props: { ariaLabel: this.removeLabel },
attrs: { 'aria-controls': tagId },
on: { click: this.onClick }
})
}
const $tag = h(
'span',
{ staticClass: 'b-form-tag-content flex-grow-1 text-truncate' },
this.normalizeSlot('default') || this.title || [h()]
)
return h(
BBadge,
{
staticClass: 'b-form-tag d-inline-flex align-items-baseline mw-100',
class: { disabled: this.disabled },
attrs: { id: tagId, title: this.title || null },
props: { tag: this.tag, variant: this.variant, pill: this.pill }
},
[$tag, $remove]
)
}
})
@@ -0,0 +1,112 @@
import { mount } from '@vue/test-utils'
import { BFormTag } from './form-tag'

describe('form-tag', () => {
it('has expected structure', async () => {
const wrapper = mount(BFormTag, {
propsData: {
title: 'foobar'
}
})

expect(wrapper.is('span')).toBe(true)

expect(wrapper.classes()).toContain('b-form-tag')
expect(wrapper.classes()).toContain('badge')
expect(wrapper.classes()).toContain('badge-secondary')
expect(wrapper.attributes('title')).toBe('foobar')
expect(wrapper.text()).toContain('foobar')

const $btn = wrapper.find('button')
expect($btn.exists()).toBe(true)
expect($btn.classes()).toContain('close')
expect($btn.classes()).toContain('b-form-tag-remove')
expect($btn.attributes('aria-label')).toBe('Remove tag')

wrapper.destroy()
})

it('renders custom root element', async () => {
const wrapper = mount(BFormTag, {
propsData: {
title: 'foobar',
tag: 'li'
}
})

expect(wrapper.is('li')).toBe(true)

expect(wrapper.classes()).toContain('b-form-tag')
expect(wrapper.classes()).toContain('badge')
expect(wrapper.classes()).toContain('badge-secondary')
expect(wrapper.attributes('title')).toBe('foobar')
expect(wrapper.text()).toContain('foobar')

const $btn = wrapper.find('button')
expect($btn.exists()).toBe(true)
expect($btn.classes()).toContain('close')
expect($btn.classes()).toContain('b-form-tag-remove')
expect($btn.attributes('aria-label')).toBe('Remove tag')

wrapper.destroy()
})

it('renders default slot', async () => {
const wrapper = mount(BFormTag, {
propsData: {
title: 'foo'
},
slots: {
default: 'bar'
}
})

expect(wrapper.is('span')).toBe(true)

expect(wrapper.classes()).toContain('b-form-tag')
expect(wrapper.classes()).toContain('badge')
expect(wrapper.classes()).toContain('badge-secondary')
expect(wrapper.attributes('title')).toBe('foo')
expect(wrapper.text()).toContain('bar')
expect(wrapper.text()).not.toContain('foo')

const $btn = wrapper.find('button')
expect($btn.exists()).toBe(true)
expect($btn.classes()).toContain('close')
expect($btn.classes()).toContain('b-form-tag-remove')
expect($btn.attributes('aria-label')).toBe('Remove tag')

wrapper.destroy()
})

it('emits remove event when button clicked', async () => {
const wrapper = mount(BFormTag, {
propsData: {
title: 'foobar'
}
})

expect(wrapper.is('span')).toBe(true)

expect(wrapper.classes()).toContain('b-form-tag')
expect(wrapper.classes()).toContain('badge')
expect(wrapper.classes()).toContain('badge-secondary')
expect(wrapper.attributes('title')).toBe('foobar')
expect(wrapper.text()).toContain('foobar')

const $btn = wrapper.find('button')
expect($btn.exists()).toBe(true)
expect($btn.classes()).toContain('close')
expect($btn.classes()).toContain('b-form-tag-remove')
expect($btn.attributes('aria-label')).toBe('Remove tag')

expect(wrapper.emitted('remove')).not.toBeDefined()

$btn.trigger('click')

expect(wrapper.emitted('remove')).toBeDefined()
expect(wrapper.emitted('remove').length).toBe(1)

wrapper.destroy()
})
})

0 comments on commit 00eb9d9

Please sign in to comment.
You can’t perform that action at this time.