mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add badge component (#24)
* added badge component * revert button changes * added color vars * remove unit test for colors * finish up badge * add unit tests * use css vars for offset, improve readability * use css for positioning * use css for size * fix lint * fix unit tests * removed unnecessary span * Tweak markup, use template instead of span * Use px for offset * Ignore zero-no-unit in offset default value * Allow px after 0 value in css * Use span instead of template * Update readme and storybook * Update the tests Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
4
src/components/v-badge/index.ts
Normal file
4
src/components/v-badge/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VBadge from './v-badge.vue';
|
||||
|
||||
export { VBadge };
|
||||
export default VBadge;
|
||||
43
src/components/v-badge/v-badge.readme.md
Normal file
43
src/components/v-badge/v-badge.readme.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Badge
|
||||
|
||||
```html
|
||||
<v-badge value="2"><v-icon>ABC</v-icon></v-badge>
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
You can set the color, background color and boder color with the `--v-badge-color`, `--v-badge-background-color` and `--v-badge-border-color` css vars respectively:
|
||||
|
||||
```html
|
||||
<v-badge
|
||||
value="11"
|
||||
style="--v-badge-color: var(--red-500);"
|
||||
>
|
||||
<v-icon>ABC</v-icon>
|
||||
</v-badge>
|
||||
```
|
||||
|
||||
## Props
|
||||
| Prop | Description | Default |
|
||||
|--------------------------|-----------------------------------------------------------------------------|------------------------------------|
|
||||
| `value` | The value that will be displayed inside the badge Only 2 characters allowed)| `null` |
|
||||
| `dot` | Only will show a small dot without any content | `false` |
|
||||
| `bordered` | Shows a border arround the badge | `false` |
|
||||
| `left` | Aligns the badge on the left side | `false` |
|
||||
| `bottom` | Aligns the badge on the bottom side | `false` |
|
||||
| `icon` | Shows an icon instead of text | `null` |
|
||||
|
||||
## Slots
|
||||
N/A
|
||||
|
||||
## Events
|
||||
N/A
|
||||
|
||||
## CSS Variables
|
||||
| Variable | Default |
|
||||
|-------------------------------------|-------------------------------------------------|
|
||||
| `--v-badge-color` | `var(--white)` |
|
||||
| `--v-badge-background-color` | `var(--danger)` |
|
||||
| `--v-badge-border-color` | `var(--background-color)` |
|
||||
| `--v-badge-offset-x` | `0px` |
|
||||
| `--v-badge-offset-y` | `0px` |
|
||||
169
src/components/v-badge/v-badge.story.ts
Normal file
169
src/components/v-badge/v-badge.story.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { withKnobs, text, boolean, number, optionsKnob as options } from '@storybook/addon-knobs';
|
||||
import Vue from 'vue';
|
||||
import VBadge from './v-badge.vue';
|
||||
import VIcon from '../v-icon/';
|
||||
import markdown from './v-badge.readme.md';
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
|
||||
Vue.component('v-badge', VBadge);
|
||||
Vue.component('v-icon', VIcon);
|
||||
|
||||
export default {
|
||||
title: 'Components / Badge',
|
||||
component: VBadge,
|
||||
decorators: [withKnobs, withPadding],
|
||||
parameters: {
|
||||
notes: markdown
|
||||
}
|
||||
};
|
||||
|
||||
export const withButton = () => ({
|
||||
props: {
|
||||
value: {
|
||||
default: text('Text in badge', '3', 'Badge')
|
||||
},
|
||||
dot: {
|
||||
default: boolean('Dot', false, 'Badge')
|
||||
},
|
||||
left: {
|
||||
default: boolean('Left', false, 'Badge')
|
||||
},
|
||||
bottom: {
|
||||
default: boolean('Bottom', false, 'Badge')
|
||||
},
|
||||
offsetX: {
|
||||
default: number('Offset X', 0, undefined, 'Badge')
|
||||
},
|
||||
offsetY: {
|
||||
default: number('Offset Y', 0, undefined, 'Badge')
|
||||
},
|
||||
icon: {
|
||||
default: text('Icon', '', 'Badge')
|
||||
},
|
||||
bordered: {
|
||||
default: boolean('Bordered', false, 'Badge')
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-badge
|
||||
:value="value"
|
||||
:dot="dot"
|
||||
:left="left"
|
||||
:bottom="bottom"
|
||||
:icon="icon"
|
||||
:bordered="bordered"
|
||||
:style="{
|
||||
'--v-badge-offset-x': (offsetX + 'px'),
|
||||
'--v-badge-offset-y': (offsetY + 'px')
|
||||
}"
|
||||
>
|
||||
<v-button>Click me!</v-button>
|
||||
</v-badge>
|
||||
`
|
||||
});
|
||||
|
||||
export const withText = () => ({
|
||||
props: {
|
||||
value: {
|
||||
default: text('Text in badge', '3', 'Badge')
|
||||
},
|
||||
dot: {
|
||||
default: boolean('Dot', false, 'Badge')
|
||||
},
|
||||
left: {
|
||||
default: boolean('Left', false, 'Badge')
|
||||
},
|
||||
bottom: {
|
||||
default: boolean('Bottom', false, 'Badge')
|
||||
},
|
||||
offsetX: {
|
||||
default: number('Offset X', 0, undefined, 'Badge')
|
||||
},
|
||||
offsetY: {
|
||||
default: number('Offset Y', 0, undefined, 'Badge')
|
||||
},
|
||||
icon: {
|
||||
default: text('Icon', '', 'Badge')
|
||||
},
|
||||
bordered: {
|
||||
default: boolean('Bordered', false, 'Badge')
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-badge
|
||||
:value="value"
|
||||
:dot="dot"
|
||||
:left="left"
|
||||
:bottom="bottom"
|
||||
:icon="icon"
|
||||
:bordered="bordered"
|
||||
:style="{
|
||||
'--v-badge-offset-x': (offsetX + 'px'),
|
||||
'--v-badge-offset-y': (offsetY + 'px')
|
||||
}"
|
||||
>
|
||||
Example text.
|
||||
</v-badge>
|
||||
`
|
||||
});
|
||||
|
||||
export const withAvatar = () => ({
|
||||
props: {
|
||||
value: {
|
||||
default: text('Text in badge', '3', 'Badge')
|
||||
},
|
||||
dot: {
|
||||
default: boolean('Dot', false, 'Badge')
|
||||
},
|
||||
left: {
|
||||
default: boolean('Left', false, 'Badge')
|
||||
},
|
||||
bottom: {
|
||||
default: boolean('Bottom', false, 'Badge')
|
||||
},
|
||||
icon: {
|
||||
default: text('Icon', '', 'Badge')
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<v-badge
|
||||
:value="value"
|
||||
:dot="dot"
|
||||
:left="left"
|
||||
:bottom="bottom"
|
||||
:icon="icon"
|
||||
:bordered="true"
|
||||
:style="{
|
||||
'--v-badge-offset-x': '3px',
|
||||
'--v-badge-offset-y': '3px'
|
||||
}"
|
||||
>
|
||||
<v-avatar>RVZ</v-avatar>
|
||||
</v-badge>
|
||||
`
|
||||
});
|
||||
|
||||
export const colors = () => ({
|
||||
template: `
|
||||
<div>
|
||||
<v-badge
|
||||
style="--v-badge-color: var(--white);--v-badge-background-color: var(--red-500); margin-right: 20px"
|
||||
value="9+"
|
||||
>
|
||||
<v-button>Click me!</v-button>
|
||||
</v-badge>
|
||||
<v-badge
|
||||
style="--v-badge-color: var(--white);--v-badge-background-color: var(--green-500); margin-right: 20px"
|
||||
icon="spa"
|
||||
>
|
||||
<v-button>Click me!</v-button>
|
||||
</v-badge>
|
||||
<v-badge
|
||||
style="--v-badge-color: var(--white);--v-badge-background-color: var(--orange-500)"
|
||||
icon="notifications_active"
|
||||
>
|
||||
<v-button>Click me!</v-button>
|
||||
</v-badge>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
68
src/components/v-badge/v-badge.test.ts
Normal file
68
src/components/v-badge/v-badge.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
import VBadge from './v-badge.vue';
|
||||
import VIcon from '../v-icon/';
|
||||
|
||||
describe('Chip', () => {
|
||||
let component: Wrapper<Vue>;
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(VBadge, { localVue });
|
||||
});
|
||||
|
||||
it('Adds the dot prop to the badge', async () => {
|
||||
component.setProps({
|
||||
dot: true
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('dot');
|
||||
});
|
||||
|
||||
it('Adds the bordered prop to the badge', async () => {
|
||||
component.setProps({
|
||||
bordered: true
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('bordered');
|
||||
});
|
||||
|
||||
it('Display the badge on the left', async () => {
|
||||
component.setProps({
|
||||
left: true
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('left');
|
||||
});
|
||||
|
||||
it('Display the badge on the bottom', async () => {
|
||||
component.setProps({
|
||||
bottom: true
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.badge').classes()).toContain('bottom');
|
||||
});
|
||||
|
||||
it('Checks if the icon exists and if the name matches', async () => {
|
||||
component.setProps({
|
||||
icon: 'add'
|
||||
});
|
||||
|
||||
await component.vm.$nextTick();
|
||||
|
||||
expect(component.find('.v-icon').exists()).toBe(true);
|
||||
expect(component.find('.v-icon').props().name).toBe('add');
|
||||
});
|
||||
});
|
||||
105
src/components/v-badge/v-badge.vue
Normal file
105
src/components/v-badge/v-badge.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="v-badge" :class="{ dot, bordered }">
|
||||
<span class="badge" :class="{ dot, bordered, left, bottom }">
|
||||
<v-icon v-if="icon" :name="icon" :color="color" x-small />
|
||||
<span v-else>{{ value }}</span>
|
||||
</span>
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { createComponent, reactive, computed, Ref } from '@vue/composition-api';
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
left: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
bottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-badge {
|
||||
--v-badge-color: var(--white);
|
||||
--v-badge-background-color: var(--danger);
|
||||
--v-badge-border-color: var(--background-color);
|
||||
--v-badge-offset-x: 0px;
|
||||
--v-badge-offset-y: 0px;
|
||||
--v-badge-size: 20px;
|
||||
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&.bordered {
|
||||
--v-badge-size: 24px;
|
||||
}
|
||||
|
||||
&.dot {
|
||||
--v-badge-size: 12px;
|
||||
|
||||
&.bordered {
|
||||
--v-badge-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: calc(var(--v-badge-size) / -2 + var(--v-badge-offset-y));
|
||||
right: calc(var(--v-badge-size) / -2 + var(--v-badge-offset-x));
|
||||
z-index: 10;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
min-width: var(--v-badge-size);
|
||||
height: var(--v-badge-size);
|
||||
padding: 0 4px;
|
||||
color: var(--v-badge-color);
|
||||
font-size: 12px;
|
||||
background-color: var(--v-badge-background-color);
|
||||
border-radius: calc(var(--v-badge-size) / 2);
|
||||
|
||||
&.left {
|
||||
right: unset;
|
||||
left: calc(var(--v-badge-size) / -2 + var(--v-badge-offset-x));
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
top: unset;
|
||||
bottom: calc(var(--v-badge-size) / -2 + var(--v-badge-offset-y));
|
||||
}
|
||||
|
||||
&.dot * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.bordered {
|
||||
border: 2px solid var(--v-badge-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,3 @@
|
||||
/* stylelint-disable custom-property-empty-line-before */
|
||||
:root {
|
||||
--transition: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-in: cubic-bezier(0, 0, 0.2, 1);
|
||||
|
||||
Reference in New Issue
Block a user