Skip to content

Commit

Permalink
feat: add more components
Browse files Browse the repository at this point in the history
  • Loading branch information
devCrossNet committed Jul 2, 2022
1 parent 9085e1c commit d78fc72
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 0 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@vueuse/core": "8.6.0",
"animejs": "3.2.1",
"marked": "4.0.17",
"mitt": "3.0.0",
"vee-validate": "4.5.11"
},
"devDependencies": {
Expand Down
43 changes: 43 additions & 0 deletions src/components/data-display/VueToast/VueToast.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, beforeEach, test, expect } from 'vitest';
import { fireEvent, render, RenderResult } from '@testing-library/vue';
import VueToast from './VueToast.vue';
import { addToast } from '@/components/utils';
import { sleep } from '@/test/test-utils';

describe('VueToast.vue', () => {
let harness: RenderResult;

beforeEach(() => {
harness = render(VueToast);
});

test('displays toast for a user specified time (100ms)', async () => {
const { queryAllByText } = harness;

addToast({ title: 'info', text: 'this is a test', displayTimeInMs: 100 });
await sleep(10);

expect(queryAllByText('info')).toHaveLength(1);
expect(queryAllByText('this is a test')).toHaveLength(1);

await sleep(150);

expect(queryAllByText('info')).toHaveLength(0);
expect(queryAllByText('this is a test')).toHaveLength(0);
});

test('displays toast for until user clicks close icon', async () => {
const { queryAllByText, getByTestId } = harness;

addToast({ title: 'warning', text: 'this is a test', type: 'warning' });
await sleep(50);

expect(queryAllByText('warning')).toHaveLength(1);
expect(queryAllByText('this is a test')).toHaveLength(1);

await fireEvent.click(getByTestId('toast-close-button'));

expect(queryAllByText('info')).toHaveLength(0);
expect(queryAllByText('this is a test')).toHaveLength(0);
});
});
75 changes: 75 additions & 0 deletions src/components/data-display/VueToast/VueToast.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import VueToast from './VueToast.vue';
import { addToast } from '@/components/utils';
import VueInline from '@/components/layout/VueInline/VueInline.vue';
import VueButton from '@/components/input-and-actions/VueButton/VueButton.vue';
import ComponentDocs from '~/assets/design-system/docs/components/ComponentDocs.vue';

export default {
title: 'Data Display/Toast',
component: VueToast,
argTypes: {},
};

const Template = (args) => ({
components: {
VueToast,
VueButton,
ComponentDocs,
VueInline,
},

setup() {
return {
args,
onSuccessToastClick() {
addToast({
title: 'This is a success message!',
type: 'success',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod',
});
},
onInfoToastClick() {
addToast({
title: 'This is an information!',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod',
});
},
onWarningToastClick() {
addToast({
title: 'This is a warning!',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod',
type: 'warning',
displayTimeInMs: 15000,
});
},
onDangerToastClick() {
addToast({
title: 'This is an error!',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod',
type: 'danger',
displayTimeInMs: 30000,
});
},
};
},

template: `<component-docs
component-name="Toast"
usage="Used to communicate messages from the system to the user."
story="Show toast messages with their different properties."
>
<vue-toast />
<vue-inline stack-phone stack-tablet-portrait stack-tablet-landscape stack-small-desktop stack-large-desktop>
<vue-button look="primary" @click="onSuccessToastClick">add success toast</vue-button>
<vue-button look="secondary" @click="onInfoToastClick">add info toast</vue-button>
<vue-button look="outline" @click="onWarningToastClick">add warning toast</vue-button>
<vue-button look="danger" @click="onDangerToastClick">add danger toast</vue-button>
</vue-inline>
</component-docs>`,
});

export const Default = Template.bind({});

Default.args = {};
144 changes: 144 additions & 0 deletions src/components/data-display/VueToast/VueToast.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<template>
<div :class="$style.vueToast">
<transition-group name="list" tag="div">
<vue-box v-for="toast in orderedToasts" :key="toast.id" padding="16" :class="[$style.toast, $style[toast.type]]">
<vue-columns space="12" align-y="top">
<vue-column width="content">
<vue-text :color="toast.type">
<vue-icon-info v-if="['info', 'success'].includes(toast.type)" />
<vue-icon-exclamation v-if="['warning', 'danger'].includes(toast.type)" />
</vue-text>
</vue-column>
<vue-column>
<vue-stack space="4">
<vue-text :color="toast.type" weight="semi-bold">{{ toast.title }}</vue-text>
<vue-text look="label" :color="toast.type">{{ toast.text }}</vue-text>
</vue-stack>
</vue-column>

