Skip to content

Commit

Permalink
feat: adds tabs component (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukicenturi authored Sep 12, 2023
1 parent 9bcb91e commit 9c07ee2
Show file tree
Hide file tree
Showing 20 changed files with 1,285 additions and 9 deletions.
37 changes: 37 additions & 0 deletions example/cypress/e2e/tabs.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// https://docs.cypress.io/api/introduction/api.html

describe('Tabs', () => {
beforeEach(() => {
cy.visit('/tabs');
});

it('check for rendered tabs', () => {
cy.contains('h2[data-cy=tabs]', 'Tabs');

cy.get('[data-cy=wrapper-0]').as('wrapper');

cy.get('@wrapper')
.find('[data-cy=tabs]')
.find('[role=tablist]')
.as('tablist');

cy.get('@tablist').children().should('have.length', 6);
cy.get('@tablist')
.find('button:first-child')
.should('have.class', 'active-tab');
cy.get('@tablist').find('button:nth-child(2)').should('be.disabled');

cy.get('@wrapper').find('[data-cy=tab-items]').as('tabcontent');

cy.get('@tabcontent').should('have.text', 'Tab 1 Content');

// Click third tab
cy.get('@tablist').find('button:nth-child(3)').click();
cy.get('@tabcontent').should('have.text', 'Tab 3 Content');

// Click last tab should redirect to stepper page
cy.get('@tablist').find('a:last-child').click();

cy.url().should('contain', '/steppers');
});
});
1 change: 1 addition & 0 deletions example/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const navigation = ref([
{ to: { name: 'data-tables' }, title: 'Data Tables' },
{ to: { name: 'dialogs' }, title: 'Dialogs' },
{ to: { name: 'cards' }, title: 'Cards' },
{ to: { name: 'tabs' }, title: 'Tabs' },
],
},
]);
Expand Down
8 changes: 8 additions & 0 deletions example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import { createPinia } from 'pinia';
import {
RiAddFill,
RiAlertLine,
RiArrowDownSLine,
RiArrowLeftLine,
RiArrowLeftSLine,
RiArrowRightLine,
RiArrowRightSLine,
RiArrowUpSLine,
RiCheckboxCircleLine,
RiCloseFill,
RiErrorWarningLine,
Expand Down Expand Up @@ -38,6 +42,10 @@ app.use(RuiPlugin, {
RiCloseFill,
RiInformationLine,
RiErrorWarningLine,
RiArrowLeftSLine,
RiArrowRightSLine,
RiArrowUpSLine,
RiArrowDownSLine,
],
});

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 @@ -86,6 +86,11 @@ const router = createRouter({
name: 'cards',
component: () => import('@/views/CardView.vue'),
},
{
path: '/tabs',
name: 'tabs',
component: () => import('@/views/TabView.vue'),
},
],
});

Expand Down
107 changes: 107 additions & 0 deletions example/src/views/TabView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script setup lang="ts">
import {
RuiCard,
RuiIcon,
RuiTab,
RuiTabItem,
RuiTabItems,
RuiTabs,
} from '@rotki/ui-library';
import { ref } from 'vue';
import { type DataType } from '@/types';
type TabsData = DataType<typeof RuiTabs, string>;
const tabs = ref<TabsData[]>([
{
color: 'primary',
},
{
color: 'secondary',
},
{
color: 'error',
},
{
color: 'warning',
},
{
color: 'info',
},
{
color: 'success',
},
{
color: 'primary',
vertical: true,
},
{
color: 'secondary',
vertical: true,
},
{
color: 'error',
vertical: true,
},
{
color: 'warning',
vertical: true,
},
{
color: 'info',
vertical: true,
},
{
color: 'success',
vertical: true,
},
]);
</script>

