-
Notifications
You must be signed in to change notification settings - Fork 393
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
10ea640
commit eaa0a68
Showing
19 changed files
with
2,062 additions
and
7 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/components/input-and-actions/VueCheckbox/VueCheckbox.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); |
58 changes: 58 additions & 0 deletions
58
src/components/input-and-actions/VueCheckbox/VueCheckbox.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
166
src/components/input-and-actions/VueCheckbox/VueCheckbox.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.