mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Rework status dot display
This commit is contained in:
70
app/src/displays/color-dot/color-dot.vue
Normal file
70
app/src/displays/color-dot/color-dot.vue
Normal 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>
|
||||
58
app/src/displays/color-dot/index.ts
Normal file
58
app/src/displays/color-dot/index.ts
Normal 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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}"
|
||||
/>
|
||||
`,
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user