Skip to content

Commit

Permalink
feat(NavigationDrawer): introduce NavigationDrawer component
Browse files Browse the repository at this point in the history
  • Loading branch information
lukicenturi committed Jun 10, 2024
1 parent ac26bb8 commit 127d366
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 7 deletions.
35 changes: 35 additions & 0 deletions example/cypress/e2e/overlays/navigation-drawer.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// https://docs.cypress.io/api/introduction/api.html

describe('navigation drawer', () => {
beforeEach(() => {
cy.visit('/navigation-drawers');
});

it('check non-persistent navigation-drawer', () => {
cy.contains('h2[data-cy=navigation-drawers]', 'Navigation Drawers');

cy.get('div[data-cy=navigation-drawer-0]').as('defaultDrawer');

// open dialog
cy.get('@defaultDrawer').find('[data-cy=activator]').as('activator');
cy.get('@activator').trigger('click');
cy.get('body').find('aside').should('be.visible');

// should close the dialog
cy.get('h2[data-cy=navigation-drawers]').trigger('click');
cy.get('body').find('aside').should('not.be.visible');
});

it('check persistent dialog', () => {
cy.get('div[data-cy=navigation-drawer-2]').as('defaultDrawer');

// open dialog
cy.get('@defaultDrawer').find('[data-cy=activator]').as('activator');
cy.get('@activator').trigger('click');
cy.get('body').find('aside').should('be.visible');

// should not close the dialog
cy.get('h2[data-cy=navigation-drawers]').trigger('click');
cy.get('body').find('aside').should('be.visible');
});
});
1 change: 1 addition & 0 deletions example/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const navigation = ref([
{ to: { name: 'bottom-sheets' }, title: 'Bottom Sheets' },
{ to: { name: 'color-pickers' }, title: 'Color Picker' },
{ to: { name: 'auto-completes' }, title: 'Auto Completes' },
{ to: { name: 'navigation-drawers' }, title: 'Navigation Drawer' },
],
},
{
Expand Down
5 changes: 5 additions & 0 deletions example/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ const router = new VueRouter({
name: 'auto-completes',
component: () => import('@/views/AutoCompleteView.vue'),
},
{
path: '/navigation-drawers',
name: 'navigation-drawers',
component: () => import('@/views/NavigationDrawerView.vue'),
},
{
path: '/breakpoint',
name: 'breakpoint',
Expand Down
53 changes: 53 additions & 0 deletions example/src/views/NavigationDrawerView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { type NavigationDrawerProps, RuiButton, RuiNavigationDrawer } from '@rotki/ui-library-compat';
interface ExtraProperties {
label: string;
}
type NavigationDrawerData = NavigationDrawerProps & ExtraProperties;
const navigationDrawers = ref<NavigationDrawerData[]>([
{ value: false, label: 'Left', temporary: true },
{ value: false, label: 'Right', position: 'right', temporary: true },
{ value: false, label: 'Persistent', temporary: false },
]);
</script>

<template>
<div>
<h2
class="text-h4 mb-6"
data-cy="navigation-drawers"
>
Navigation Drawers
</h2>
<div class="grid gap-4 grid-cols-2">
<div
v-for="(navigationDrawer, i) in navigationDrawers"
:key="i"
:data-cy="`navigation-drawer-${i}`"
>
<RuiNavigationDrawer
v-bind="navigationDrawer"
v-model="navigationDrawer.value"
content-class="!top-16"
>
<template #activator="{ on }">
<RuiButton
color="primary"
data-cy="activator"
v-on="on"
>
{{ navigationDrawer.label }}
</RuiButton>
</template>

<div class="p-4">
{{ navigationDrawer.label }} Navigation Drawer
</div>
</RuiNavigationDrawer>
</div>
</div>
</div>
</template>
6 changes: 6 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ import {
type Props as AutoCompleteProps,
default as RuiAutoComplete,
} from '@/components/forms/auto-complete/RuiAutoComplete.vue';
import {
type Props as NavigationDrawerProps,
default as RuiNavigationDrawer,
} from '@/components/overlays/navigation-drawer/NavigationDrawer.vue';
import type {
TableColumn as DataTableColumn,
SortColumn as DataTableSortColumn,
Expand Down Expand Up @@ -197,6 +201,7 @@ export {
RuiDialog,
RuiColorPicker,
RuiAutoComplete,
RuiNavigationDrawer,
ProgressProps,
ChipProps,
TextFieldProps,
Expand Down Expand Up @@ -239,4 +244,5 @@ export {
DialogProps,
ColorPickerProps,
AutoCompleteProps,
NavigationDrawerProps,
};
14 changes: 13 additions & 1 deletion src/components/overlays/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export default defineComponent({
name: 'RuiTeleport',
props: {
disabled: { default: false, type: Boolean },
immediate: { default: false, type: Boolean },
},
render(_h) {
return _h();
Expand All @@ -18,14 +19,25 @@ export default defineComponent({
return proxy?.$teleport;
};

onUpdated(() => {
const update = () => {
const containerEl = getTeleportContainer();
if (!get(disabled) && containerEl) {
if (slots.default)
containerEl.updateNodes(id, slots.default);
else
containerEl.clearNodes(id);
}
};

onMounted(() => {
if (!get(props.immediate))
return;

update();
});

onUpdated(() => {
update();
});

onBeforeUnmount(() => {
Expand Down
6 changes: 0 additions & 6 deletions src/components/overlays/dialog/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,6 @@ function close() {
set(isOpen, false);
}
function transformPropsUnit(value?: string | number): string | undefined {
if (value === undefined || (typeof value === 'string' && isNaN(Number(value))))
return value;
return `${value}px`;
}
const style = computed(() => ({
width: transformPropsUnit(get(width)),
maxWidth: transformPropsUnit(get(maxWidth)),
Expand Down
128 changes: 128 additions & 0 deletions src/components/overlays/navigation-drawer/NavigationDrawer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { describe, expect, it, vi } from 'vitest';
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import { TeleportPlugin } from '@/components/overlays/teleport-container';
import NavigationDrawer from '@/components/overlays/navigation-drawer/NavigationDrawer.vue';
import Button from '@/components/buttons/button/Button.vue';

const text = 'Navigation Drawer Content';

Vue.use(TeleportPlugin);

function createWrapper(options?: any) {
return mount(NavigationDrawer, {
...options,
scopedSlots: {
activator: `<rui-button id="trigger" v-on="props.on">
Click me!
</rui-button>`,
default: `
<div>
${text}
<rui-button id="close" @click="props.close()" />
</div>
`,
},
stubs: { RuiButton: Button },
});
}

describe('dialog', () => {
it('renders properly', async () => {
const wrapper = createWrapper();
await nextTick();
let drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;

expect(drawer).toBeFalsy();

// Open drawer by clicking activator
await wrapper.find('#trigger').trigger('click');

drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;

expect(drawer).toBeTruthy();

// Click the button that call close function
const closeButton = drawer.querySelector('#close') as HTMLButtonElement;
closeButton.click();

await nextTick();

drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;

expect(drawer).toBeFalsy();
wrapper.destroy();
});

it('should pass width and position props', async () => {
const wrapper = createWrapper();
await nextTick();

// Open dialog by clicking activator
await wrapper.find('#trigger').trigger('click');

let drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;
expect(drawer).toBeTruthy();

expect(drawer.style.width).toBe('360px');

await wrapper.setProps({
position: 'right',
width: '500',
});

drawer = document.body.querySelector('aside[class*=_visible_][class*=_right_]') as HTMLDivElement;

expect(drawer).toBeTruthy();
expect(drawer.style.width).toBe('500px');

wrapper.destroy();
});

it('dialog works with `temporary=false`', async () => {
const wrapper = createWrapper();
await nextTick();

// Open dialog by clicking activator
await wrapper.find('#trigger').trigger('click');

let drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;
expect(drawer).toBeTruthy();

// Click outside should not close the drawer
document.body.click();
await vi.delay();

drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;

expect(drawer).toBeTruthy();

wrapper.destroy();
});

it('dialog works with `temporary=true`', async () => {
const wrapper = createWrapper({
propsData: {
temporary: true,
},
});
await nextTick();

// Open dialog by clicking activator
await wrapper.find('#trigger').trigger('click');

let drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;
expect(drawer).toBeTruthy();

// Click outside should not close the drawer
document.body.click();
await vi.delay();

drawer = document.body.querySelector('aside[class*=_visible_]') as HTMLDivElement;

expect(drawer).toBeFalsy();

wrapper.destroy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Button from '@/components/buttons/button/Button.vue';
import NavigationDrawer, { type NavigationDrawerProps } from './NavigationDrawer.vue';
import type { Meta, StoryFn, StoryObj } from '@storybook/vue';

const render: StoryFn<NavigationDrawerProps> = args => ({
components: { Button, NavigationDrawer },
setup() {
const value = computed({
get() {
return args.value;
},
set(val) {
args.value = val;
},
});

return { args, value };
},
template: `
<NavigationDrawer v-bind="args" v-model='value'>
<template #activator="{ on }">
<Button v-on="on">
Click me!
</Button>
</template>
<div class="p-4">
Navigation Drawer
</div>
</NavigationDrawer>
`,
});

const meta: Meta<NavigationDrawerProps> = {
args: {},
argTypes: {
position: {
control: 'select',
options: ['left', 'right'],
table: { category: 'State' },
},
temporary: { control: 'boolean' },
width: { control: 'text' },
},
component: NavigationDrawer,
parameters: {
docs: {
controls: { exclude: ['default'] },
},
},
render,
tags: ['autodocs'],
title: 'Components/Overlays/NavigationDrawer',
};

type Story = StoryObj<NavigationDrawerProps>;

export const Default: Story = {
args: {
temporary: true,
},
};

export const Right: Story = {
args: {
position: 'right',
temporary: true,
},
};

export const Persistent: Story = {
args: {},
};

export default meta;
Loading

0 comments on commit 127d366

Please sign in to comment.