Status interface (#471)

* Add status interface

* Add some tests

* Set active state of list item and add disabled
This commit is contained in:
Rijk van Zanten
2020-04-24 15:31:06 -04:00
committed by GitHub
parent bc0fbaa897
commit 41d77cde65
7 changed files with 261 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ import InterfaceDropdown from './dropdown/';
import InterfaceDropdownMultiselect from './dropdown-multiselect/';
import InterfaceRadioButtons from './radio-buttons';
import InterfaceCheckboxes from './checkboxes';
import InterfaceStatus from './status';
export const interfaces = [
InterfaceTextInput,
@@ -22,6 +23,7 @@ export const interfaces = [
InterfaceDropdownMultiselect,
InterfaceRadioButtons,
InterfaceCheckboxes,
InterfaceStatus,
];
export default interfaces;

View File

@@ -0,0 +1,17 @@
import InterfaceStatus from './status.vue';
import { defineInterface } from '@/interfaces/define';
export default defineInterface(({ i18n }) => ({
id: 'status',
name: i18n.t('status'),
icon: 'bubble_chart',
component: InterfaceStatus,
options: [
{
field: 'status_mapping',
name: i18n.t('status_mapping'),
width: 'full',
interface: 'code',
},
],
}));

View File

@@ -0,0 +1,26 @@
# Status Interface
Renders a dropdown with the available status options.
## Options
| Option | Description | Default |
|------------------|-----------------------------|---------|
| `status_mapping` | What statuses are available | `null` |
### Status Mapping format
```ts
type Status = {
[key: string]: {
name: string;
text_color: string;
background_color: string;
soft_delete: boolean;
published: boolean;
}
}
```
`status_mapping` is the only option for an interface that isn't camelCased. This is due to the fact
that the API relies on the same setting for it's permissions management.

View File

@@ -0,0 +1,55 @@
import withPadding from '../../../.storybook/decorators/with-padding';
import { defineComponent, ref } from '@vue/composition-api';
import { boolean, withKnobs, object } from '@storybook/addon-knobs';
import readme from './readme.md';
import RawValue from '../../../.storybook/raw-value.vue';
import i18n from '@/lang';
export default {
title: 'Interfaces / Status',
decorators: [withPadding, withKnobs],
parameters: {
notes: readme,
},
};
export const basic = () =>
defineComponent({
i18n,
components: { RawValue },
props: {
statusMapping: {
default: object('Status Mapping', {
published: {
name: 'Published',
background_color: 'var(--primary)',
},
draft: {
name: 'Draft',
background_color: 'var(--background-normal)',
},
deleted: {
name: 'Deleted',
background_color: 'var(--danger)',
},
}),
},
disabled: {
default: boolean('Disabled', false),
},
},
setup() {
const value = ref(null);
return { value };
},
template: `
<div style="max-width: 300px;">
<interface-status
v-model="value"
:status_mapping="statusMapping"
:disabled="disabled"
/>
<raw-value>{{ value }}</raw-value>
</div>
`,
});

View File

@@ -0,0 +1,66 @@
import VueCompositionAPI from '@vue/composition-api';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import InterfaceStatus from './status.vue';
import VNotice from '@/components/v-notice';
import VMenu from '@/components/v-menu';
import VList, { VListItem, VListItemIcon, VListItemContent } from '@/components/v-list';
import i18n from '@/lang';
const localVue = createLocalVue();
localVue.use(VueCompositionAPI);
localVue.component('v-notice', VNotice);
localVue.component('v-menu', VMenu);
localVue.component('v-list', VList);
localVue.component('v-list-item', VListItem);
localVue.component('v-list-item-content', VListItemContent);
localVue.component('v-list-item-icon', VListItemIcon);
describe('Interfaces / Slider', () => {
it('Renders a notice when status mapping is missing', () => {
const component = shallowMount(InterfaceStatus, {
localVue,
i18n,
propsData: {},
});
expect(component.find(VNotice).exists()).toBe(true);
});
it('Converts the status mapping into a loopable array', () => {
const component = shallowMount(InterfaceStatus, {
localVue,
i18n,
propsData: {
status_mapping: null,
},
});
expect((component.vm as any).statuses).toBe(null);
component.setProps({
status_mapping: {
test: {
name: 'Test',
background_color: '#abcabc',
},
another_test: {
name: 'Another Test',
background_color: '#123123',
},
},
});
expect((component.vm as any).statuses).toEqual([
{
value: 'test',
name: 'Test',
color: '#abcabc',
},
{
value: 'another_test',
name: 'Another Test',
color: '#123123',
},
]);
});
});

View File

@@ -0,0 +1,90 @@
<template>
<v-notice v-if="!statuses">
{{ $t('statuses_not_configured') }}
</v-notice>
<v-menu v-else attached :disabled="disabled">
<template #activator="{ toggle }">
<v-input
readonly
@click="toggle"
:value="current ? current.name : null"
:placeholder="$t('choose_status')"
:disabled="disabled"
>
<template #prepend>
<div
class="status-dot"
:style="current ? { backgroundColor: current.background_color } : null"
/>
</template>
</v-input>
</template>
<v-list dense>
<v-list-item
v-for="status in statuses"
:key="status.value"
:active="status.value === value"
@click="$emit('input', status.value)"
>
<v-list-item-icon>
<div class="status-dot" :style="{ backgroundColor: status.color }" />
</v-list-item-icon>
<v-list-item-content>{{ status.name }}</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
export default defineComponent({
props: {
value: {
type: String,
default: null,
},
status_mapping: {
type: Object,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
},
setup(props) {
const statuses = computed(() => {
if (props.status_mapping === null) return null;
return Object.keys(props.status_mapping).map((key: string) => {
const status = props.status_mapping[key];
return {
value: key,
name: status.name,
color: status.background_color,
};
});
});
const current = computed(() => {
if (props.value === null) return null;
if (props.status_mapping.hasOwnProperty(props.value) === false) return null;
return props.status_mapping[props.value] || null;
});
return { statuses, current };
},
});
</script>
<style lang="scss" scoped>
.status-dot {
width: 12px;
height: 12px;
border-radius: 6px;
}
</style>

View File

@@ -122,6 +122,8 @@
"radio_buttons": "Radio Buttons",
"checkboxes": "Checkboxes",
"choose_status": "Choose Status...",
"users": "Users",
"files": "Files",
"activity": "Activity",
@@ -351,6 +353,9 @@
"other": "Other...",
"statuses_not_configured": "Status mapping option configured incorrectly",
"status_mapping": "Status Mapping",
"about_directus": "About Directus",
"activity_log": "Activity Log",
"add_field_filter": "Add a field filter",