diff --git a/src/components/v-chip/index.ts b/src/components/v-chip/index.ts new file mode 100644 index 0000000000..587e4a84a2 --- /dev/null +++ b/src/components/v-chip/index.ts @@ -0,0 +1,4 @@ +import VChip from './v-chip.vue'; + +export { VChip }; +export default VChip; diff --git a/src/components/v-chip/v-chip.readme.md b/src/components/v-chip/v-chip.readme.md new file mode 100644 index 0000000000..4adb77740c --- /dev/null +++ b/src/components/v-chip/v-chip.readme.md @@ -0,0 +1,67 @@ +# Chip + +```html +I'm a chip! +``` + +## Sizes + +The chip component supports the following sizes through the use of props: + +* x-small +* small +* (default) +* large +* x-large + +```html +I'm a chip! +``` + +## Colors + +You can set the color, background color, hover color, and background hover color with the `color`, `background-color`, `hover-color`, and `hover-background-color` props respectively: + +```html + + I'm a chip! + +``` + +## Events + +There are two events, one when clicking on the chip called `click` and one when clicking on the enabled close icon called `close`. + +```html +Hello! +I'm closeable! +``` + +| Event | Description | +|---------|------------------------------------------------------------------------------------------------| +| `click` | Triggers when clicked somewhere on the chip | +| `close` | Triggers when the `close` prop is enabled and gets clicked (Doesn't trigger the `click` event) | + +## Props + +| Prop | Description | Default | +|--------------------------|------------------------------------------------------|-----------------------------------------| +| `active` | Change visibility. Can be reacted to via `sync` | `true` | +| `close` | Displays a close icon which triggers the close event | `false` | +| `closeIcon` | Which icon should be displayed instead of `close ` | `close` | +| `outlined` | No background | `false` | +| `color` | Text color | `--chip-primary-text-color` | +| `hover-color` | Text color on hover | `--chip-primary-text-color` | +| `background-color` | Chip color | `--chip-primary-background-color` | +| `hover-background-color` | Chip color on hover | `--chip-primary-background-color-hover` | +| `label` | Label style | `false` | +| `disabled` | Disabled state | `false` | +| `x-small` | Render extra small | `false` | +| `small` | Render small | `false` | +| `large` | Render large | `false` | +| `x-large` | Render extra large | `false` | diff --git a/src/components/v-chip/v-chip.story.ts b/src/components/v-chip/v-chip.story.ts new file mode 100644 index 0000000000..10f749dbdb --- /dev/null +++ b/src/components/v-chip/v-chip.story.ts @@ -0,0 +1,261 @@ +import { withKnobs, text, boolean, number, optionsKnob as options } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import Vue from 'vue'; +import VChip from './v-chip.vue'; +import VIcon from '../v-icon/'; +import markdown from './v-chip.readme.md'; +import withPadding from '../../../.storybook/decorators/with-padding'; + +Vue.component('v-chip', VChip); +Vue.component('v-icon', VIcon); + +export default { + title: 'Components / Chip', + component: VChip, + decorators: [withKnobs, withPadding], + parameters: { + notes: markdown + } +}; + +export const withText = () => ({ + methods: { onClick: action('click'), onClose: action('close') }, + props: { + text: { + default: text('Text in chip', 'Click me') + }, + label: { + default: boolean('Label', false, 'Button') + }, + outlined: { + default: boolean('Outlined', false, 'Button') + }, + size: { + default: options( + 'Size', + { + 'Extra Small': 'xSmall', + Small: 'small', + '(default)': 'default', + Large: 'large', + 'Extra Large': 'xLarge' + }, + 'default', + { + display: 'select' + }, + 'Button' + ) + }, + close: { + default: boolean('Close', false, 'Button') + }, + disabled: { + default: boolean('Disabled', false, 'Button') + }, + active: { + default: boolean('Active', true, 'Button') + }, + color: { + default: text('Color', '--chip-primary-text-color', 'Colors') + }, + backgroundColor: { + default: text('Background Color', '--chip-primary-background-color', 'Colors') + }, + hoverColor: { + default: text('Color (hover)', '--chip-primary-text-color', 'Colors') + }, + hoverBackgroundColor: { + default: text( + 'Background Color (hover)', + '--chip-primary-background-color-hover', + 'Colors' + ) + } + }, + template: ` + + {{ text }} + + ` +}); + +export const withIcon = () => ({ + methods: { onClick: action('click'), onClose: action('close') }, + props: { + iconName: { + default: text('Material Icon', 'add') + }, + text: { + default: text('Text in chip', 'Click me') + }, + label: { + default: boolean('Label', false, 'Button') + }, + outlined: { + default: boolean('Outlined', false, 'Button') + }, + size: { + default: options( + 'Size', + { + 'Extra Small': 'xSmall', + Small: 'small', + '(default)': 'default', + Large: 'large', + 'Extra Large': 'xLarge' + }, + 'default', + { + display: 'select' + }, + 'Button' + ) + }, + iconSize: { + default: options( + 'Size (Icon)', + { + 'Extra Small': 'xSmall', + Small: 'small', + '(default)': 'default', + Large: 'large', + 'Extra Large': 'xLarge' + }, + 'default', + { + display: 'select' + }, + 'Button' + ) + }, + close: { + default: boolean('Close', false, 'Button') + }, + disabled: { + default: boolean('Disabled', false, 'Button') + }, + active: { + default: boolean('Active', true, 'Button') + }, + color: { + default: text('Color', '--chip-primary-text-color', 'Colors') + }, + backgroundColor: { + default: text('Background Color', '--chip-primary-background-color', 'Colors') + }, + hoverColor: { + default: text('Color (hover)', '--chip-primary-text-color', 'Colors') + }, + hoverBackgroundColor: { + default: text( + 'Background Color (hover)', + '--chip-primary-background-color-hover', + 'Colors' + ) + } + }, + template: ` + + + {{ text }} + + ` +}); + +export const withColor = () => ({ + template: ` +
+ + + Delete + + + + Add Item + + + + Watch out + +
+ ` +}); + +export const sizes = () => ({ + template: ` +
+ Extra small + Small + Default + Large + Extra large +
+ ` +}); diff --git a/src/components/v-chip/v-chip.test.ts b/src/components/v-chip/v-chip.test.ts new file mode 100644 index 0000000000..5dda62a1d0 --- /dev/null +++ b/src/components/v-chip/v-chip.test.ts @@ -0,0 +1,183 @@ +import { mount, createLocalVue, Wrapper } from '@vue/test-utils'; +import VueCompositionAPI from '@vue/composition-api'; +import VChip from './v-chip.vue'; +import VIcon from '@/components/v-icon/'; + +const localVue = createLocalVue(); +localVue.use(VueCompositionAPI); +localVue.component('v-icon', VIcon); + +describe('Chip', () => { + let component: Wrapper; + + beforeEach(() => { + component = mount(VChip, { localVue }); + }); + + it('Renders the provided markup in the default slow', () => { + const component = mount(VChip, { + localVue, + slots: { + default: 'Click me' + } + }); + + expect(component.text()).toContain('Click me'); + }); + + it('Hides the whole component', async () => { + component.setProps({ + active: false + }); + + await component.vm.$nextTick(); + + expect(component.find('span').exists()).toBe(false); + }); + + it('Adds the outline class for outline chips', async () => { + component.setProps({ + outlined: true + }); + + await component.vm.$nextTick(); + + expect(component.classes()).toContain('outlined'); + }); + + it('Adds the label class for block chips', async () => { + component.setProps({ + label: true + }); + + await component.vm.$nextTick(); + + expect(component.classes()).toContain('label'); + }); + + it('Adds the close icon for icon chips', async () => { + component.setProps({ + close: true + }); + + await component.vm.$nextTick(); + + expect(component.find('.close-outline').exists()).toBe(true); + }); + + it('Sets the correct CSS variables for custom colors', async () => { + component.setProps({ + color: '--red', + hoverColor: '--blue', + backgroundColor: '--green', + hoverBackgroundColor: '--yellow' + }); + + await component.vm.$nextTick(); + + expect((component.vm as any).styles['--_v-chip-color']).toBe('var(--red)'); + expect((component.vm as any).styles['--_v-chip-hover-color']).toBe('var(--blue)'); + expect((component.vm as any).styles['--_v-chip-background-color']).toBe('var(--green)'); + expect((component.vm as any).styles['--_v-chip-hover-background-color']).toBe( + 'var(--yellow)' + ); + }); + + it('Emits a click event when chip is not disabled', async () => { + component.setProps({ + disabled: false + }); + + await component.vm.$nextTick(); + + (component.vm as any).onClick(new Event('click')); + + expect(component.emitted('click')[0][0]).toBeInstanceOf(Event); + }); + + it('Does not emit click when disabled', async () => { + component.setProps({ + disabled: true + }); + + await component.vm.$nextTick(); + + (component.vm as any).onClick(new Event('click')); + + expect(component.emitted('click')).toBe(undefined); + }); + + it('Emits a click event when chip is not disabled and close button is clicked', async () => { + component.setProps({ + disabled: false + }); + + await component.vm.$nextTick(); + + (component.vm as any).onCloseClick(new Event('click')); + + expect(component.emitted('close')[0][0]).toBeInstanceOf(Event); + }); + + it('Does not emit click when disabled and close button is clicked', async () => { + component.setProps({ + disabled: true + }); + + await component.vm.$nextTick(); + + (component.vm as any).onCloseClick(new Event('click')); + + expect(component.emitted('click')).toBe(undefined); + }); + + describe('Sizes', () => { + const component = mount(VChip, { + localVue, + propsData: { + color: '--blue-grey', + name: 'person' + } + }); + + test('Extra Small', () => { + component.setProps({ + xSmall: true, + small: false, + large: false, + xLarge: false + }); + component.vm.$nextTick(() => expect(component.classes()).toContain('x-small')); + }); + + test('Small', () => { + component.setProps({ + xSmall: false, + small: true, + large: false, + xLarge: false + }); + component.vm.$nextTick(() => expect(component.classes()).toContain('small')); + }); + + test('Large', () => { + component.setProps({ + xSmall: false, + small: false, + large: true, + xLarge: false + }); + component.vm.$nextTick(() => expect(component.classes()).toContain('large')); + }); + + test('Extra Large', () => { + component.setProps({ + xSmall: false, + small: false, + large: false, + xLarge: true + }); + component.vm.$nextTick(() => expect(component.classes()).toContain('x-large')); + }); + }); +}); diff --git a/src/components/v-chip/v-chip.vue b/src/components/v-chip/v-chip.vue new file mode 100644 index 0000000000..139dbb6b3a --- /dev/null +++ b/src/components/v-chip/v-chip.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/src/components/v-overlay/v-overlay.test.ts b/src/components/v-overlay/v-overlay.test.ts index 7c9ba8ed47..1c8b0bbf89 100644 --- a/src/components/v-overlay/v-overlay.test.ts +++ b/src/components/v-overlay/v-overlay.test.ts @@ -1,4 +1,4 @@ -import { mount, createLocalVue, Wrapper } from '@vue/test-utils'; +import { shallowMount, createLocalVue, Wrapper } from '@vue/test-utils'; import VueCompositionAPI from '@vue/composition-api'; const localVue = createLocalVue(); @@ -10,19 +10,19 @@ describe('Overlay', () => { let component: Wrapper; beforeEach(() => { - component = mount(VOverlay, { + component = shallowMount(VOverlay, { localVue }); }); it('Is invisible when active prop is false', () => { - expect(component.isVisible()).toBe(false); + expect(component.classes()).toEqual(['v-overlay']); }); it('Is visible when active is true', async () => { component.setProps({ active: true }); await component.vm.$nextTick(); - expect(component.isVisible()).toBe(true); + expect(component.classes()).toEqual(['v-overlay', 'active']); }); it('Sets position absolute based on absolute prop', async () => { @@ -46,7 +46,7 @@ describe('Overlay', () => { }); it('Adds the has-click class when click event is passed', async () => { - const component = mount(VOverlay, { + const component = shallowMount(VOverlay, { localVue, listeners: { click: () => {} diff --git a/src/routes/debug.vue b/src/routes/debug.vue new file mode 100644 index 0000000000..fc2e35de79 --- /dev/null +++ b/src/routes/debug.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 7aadab60f8..6b32c1b650 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -341,6 +341,18 @@ body { --button-tertiary-background-color-disabled: var(--blue-grey-400); --button-tertiary-text-color-disabled: var(--blue-grey-600); + // Chip colors + + --chip-primary-text-color: var(--black); + --chip-primary-background-color: var(--blue-grey-100); + --chip-primary-background-color-hover: var(--blue-grey-200); + --chip-primary-close-color: var(--blue-grey-700); + --chip-primary-close-color-hover: var(--blue-grey-800); + --chip-primary-close-color-disabled: var(--blue-grey-200); + --chip-primary-background-color-disabled: var(--blue-grey-50); + --chip-primary-text-color-disabled: var(--blue-grey-300); + + // Table --table-head-border-color: var(--blue-grey-50); diff --git a/src/views/public/public-view.test.ts b/src/views/public/public-view.test.ts index 0360e82568..5d78ab0f9c 100644 --- a/src/views/public/public-view.test.ts +++ b/src/views/public/public-view.test.ts @@ -1,10 +1,12 @@ import VueCompositionAPI from '@vue/composition-api'; import { mount, createLocalVue } from '@vue/test-utils'; import { VTooltip } from 'v-tooltip'; +import VIcon from '@/components/v-icon/'; const localVue = createLocalVue(); localVue.use(VueCompositionAPI); localVue.directive('tooltip', VTooltip); +localVue.component('v-icon', VIcon); import PublicView from './public-view.vue';