<vue-column width="content">
<vue-text
tabindex="0"
aria-label="close"
:color="toast.type"
data-testid="toast-close-button"
as="a"
href="#"
@click.native.stop.prevent="removeToast(toast)"
>
<vue-icon-times />
</vue-text>
</vue-column>
</vue-columns>
</vue-box>
</transition-group>
</div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { EventBus } from '~/services/EventBus';
import { IToast } from '@/interfaces/IToast';
import VueBox from '@/components/layout/VueBox/VueBox.vue';
import VueColumns from '@/components/layout/VueColumns/VueColumns.vue';
import VueColumn from '@/components/layout/VueColumns/VueColumn/VueColumn.vue';
import VueStack from '@/components/layout/VueStack/VueStack.vue';
import VueText from '@/components/typography/VueText/VueText.vue';
import VueIconInfo from '@/components/icons/VueIconInfoCircle.vue';
import VueIconTimes from '@/components/icons/VueIconTimes.vue';
import VueIconExclamation from '@/components/icons/VueIconExclamation.vue';
import { getGUID } from '~/components/utils';
const toasts = ref<IToast[]>([]);
const orderedToasts = computed<IToast[]>(() => toasts.value.slice(0).reverse());
const removeToast = (n: IToast) => {
toasts.value = toasts.value.filter((toast) => toast.id !== n.id);
};
const addToast = (n: IToast) => {
n.id = getGUID();
n.type = n.type || 'info';
n.displayTimeInMs = n.displayTimeInMs || 10000;
toasts.value.push(n);
setTimeout(() => removeToast(n), n.displayTimeInMs);
};
onMounted(() => {
EventBus.on('toast.add', addToast);
});
</script>

<style lang="scss" module>
@import 'assets/_design-system';
.vueToast {
position: fixed;
top: $toast-position-top;
right: $space-16;
z-index: $toast-index;
width: calc(100% - #{$space-32});
max-width: $toast-max-width;
:global {
.list-move {
transition: $toast-transition;
}
.list-enter {
opacity: 0;
transform: translateY(-100%);
}
.list-enter-active {
transition: $toast-transition;
}
.list-enter-to {
opacity: 1;
transform: translateY(0);
}
.list-leave {
opacity: 1;
transform: translateY(0);
}
.list-leave-active {
transition: $toast-transition;
}
.list-leave-to {
opacity: 0;
transform: translateY(100%);
}
}
.toast {
border-radius: $toast-border-radius;
box-shadow: $toast-elevation;
margin-bottom: $toast-gap;
i {
width: $toast-icons-size;
height: $toast-icons-size;
}
&.info {
background: $toast-info-bg;
border: $toast-info-border;
}
&.warning {
background: $toast-warning-bg;
border: $toast-warning-border;
}
&.danger {
background: $toast-danger-bg;
border: $toast-danger-border;
}
&.success {
background: $toast-success-bg;
border: $toast-success-border;
}
}
}
</style>
6 changes: 6 additions & 0 deletions src/components/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { IBreakpoints } from '~/interfaces/IBreakpoints';
import { BreakpointValues } from '~/components/prop-types';
import { EventBus } from '~/services/EventBus';
import { IToast } from '~/interfaces/IToast';

export interface CssSpacing {
top: string;
Expand Down Expand Up @@ -190,3 +192,7 @@ export const getIntInRange = (min: number, max: number): number => {
export const getFloatInRange = (min: number, max: number): number => {
return Math.random() * (max - min) + min;
};

export const addToast = (n: IToast): void => {
EventBus.emit('toast.add', n);
};
3 changes: 3 additions & 0 deletions src/services/EventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import mitt from 'mitt';

export const EventBus = mitt();
5 changes: 5 additions & 0 deletions src/services/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Services

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains services that can be used outside the vue and nuxt life-cycle.

0 comments on commit d78fc72

Please sign in to comment.