Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

feat(NavigationDrawer): introduce NavigationDrawer component #203

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
name: 'auto-completes',
component: () => import('@/views/AutoCompleteView.vue'),
},
{
path: '/navigation-drawers',
name: 'navigation-drawers',
component: () => import('@/views/NavigationDrawerView.vue'),
},
{
path: '/breakpoint',
name: 'breakpoint',
Expand All @@ -160,4 +165,4 @@
],
});

export default router;

Check warning on line 168 in example/src/router/index.ts

View workflow job for this annotation

GitHub Actions / ci

Prefer named exports
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
Loading