Rework status dot display

This commit is contained in:
rijkvanzanten
2020-09-03 12:57:04 -04:00
parent 4078506e8c
commit 027e66bb0c
8 changed files with 130 additions and 192 deletions

View File

@@ -0,0 +1,70 @@
<template>
<div>
<value-null v-if="value === null" />
<div class="dot" :style="styles">{{ displayValue }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, PropType } from '@vue/composition-api';
import formatTitle from '@directus/format-title';
type Choice = {
value: string;
text: string;
color: string | null;
};
export default defineComponent({
props: {
value: {
type: String,
default: null,
},
choices: {
type: Array as PropType<Choice[]>,
default: () => [],
},
defaultBackground: {
type: String,
default: '#eceff1',
},
defaultForeground: {
type: String,
default: '#263238',
},
},
setup(props) {
const currentChoice = computed(() => {
return props.choices.find((choice) => {
return choice.value === props.value;
});
});
const displayValue = computed(() => {
if (!currentChoice.value) return formatTitle(props.value);
return currentChoice.value.text;
});
const styles = computed(() => {
return {
backgroundColor: currentChoice.value?.color || props.defaultForeground,
};
});
return { displayValue, styles };
},
});
</script>
<style lang="scss" scoped>
.dot {
display: inline-block;
flex-shrink: 0;
width: 12px;
height: 12px;
margin: 0 4px;
vertical-align: middle;
border-radius: 6px;
}
</style>

View File

@@ -0,0 +1,58 @@
import { defineDisplay } from '@/displays/define';
import DisplayColorDot from './color-dot.vue';
export default defineDisplay(({ i18n }) => ({
id: 'color-dot',
name: i18n.t('color_dot'),
types: ['string'],
icon: 'flag',
handler: DisplayColorDot,
options: [
{
field: 'defaultColor',
name: i18n.t('default_color'),
type: 'string',
meta: {
interface: 'color',
width: 'half',
},
schema: {
default_value: '#eceff1',
},
},
{
field: 'choices',
name: i18n.t('choices'),
type: 'json',
meta: {
interface: 'repeater',
options: {
template: '{{text}}',
fields: [
{
field: 'value',
name: i18n.t('value'),
type: 'string',
meta: {
interface: 'text-input',
options: {
font: 'monospace',
},
width: 'half',
},
},
{
field: 'color',
name: i18n.t('color'),
type: 'string',
meta: {
interface: 'color',
width: 'half',
},
},
],
},
},
},
],
}));

View File

@@ -9,7 +9,7 @@ import DisplayImage from './image';
import DisplayMimeType from './mime-type';
import DisplayRating from './rating';
import DisplayRaw from './raw';
import DisplayStatusDot from './status-dot/';
import DisplayColorDot from './color-dot/';
import DisplayTags from './tags/';
import DisplayTemplate from './template';
import DisplayUser from './user';
@@ -26,7 +26,7 @@ export const displays = [
DisplayImage,
DisplayMimeType,
DisplayRating,
DisplayStatusDot,
DisplayColorDot,
DisplayTags,
DisplayTemplate,
DisplayUser,

View File

@@ -1,11 +0,0 @@
import { defineDisplay } from '@/displays/define';
import DisplayStatusDot from './status-dot.vue';
export default defineDisplay(({ i18n }) => ({
id: 'status-dot',
name: i18n.t('status_dot'),
types: ['string'],
icon: 'flag',
handler: DisplayStatusDot,
options: null,
}));

View File

@@ -1,4 +0,0 @@
# Status Dot
Renders the background color as set in the status mapping of the status type interface as a small dot.

View File

@@ -1,53 +0,0 @@
import withPadding from '../../../.storybook/decorators/with-padding';
import { withKnobs, text, object } from '@storybook/addon-knobs';
import readme from './readme.md';
import { defineComponent } from '@vue/composition-api';
export default {
title: 'Displays / Status (Dot)',
decorators: [withPadding, withKnobs],
parameters: {
notes: readme,
},
};
const defaultStatusMapping = {
published: {
name: 'Published',
value: 'published',
text_color: '#fff',
background_color: 'var(--primary)',
},
draft: {
name: 'Draft',
value: 'draft',
text_color: 'var(--primary-subdued)',
background_color: 'var(--background-subdued)',
},
deleted: {
name: 'Deleted',
value: 'deleted',
text_color: 'var(--danger)',
background_color: 'var(--danger-alt)',
},
};
export const basic = () =>
defineComponent({
props: {
value: {
default: text('Value', 'published'),
},
statusMapping: {
default: object('Status Mapping', defaultStatusMapping),
},
},
template: `
<display-status-dot
:value="value"
:interface-options="{
status_mapping: statusMapping,
}"
/>
`,
});

View File

@@ -1,72 +0,0 @@
import DisplayStatusDot from './status-dot.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VIcon from '@/components/v-icon';
import VueCompositionAPI from '@vue/composition-api';
import Tooltip from '@/directives/tooltip';
const localVue = createLocalVue();
localVue.component('v-icon', VIcon);
localVue.use(VueCompositionAPI);
localVue.directive('tooltip', Tooltip);
describe('Displays / Status Dot', () => {
it('Renders an empty span if no value is passed', () => {
const component = shallowMount(DisplayStatusDot, {
localVue,
propsData: {
value: null,
},
});
expect(component.find('span').exists()).toBe(true);
expect(component.find('span').text()).toBe('');
});
it('Renders a question mark icon is status is unknown in interface options', () => {
const component = shallowMount(DisplayStatusDot, {
localVue,
propsData: {
value: 'draft',
interfaceOptions: {
status_mapping: {
published: {},
},
},
},
});
expect(component.find(VIcon).exists()).toBe(true);
expect(component.attributes('name')).toBe('help_outline');
});
it('Renders the dot with the correct color', () => {
const component = shallowMount(DisplayStatusDot, {
localVue,
propsData: {
value: 'draft',
interfaceOptions: {
status_mapping: {
draft: {
background_color: 'rgb(171, 202, 188)',
},
},
},
},
});
expect(component.exists()).toBe(true);
expect(component.attributes('style')).toBe('background-color: rgb(171, 202, 188);');
});
it('Sets status to null if interface options are missing', () => {
const component = shallowMount(DisplayStatusDot, {
localVue,
propsData: {
value: 'draft',
interfaceOptions: null,
},
});
expect((component.vm as any).status).toBe(null);
});
});

View File

@@ -1,50 +0,0 @@
<template>
<span v-if="!value" />
<v-icon name="help_outline" small v-else-if="!status" />
<div
v-else
class="dot"
v-tooltip="status.name"
:style="{
backgroundColor: status.background_color,
}"
/>
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
export default defineComponent({
props: {
value: {
type: String,
default: null,
},
interfaceOptions: {
type: Object,
default: null,
},
},
setup(props) {
const status = computed(() => {
if (props.interfaceOptions === null) return null;
return props.interfaceOptions.status_mapping?.[props.value];
});
return { status };
},
});
</script>
<style lang="scss" scoped>
.dot {
display: inline-block;
flex-shrink: 0;
width: 12px;
height: 12px;
margin: 0 4px;
vertical-align: middle;
border-radius: 6px;
}
</style>