Skip to content

Commit 8e33e98

Browse files
authored
feat(Switch): introduce switch component (#190)
1 parent fa38362 commit 8e33e98

File tree

9 files changed

+641
-0
lines changed

9 files changed

+641
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
describe('forms/Switch', () => {
2+
beforeEach(() => {
3+
cy.visit('/switches');
4+
});
5+
6+
it('checks for switches', () => {
7+
cy.contains('h2[data-cy=switches]', 'Switches');
8+
9+
cy.get('input[type="checkbox"]').first().as('firstSwitch');
10+
cy.get('input[type="checkbox"]').eq(1).as('secondSwitch');
11+
cy.get('input[type="checkbox"][disabled]').first().as('disabledSwitch');
12+
13+
cy.get('@firstSwitch').should('not.be.checked');
14+
cy.get('@firstSwitch').click();
15+
cy.get('@firstSwitch').should('be.checked');
16+
17+
cy.get('@secondSwitch').should('not.be.checked');
18+
cy.get('@secondSwitch').click();
19+
cy.get('@secondSwitch').should('be.checked');
20+
21+
cy.get('@disabledSwitch').should('be.disabled');
22+
});
23+
});

example/src/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const navigation = ref([
1313
{ to: { name: 'buttons' }, title: 'Buttons' },
1414
{ to: { name: 'icons' }, title: 'Icons' },
1515
{ to: { name: 'checkboxes' }, title: 'Checkboxes' },
16+
{ to: { name: 'switches' }, title: 'Switches' },
1617
{ to: { name: 'radios' }, title: 'Radio' },
1718
{ to: { name: 'text-fields' }, title: 'Text Fields' },
1819
{ to: { name: 'text-areas' }, title: 'Text Areas' },

example/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable import/max-dependencies */
12
import { createRouter, createWebHistory } from 'vue-router';
23
import ButtonView from '@/views/ButtonView.vue';
34

@@ -31,6 +32,11 @@ const router = createRouter({
3132
name: 'checkboxes',
3233
component: () => import('@/views/CheckboxView.vue'),
3334
},
35+
{
36+
path: '/switches',
37+
name: 'switches',
38+
component: () => import('@/views/SwitchView.vue'),
39+
},
3440
{
3541
path: '/radios',
3642
name: 'radios',

example/src/views/SwitchView.vue

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script lang="ts" setup>
2+
import { RuiSwitch, type SwitchProps } from '@rotki/ui-library';
3+
import { ref } from 'vue';
4+
5+
type SwitchData = SwitchProps & {
6+
value?: boolean;
7+
};
8+
9+
const switches = ref<SwitchData[]>([
10+
{ value: false, color: 'primary' },
11+
{ value: false, color: 'secondary' },
12+
{ value: false, color: 'error' },
13+
{ value: false, color: 'warning' },
14+
{ value: false, color: 'info' },
15+
{ value: false, color: 'success' },
16+
17+
{ value: true, color: 'primary' },
18+
{ value: true, color: 'secondary' },
19+
{ value: true, color: 'error' },
20+
{ value: true, color: 'warning' },
21+
{ value: true, color: 'info' },
22+
{ value: true, color: 'success' },
23+
24+
{ value: false, color: 'primary', size: 'sm' },
25+
{ value: false, color: 'secondary', size: 'sm' },
26+
{ value: false, color: 'error', size: 'sm' },
27+
{ value: false, color: 'warning', size: 'sm' },
28+
{ value: false, color: 'info', size: 'sm' },
29+
{ value: false, color: 'success', size: 'sm' },
30+
31+
{ value: false, color: 'primary', disabled: true },
32+
{ value: false, color: 'secondary', disabled: true },
33+
{ value: false, color: 'error', disabled: true },
34+
{ value: false, color: 'warning', disabled: true },
35+
{ value: false, color: 'info', disabled: true },
36+
{ value: false, color: 'success', disabled: true },
37+
38+
{ value: true, color: 'primary', disabled: true },
39+
{ value: true, color: 'secondary', disabled: true },
40+
{ value: true, color: 'error', disabled: true },
41+
{ value: true, color: 'warning', disabled: true },
42+
{ value: true, color: 'info', disabled: true },
43+
{ value: true, color: 'success', disabled: true },
44+
45+
{ value: false, color: 'primary', hint: 'Switch hint' },
46+
{ value: false, color: 'secondary', hint: 'Switch hint' },
47+
{ value: false, color: 'error', hint: 'Switch hint' },
48+
{ value: false, color: 'warning', hint: 'Switch hint' },
49+
{ value: false, color: 'info', hint: 'Switch hint' },
50+
{ value: false, color: 'success', hint: 'Switch hint' },
51+
52+
{ value: false, color: 'primary', errorMessages: ['Switch error message'] },
53+
{
54+
value: false,
55+
color: 'secondary',
56+
errorMessages: 'Switch error message',
57+
},
58+
{ value: false, color: 'error', errorMessages: ['Switch error message'] },
59+
{ value: false, color: 'warning', errorMessages: 'Switch error message' },
60+
{ value: false, color: 'info', errorMessages: ['Switch error message'] },
61+
{ value: false, color: 'success', errorMessages: 'Switch error message' },
62+
63+
{
64+
value: false,
65+
color: 'primary',
66+
successMessages: ['Switch success message'],
67+
},
68+
{
69+
value: false,
70+
color: 'secondary',
71+
successMessages: 'Switch success message',
72+
},
73+
{
74+
value: false,
75+
color: 'error',
76+
successMessages: ['Switch success message'],
77+
},
78+
{
79+
value: false,
80+
color: 'warning',
81+
successMessages: 'Switch success message',
82+
},
83+
{
84+
value: false,
85+
color: 'info',
86+
successMessages: ['Switch success message'],
87+
},
88+
{
89+
value: false,
90+
color: 'success',
91+
successMessages: 'Switch success message',
92+
},
93+
]);
94+
</script>
95+
96+
<template>
97+
<div>
98+
<h2
99+
class="text-h4 mb-6"
100+
data-cy="switches"
101+
>
102+
Switches
103+
</h2>
104+
<div class="grid gap-3 grid-rows-2 grid-cols-6">
105+
<RuiSwitch
106+
v-for="(sw, i) in switches"
107+
:key="i"
108+
v-model="sw.value"
109+
v-bind="sw"
110+
>
111+
<span class="capitalize"> {{ sw.color }} </span>
112+
</RuiSwitch>
113+
</div>
114+
</div>
115+
</template>

src/components/forms/checkbox/Checkbox.stories.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const meta: Meta<PropsAndLabel> = {
4242
label: { control: 'text' },
4343
modelValue: { control: 'boolean' },
4444
size: { control: 'select', options: ['medium', 'sm', 'lg'] },
45+
successMessages: { control: 'array', defaultValue: [] },
4546
},
4647
component: Checkbox,
4748
parameters: {
@@ -106,6 +107,13 @@ export const WithErrorMessage: Story = {
106107
},
107108
};
108109

110+
export const WithSuccessMessage: Story = {
111+
args: {
112+
label: 'Label',
113+
successMessages: ['With success messages'],
114+
},
115+
};
116+
109117
export const WithHint: Story = {
110118
args: {
111119
hint: 'With hint',
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { type ComponentMountingOptions, mount } from '@vue/test-utils';
3+
import Switch from '@/components/forms/switch/Switch.vue';
4+
5+
function createWrapper(options?: ComponentMountingOptions<typeof Switch>) {
6+
return mount(Switch, { ...options });
7+
}
8+
9+
describe('forms/Switch', () => {
10+
it('renders properly', () => {
11+
const label = 'Switch Label';
12+
const wrapper = createWrapper({
13+
slots: {
14+
default: () => label,
15+
},
16+
});
17+
expect(wrapper.text()).toContain(label);
18+
expect(wrapper.get('label > div > div').classes()).toMatch(/_toggle_/);
19+
});
20+
21+
it('passes disabled props', async () => {
22+
const wrapper = createWrapper();
23+
expect(wrapper.find('input').attributes('disabled')).toBeUndefined();
24+
expect(wrapper.get('label').classes()).not.toMatch(/_disabled_/);
25+
await wrapper.setProps({ disabled: true });
26+
expect(wrapper.find('input').attributes('disabled')).toBeDefined();
27+
expect(wrapper.get('label').classes()).toMatch(/_disabled_/);
28+
await wrapper.setProps({ disabled: false });
29+
expect(wrapper.find('input').attributes('disabled')).toBeUndefined();
30+
expect(wrapper.get('label').classes()).not.toMatch(/_disabled_/);
31+
});
32+
33+
it('passes color props', async () => {
34+
const wrapper = createWrapper({ props: { color: 'primary' } });
35+
expect(wrapper.find('label').classes()).toMatch(/_primary_/);
36+
37+
await wrapper.setProps({ color: 'secondary' });
38+
expect(wrapper.find('label').classes()).toMatch(/_secondary_/);
39+
40+
await wrapper.setProps({ color: 'error' });
41+
expect(wrapper.find('label').classes()).toMatch(/_error_/);
42+
43+
await wrapper.setProps({ color: 'success' });
44+
expect(wrapper.find('label').classes()).toMatch(/_success_/);
45+
});
46+
47+
it('passes size props', async () => {
48+
const wrapper = createWrapper({ props: { size: 'sm' } });
49+
expect(wrapper.find('label').classes()).toMatch(/_sm_/);
50+
});
51+
52+
it('passes hint props', async () => {
53+
const wrapper = createWrapper();
54+
expect(wrapper.find('.details > div').exists()).toBeFalsy();
55+
56+
const hint = 'Switch Hints';
57+
await wrapper.setProps({ hint });
58+
expect(wrapper.find('.details > div').classes()).toMatch(
59+
/text-rui-text-secondary/,
60+
);
61+
expect(wrapper.find('.details > div').text()).toBe(hint);
62+
});
63+
64+
it('passes hint errorMessages', async () => {
65+
const wrapper = createWrapper();
66+
expect(wrapper.find('.details > div').exists()).toBeFalsy();
67+
68+
const errorMessage = 'Switch Error Message';
69+
await wrapper.setProps({ errorMessages: [errorMessage] });
70+
expect(wrapper.find('.details > div').classes()).toMatch(/text-rui-error/);
71+
expect(wrapper.find('.details > div').text()).toBe(errorMessage);
72+
});
73+
74+
it('passes hideDetails', () => {
75+
const wrapper = createWrapper({
76+
props: {
77+
hideDetails: true,
78+
hint: 'This hint should not be rendered',
79+
},
80+
});
81+
expect(wrapper.find('.details > div').exists()).toBeFalsy();
82+
});
83+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { contextColors } from '@/consts/colors';
2+
import { type Props, default as Switch } from './Switch.vue';
3+
import type { Meta, StoryFn, StoryObj } from '@storybook/vue3';
4+
5+
type PropsAndLabel = Props & { label: string };
6+
7+
const render: StoryFn<PropsAndLabel> = args => ({
8+
components: { Switch },
9+
setup() {
10+
const modelValue = computed({
11+
get() {
12+
return args.modelValue;
13+
},
14+
set(val) {
15+
args.modelValue = val;
16+
},
17+
});
18+
19+
return { args, modelValue };
20+
},
21+
template: `<Switch v-bind="args" v-model="modelValue">
22+
{{ args.label }}
23+
</Switch>`,
24+
});
25+
26+
const meta: Meta<PropsAndLabel> = {
27+
argTypes: {
28+
color: { control: 'select', options: contextColors },
29+
disabled: { control: 'boolean', table: { category: 'State' } },
30+
errorMessages: { control: 'array', defaultValue: [] },
31+
hideDetails: { control: 'boolean' },
32+
hint: { control: 'text' },
33+
label: { control: 'text' },
34+
modelValue: { control: 'boolean' },
35+
size: { control: 'select', options: ['medium', 'sm'] },
36+
successMessages: { control: 'array', defaultValue: [] },
37+
},
38+
component: Switch,
39+
parameters: {
40+
docs: {
41+
controls: { exclude: ['default'] },
42+
},
43+
},
44+
render,
45+
tags: ['autodocs'],
46+
title: 'Components/Forms/Switch',
47+
};
48+
49+
type Story = StoryObj<PropsAndLabel>;
50+
51+
export const Checked: Story = {
52+
args: {
53+
modelValue: true,
54+
},
55+
};
56+
57+
export const Small: Story = {
58+
args: {
59+
label: 'asdfa',
60+
size: 'sm',
61+
},
62+
};
63+
64+
export const Primary: Story = {
65+
args: {
66+
color: 'primary',
67+
},
68+
};
69+
70+
export const WithLabel: Story = {
71+
args: {
72+
label: 'With Label',
73+
},
74+
};
75+
76+
export const Disabled: Story = {
77+
args: {
78+
disabled: true,
79+
label: 'Disabled',
80+
},
81+
};
82+
83+
export const WithErrorMessage: Story = {
84+
args: {
85+
errorMessages: ['With error messages'],
86+
label: 'Label',
87+
},
88+
};
89+
90+
export const WithSuccessMessage: Story = {
91+
args: {
92+
label: 'Label',
93+
successMessages: ['With success messages'],
94+
},
95+
};
96+
97+
export const WithHint: Story = {
98+
args: {
99+
hint: 'With hint',
100+
label: 'Label',
101+
},
102+
};
103+
104+
export const HideDetails: Story = {
105+
args: {
106+
hideDetails: true,
107+
hint: 'Hint (should be invisible)',
108+
label: 'Label',
109+
},
110+
};
111+
112+
export default meta;

0 commit comments

Comments
 (0)