feat: abstract BaseCheckbox
This commit is contained in:
parent
7d3b97d422
commit
8fc254d2db
@ -120,7 +120,7 @@ describe('Namepaces', () => {
|
||||
.should('not.contain', 'Archived')
|
||||
|
||||
// Show archived
|
||||
cy.get('[data-cy="show-archived-check"] label.check span')
|
||||
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('[data-cy="show-archived-check"] input')
|
||||
@ -129,7 +129,7 @@ describe('Namepaces', () => {
|
||||
.should('contain', 'Archived')
|
||||
|
||||
// Don't show archived
|
||||
cy.get('[data-cy="show-archived-check"] label.check span')
|
||||
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('[data-cy="show-archived-check"] input')
|
||||
|
@ -22,10 +22,10 @@ describe('Project View Table', () => {
|
||||
cy.get('.project-table .filter-container .items .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
.contains('Priority')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
.contains('Done')
|
||||
.click()
|
||||
|
||||
|
@ -96,7 +96,7 @@ describe('Task', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.get('.tasks .task .fancycheckbox label.check')
|
||||
cy.get('.tasks .task .fancycheckbox')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.global-notification')
|
||||
|
4
src/assets/checkbox.svg
Normal file
4
src/assets/checkbox.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
|
||||
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z" stroke-dasharray="60"></path>
|
||||
<polyline points="1 9 7 14 15 4" stroke-dasharray="22" stroke-dashoffset="66"></polyline>
|
||||
</svg>
|
After Width: | Height: | Size: 420 B |
53
src/components/base/BaseCheckbox.vue
Normal file
53
src/components/base/BaseCheckbox.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="base-checkbox" v-cy="'checkbox'">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="checkboxId"
|
||||
class="is-sr-only"
|
||||
:checked="modelValue"
|
||||
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
|
||||
:disabled="disabled || undefined"
|
||||
/>
|
||||
|
||||
<slot name="label" :checkboxId="checkboxId">
|
||||
<label :for="checkboxId" class="base-checkbox__label">
|
||||
<slot/>
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const checkboxId = ref(`fancycheckbox_${createRandomID()}`)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.base-checkbox__label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.base-checkbox:has(input:disabled) .base-checkbox__label {
|
||||
cursor:not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
71
src/components/input/fancycheckbox.story.vue
Normal file
71
src/components/input/fancycheckbox.story.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {logEvent} from 'histoire/client'
|
||||
import FancyCheckbox from './fancycheckbox.vue'
|
||||
|
||||
const isDisabled = ref<boolean | undefined>()
|
||||
|
||||
const isChecked = ref(false)
|
||||
|
||||
const isCheckedInitiallyEnabled = ref(true)
|
||||
|
||||
const isCheckedDisabled = ref(false)
|
||||
|
||||
const withoutInitialState = ref<boolean | undefined>()
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<Story :layout="{ type: 'grid', width: '200px' }">
|
||||
<Variant title="Default">
|
||||
<FancyCheckbox
|
||||
v-model="isChecked"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
This is probably not important
|
||||
</FancyCheckbox>
|
||||
|
||||
Visualisation
|
||||
<input type="checkbox" v-model="isChecked">
|
||||
{{ isChecked }}
|
||||
</Variant>
|
||||
<Variant title="Enabled Initially">
|
||||
<FancyCheckbox
|
||||
:disabled="isDisabled"
|
||||
v-model="isCheckedInitiallyEnabled"
|
||||
>
|
||||
We want you to use this option
|
||||
</FancyCheckbox>
|
||||
|
||||
Visualisation
|
||||
<input type="checkbox" v-model="isCheckedInitiallyEnabled">
|
||||
{{ isCheckedInitiallyEnabled }}
|
||||
</Variant>
|
||||
<Variant title="Disabled">
|
||||
<FancyCheckbox
|
||||
disabled
|
||||
:modelValue="isCheckedDisabled"
|
||||
@update:model-value="logEvent('Setting disabled: This should never happen', $event)"
|
||||
>
|
||||
You can't change this
|
||||
</FancyCheckbox>
|
||||
|
||||
Visualisation
|
||||
<input type="checkbox" v-model="isCheckedDisabled" disabled>
|
||||
{{ isCheckedDisabled }}
|
||||
</Variant>
|
||||
|
||||
<Variant title="Undefined initial State">
|
||||
<FancyCheckbox
|
||||
v-model="withoutInitialState"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
Not sure what the value should be
|
||||
</FancyCheckbox>
|
||||
|
||||
Visualisation
|
||||
<input type="checkbox" v-model="withoutInitialState" disabled>
|
||||
{{ withoutInitialState }}
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
@ -1,66 +1,42 @@
|
||||
<template>
|
||||
<div :class="{'is-disabled': disabled}" class="fancycheckbox">
|
||||
<input
|
||||
:checked="checked"
|
||||
:disabled="disabled || undefined"
|
||||
:id="checkBoxId"
|
||||
@change="(event: Event) => updateData((event.target as HTMLInputElement).checked)"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label :for="checkBoxId" class="check" @click.prevent="check">
|
||||
<svg height="18px" viewBox="0 0 18 18" width="18px">
|
||||
<path
|
||||
d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
||||
<polyline points="1 9 7 14 15 4"></polyline>
|
||||
</svg>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<BaseCheckbox
|
||||
class="fancycheckbox"
|
||||
:class="{
|
||||
'is-disabled': disabled,
|
||||
'is-block': isBlock,
|
||||
}"
|
||||
:disabled="disabled"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => emit('update:modelValue', value)"
|
||||
>
|
||||
<CheckboxIcon class="fancycheckbox__icon" />
|
||||
<span class="fancycheckbox__content">
|
||||
<slot/>
|
||||
</span>
|
||||
</BaseCheckbox>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, toRef, watch} from 'vue'
|
||||
import CheckboxIcon from '@/assets/checkbox.svg?component'
|
||||
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import BaseCheckbox from '@/components/base/BaseCheckbox.vue'
|
||||
|
||||
const checked = ref(false)
|
||||
const checkBoxId = `fancycheckbox_${createRandomID()}`
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
isBlock: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const modelValue = toRef(props, 'modelValue')
|
||||
|
||||
watch(
|
||||
modelValue,
|
||||
newValue => {
|
||||
checked.value = newValue
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function updateData(newChecked: boolean) {
|
||||
checked.value = newChecked
|
||||
emit('update:modelValue', newChecked)
|
||||
emit('change', newChecked)
|
||||
}
|
||||
|
||||
function check() {
|
||||
checked.value = !checked.value
|
||||
updateData(checked.value)
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
||||
@ -70,75 +46,51 @@ function check() {
|
||||
padding-right: 5px;
|
||||
padding-top: 3px;
|
||||
|
||||
// FIXME: should be a prop
|
||||
&.is-block {
|
||||
display: block;
|
||||
margin: .5rem .2rem;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.check {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
span {
|
||||
.fancycheckbox__content {
|
||||
font-size: 0.8rem;
|
||||
vertical-align: top;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
.fancycheckbox__icon {
|
||||
--stroke-color: #c8ccd4;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke: #c8ccd4;
|
||||
stroke-width: 1.5;
|
||||
stroke: var(--stroke-color);
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.check:hover svg {
|
||||
stroke: var(--primary);
|
||||
}
|
||||
|
||||
.is-disabled .check:hover svg {
|
||||
stroke: #c8ccd4;
|
||||
}
|
||||
|
||||
path {
|
||||
stroke-dasharray: 60;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
polyline {
|
||||
stroke-dasharray: 22;
|
||||
stroke-dashoffset: 66;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked + .check {
|
||||
svg {
|
||||
stroke: var(--primary);
|
||||
:deep(path) {
|
||||
// stroke-dasharray: 60;
|
||||
transition: all 0.2s linear, color 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(polyline) {
|
||||
// stroke-dasharray: 22;
|
||||
// stroke-dashoffset: 66;
|
||||
transition: all 0.2s linear, color 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.fancycheckbox:not(:has(input:disabled)):hover .fancycheckbox__icon {
|
||||
--stroke-color: var(--primary);
|
||||
}
|
||||
|
||||
.fancycheckbox:has(input:checked) .fancycheckbox__icon:deep() {
|
||||
--stroke-color: var(--primary);
|
||||
|
||||
path {
|
||||
stroke-dashoffset: 60;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
polyline {
|
||||
stroke-dashoffset: 42;
|
||||
transition: all 0.2s linear;
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
>
|
||||
<fancycheckbox
|
||||
:disabled="(isArchived || disabled) && !canMarkAsDone"
|
||||
@change="markAsDone"
|
||||
@update:model-value="markAsDone"
|
||||
v-model="task.done"
|
||||
/>
|
||||
|
||||
|
@ -6,13 +6,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const cypressDirective: Directive = {
|
||||
mounted(el, {value}) {
|
||||
if (
|
||||
(window.Cypress || import.meta.env.DEV) &&
|
||||
value
|
||||
) {
|
||||
el.setAttribute('data-cy', value)
|
||||
const cypressDirective = <Directive<HTMLElement,string>>{
|
||||
mounted(el, {arg, value}) {
|
||||
const testingId = arg || value
|
||||
if ((window.Cypress || import.meta.env.DEV) && testingId) {
|
||||
el.setAttribute('data-cy', testingId)
|
||||
}
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineSetupVue3 } from '@histoire/plugin-vue'
|
||||
import {defineSetupVue3} from '@histoire/plugin-vue'
|
||||
import {i18n} from './i18n'
|
||||
|
||||
// import './histoire.css' // Import global CSS
|
||||
@ -6,18 +6,21 @@ import './styles/global.scss'
|
||||
|
||||
import {createPinia} from 'pinia'
|
||||
|
||||
import cypress from '@/directives/cypress'
|
||||
|
||||
import FontAwesomeIcon from '@/components/misc/Icon'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
import Modal from '@/components/misc/modal.vue'
|
||||
import Card from '@/components/misc/card.vue'
|
||||
|
||||
|
||||
export const setupVue3 = defineSetupVue3(({ app }) => {
|
||||
// Add Pinia store
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
|
||||
app.directive('cy', cypress)
|
||||
|
||||
app.component('icon', FontAwesomeIcon)
|
||||
app.component('XButton', XButton)
|
||||
app.component('modal', Modal)
|
||||
|
@ -22,7 +22,7 @@
|
||||
<x-button @click="setDefaultFilters">Reset</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<fancycheckbox class="is-block" v-model="filters.showTasksWithoutDates">
|
||||
<fancycheckbox is-block v-model="filters.showTasksWithoutDates">
|
||||
{{ $t('project.gantt.showTasksWithoutDates') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
|
@ -11,10 +11,10 @@
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
<fancycheckbox @change="setShowNulls" class="mr-2">
|
||||
<fancycheckbox @update:model-value="setShowNulls" class="mr-2">
|
||||
{{ $t('task.show.noDates') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="setShowOverdue">
|
||||
<fancycheckbox @update:model-value="setShowOverdue">
|
||||
{{ $t('task.show.overdue') }}
|
||||
</fancycheckbox>
|
||||
</p>
|
||||
|
Reference in New Issue
Block a user