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 3, 2022
1 parent 10ea640 commit eaa0a68
Show file tree
Hide file tree
Showing 19 changed files with 2,062 additions and 7 deletions.
13 changes: 13 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 @@ -78,6 +78,7 @@
"eslint-config-prettier": "8.5.0",
"eslint-plugin-storybook": "0.5.12",
"eslint-plugin-vue": "9.1.1",
"flush-promises": "1.0.2",
"happy-dom": "5.2.0",
"html-webpack-plugin": "5.5.0",
"husky": "8.0.1",
Expand Down
74 changes: 74 additions & 0 deletions src/components/input-and-actions/VueCheckbox/VueCheckbox.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, beforeEach, test, expect } from 'vitest';
import { fireEvent, render, RenderResult } from '@testing-library/vue';
import { defineRule } from 'vee-validate';
import { required } from '@vee-validate/rules';
import VueCheckbox from './VueCheckbox.vue';
import { sleep } from '~/test/test-utils';

defineRule('required', required);

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

beforeEach(() => {
harness = render(VueCheckbox, {
props: {
name: 'foo',
id: 'foo',
label: 'Test',
description: 'Description',
},
});
});

test('renders component', () => {
const { getByText } = harness;

getByText('Test');
getByText('Description');
});

test('should emit click event', async () => {
const { getByText, emitted } = harness;

await fireEvent.click(getByText('Test'));
await sleep(1);

expect(emitted().click).toBeTruthy();
});

test('should set checked attribute', async () => {
const { rerender, getByText } = harness;
const input: any = getByText('Test').parentElement.querySelector('#foo');

expect(input.checked).toBeFalsy();

await rerender({ checked: true });

expect(input.checked).toBeTruthy();
});

test('should disable checkbox', async () => {
const { getByText, emitted, rerender } = harness;

await rerender({ disabled: true });

await fireEvent.click(getByText('Test'));

expect(emitted().click).toBeFalsy();
});

test('should show error', async () => {
const { getByTestId, html, rerender } = harness;

await rerender({ required: true });

const input = getByTestId<HTMLInputElement>('checkbox-input');

await fireEvent.click(input);
await fireEvent.click(input);
await sleep(1);

expect(html()).toMatch('error');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import VueCheckbox from './VueCheckbox.vue';
import VueInline from '@/components/layout/VueInline/VueInline.vue';
import VueText from '@/components/typography/VueText/VueText.vue';
import ComponentDocs from '@/assets/design-system/docs/components/ComponentDocs.vue';

export default {
title: 'Input & Actions/Checkbox',
component: VueCheckbox,
argTypes: {
modelValue: { table: { disable: true } },
'update:modelValue': { table: { disable: true } },
},
};

const Template = (args) => ({
components: {
VueCheckbox,
ComponentDocs,
VueInline,
VueText,
},
data(): any {
return {
model: true,
};
},
setup() {
return { args };
},
template: `<component-docs
component-name="Toggle"
usage="Allows users to choose between two mutually exclusive options. There is always a default value and settings should be saved and take into effect immediately."
story="Show default checkbox. Please interact with the checkbox to see different states."
>
<vue-inline stack-phone stack-tablet-portrait stack-tablet-landscape stack-small-desktop stack-large-desktop>
<vue-text look="small-title" weight="semi-bold">Model: {{ model }}</vue-text>
<vue-checkbox
:id="args.id"
v-model="model"
:name="args.name"
:label="args.label"
:description="args.description"
:required="args.required"
:disabled="args.disabled"
/>
</vue-inline>
</component-docs>`,
});

export const Default = Template.bind({});
Default.args = {
id: 'checkbox',
name: 'checkbox',
label: 'Check me',
description: 'Get notified when someone comments on your posting.',
required: true,
disabled: false,
};
166 changes: 166 additions & 0 deletions src/components/input-and-actions/VueCheckbox/VueCheckbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<template>
<div
:tabindex="disabled ? null : 0"
:class="[$style.vueCheckbox, disabled && $style.disabled, errors.length > 0 && $style.error]"
@click.stop.prevent="onClick"
@keypress.space.stop.prevent="onClick"
>
<div :class="$style.wrapper">
<input
:id="id"
:name="name"
:value="value"
:checked="value"
type="checkbox"
:required="required"
:disabled="disabled"
v-bind="$attrs"
tabindex="-1"
data-testid="checkbox-input"
/>
<div :class="$style.checkmark">
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 8">
<path
d="M9.207.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L3.5 5.086 7.793.793a1 1 0 011.414 0z"
fill="currentColor"
/>
</svg>
</div>
<vue-text :for="id" as="label" weight="semi-bold" color="text-medium" tabindex="-1">
<slot name="label">
{{ label }}
</slot>
</vue-text>
</div>
<vue-text v-if="description" :class="$style.description" as="div">
<slot name="description">
{{ description }}
</slot>
</vue-text>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useField } from 'vee-validate';
import VueText from '@/components/typography/VueText/VueText.vue';
const props = defineProps({
id: { type: String, required: true },
name: { type: String, required: true },
label: { type: String, required: true },
description: { type: String, default: null },
required: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
modelValue: { type: Boolean, default: false },
});
const emit = defineEmits(['click', 'update:modelValue']);
const rules = computed(() => (props.required ? 'required' : null));
const { errors, value, validate } = useField<boolean>(props.id, rules, {
initialValue: props.modelValue,
type: 'checkbox',
});
const onClick = async () => {
if (!props.disabled) {
value.value = !value.value;
await validate({ mode: 'force' });
emit('update:modelValue', value);
emit('click', value);
}
};
</script>

<style lang="scss" module>
@import 'assets/_design-system';
.vueCheckbox {
display: inline-block;
position: relative;
cursor: pointer;
user-select: none;
outline: none;
.wrapper {
display: inline-flex;
align-items: center;
}
.description {
padding-left: $checkbox-checkmark-size + $checkbox-label-gap;
line-height: $space-20;
}
input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
&:checked ~ .checkmark {
background-color: $checkbox-checkmark-bg-checked !important;
border: $checkbox-checkmark-border-checked !important;
color: $checkbox-checkmark-color !important;
}
&:checked ~ .checkmark > svg {
display: block;
}
}
.checkmark {
height: $checkbox-checkmark-size;
width: $checkbox-checkmark-size;
background-color: $checkbox-checkmark-bg;
color: $checkbox-checkmark-bg;
border-radius: $checkbox-checkmark-border-radius;
border: $checkbox-checkmark-border;
display: flex;
justify-content: center;
align-items: center;
> svg {
width: $checkbox-checkmark-size - ($space-4 + $space-2);
height: $checkbox-checkmark-size - ($space-4 + $space-2);
}
}
label {
cursor: pointer;
padding-left: $checkbox-label-gap;
}
&:hover {
input ~ .checkmark {
background-color: $checkbox-checkmark-bg-hover;
border: $checkbox-checkmark-border-hover;
}
}
&:focus {
.checkmark {
box-shadow: $checkbox-checkmark-outline;
}
}
&.disabled {
opacity: $checkbox-disabled-disabled-opacity;
}
&.error {
.description,
label {
color: $checkbox-error-color;
}
.checkmark {
border-color: $checkbox-error-color;
}
&:hover {
input ~ .checkmark {
border-color: $checkbox-error-color;
}
}
}
}
</style>
Loading

0 comments on commit eaa0a68

Please sign in to comment.