<template>
<div>
<h2 class="text-h4 mb-6" data-cy="tabs">Tabs</h2>
<div
v-for="(data, i) in tabs"
:key="i"
class="flex mb-6 gap-x-6"
:class="data.vertical ? 'flex-row' : 'flex-col'"
:data-cy="`wrapper-${i}`"
>
<RuiTabs v-bind="data" v-model="data.modelValue" data-cy="tabs">
<RuiTab>
<template #prepend>
<RuiIcon name="add-fill" />
</template>
Tab 1
</RuiTab>
<RuiTab disabled>Tab 2</RuiTab>
<RuiTab>Tab 3</RuiTab>
<RuiTab>Tab 4</RuiTab>
<RuiTab>Tab 5</RuiTab>
<RuiTab link to="/steppers">Stepper View</RuiTab>
</RuiTabs>
<RuiTabItems v-model="data.modelValue" data-cy="tab-items">
<RuiTabItem>
<RuiCard>Tab 1 Content</RuiCard>
</RuiTabItem>
<RuiTabItem>
<RuiCard>Tab 2 Content</RuiCard>
</RuiTabItem>
<RuiTabItem>
<RuiCard>Tab 3 Content</RuiCard>
</RuiTabItem>
<RuiTabItem>
<RuiCard>Tab 4 Content</RuiCard>
</RuiTabItem>
<RuiTabItem>
<RuiCard>
Tab 5 Long Long Long Long Long Long Long Long Long Long Long Long
Long Long Long Long Long Long Long Long Long Long Long Long Long
Long Long Long Long Long Long Long Long Content
</RuiCard>
</RuiTabItem>
</RuiTabItems>
</div>
</div>
</template>
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
"@vueuse/shared": ">10.0.0",
"vue": ">=3.3.4"
},
"optionalDependencies": {
"vue-router": ">=3.6.5"
},
"devDependencies": {
"@babel/core": "7.22.9",
"@babel/types": "7.22.5",
Expand Down Expand Up @@ -145,6 +148,7 @@
"vitest": "0.34.1",
"vue": "3.3.4",
"vue-loader": "17.2.2",
"vue-router": "4.2.4",
"vue-tsc": "1.8.8"
},
"engines": {
Expand Down
14 changes: 9 additions & 5 deletions pnpm-lock.yaml

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

7 changes: 5 additions & 2 deletions src/components/buttons/button/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Props {
variant?: 'default' | 'outlined' | 'text' | 'fab';
icon?: boolean;
size?: 'sm' | 'lg';
tag?: 'button' | 'a';
}
defineOptions({
Expand All @@ -26,6 +27,7 @@ const props = withDefaults(defineProps<Props>(), {
variant: 'default',
icon: false,
size: undefined,
tag: 'button',
});
const { disabled, elevation, variant, size } = toRefs(props);
Expand Down Expand Up @@ -63,7 +65,8 @@ const spinnerSize: ComputedRef<number> = computed(() => {
</script>

<template>
<button
<Component
:is="tag"
:class="[
css.btn,
css[color ?? 'grey'],
Expand All @@ -89,7 +92,7 @@ const spinnerSize: ComputedRef<number> = computed(() => {
variant="indeterminate"
:size="spinnerSize"
/>
</button>
</Component>
</template>

<style lang="scss" module>
Expand Down
9 changes: 9 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines,import/max-dependencies */
import { default as RuiAlert } from '@/components/alerts/Alert.vue';
import {
type Props as AutoCompleteProps,
Expand Down Expand Up @@ -45,6 +46,10 @@ import {
type Props as CardProps,
default as RuiCard,
} from '@/components/cards/Card.vue';
import { default as RuiTabs } from '@/components/tabs/tabs/Tabs.vue';
import { default as RuiTab } from '@/components/tabs/tab/Tab.vue';
import { default as RuiTabItems } from '@/components/tabs/tab-items/TabItems.vue';
import { default as RuiTabItem } from '@/components/tabs/tab-item/TabItem.vue';

export {
RuiAlert,
Expand All @@ -67,6 +72,10 @@ export {
RuiSimpleSelect,
RuiDialog,
RuiCard,
RuiTabs,
RuiTab,
RuiTabItems,
RuiTabItem,
AutoCompleteProps,
ChipProps,
TooltipProps,
Expand Down
2 changes: 1 addition & 1 deletion src/components/steppers/StepperCustomIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const css = useCssModule();

<style lang="scss" module>
.indicator {
@apply inline-flex items-center justify-center rounded-full h-10 w-10 border text-white bg-rui-primary border-rui-primary;
@apply inline-flex items-center justify-center rounded-full h-10 w-10 shrink-0 border text-white bg-rui-primary border-rui-primary;
&.inactive {
@apply text-xs bg-white;
Expand Down
55 changes: 55 additions & 0 deletions src/components/tabs/tab-item/TabItem.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { type ComponentMountingOptions, mount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest';
import TabItem from '@/components/tabs/tab-item/TabItem.vue';

vi.mock('@headlessui/vue', () => ({
TransitionRoot: {
template: `
<div v-if='show'><slot /></div>
`,
props: {
show: { type: Boolean },
},
},
}));

const createWrapper = (options?: ComponentMountingOptions<typeof TabItem>) =>
mount(TabItem, {
...options,
props: {
value: 'value',
...options?.props,
},
});

describe('Tabs/TabItem', () => {
it('do not render if not active', () => {
const wrapper = createWrapper();

expect(wrapper.find('div').find('div').exists()).toBeFalsy();
});

it("render if it's active", async () => {
const wrapper = createWrapper({
props: {
active: true,
},
});

expect(wrapper.find('div').find('div').exists()).toBeTruthy();
expect(wrapper.find('div').find('div').classes()).not.toMatch(/hidden/);

await wrapper.setProps({
eager: true,
});
expect(wrapper.find('div').find('div').exists()).toBeTruthy();
expect(wrapper.find('div').find('div').classes()).not.toMatch(/hidden/);

await wrapper.setProps({
active: false,
});

expect(wrapper.find('div').find('div').exists()).toBeTruthy();
expect(wrapper.find('div').find('div').classes()).toMatch(/hidden/);
});
});
Loading

0 comments on commit 9c07ee2

Please sign in to comment.