Skip to content

Commit

Permalink
feat: add checkbox list (#310)
Browse files Browse the repository at this point in the history
Co-authored-by: Guillaume Chau <guillaume.b.chau@gmail.com>

Related to #30
  • Loading branch information
hugoattal committed Oct 4, 2022
1 parent 84bb3da commit ad39f96
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 58 deletions.
Expand Up @@ -9,7 +9,10 @@ function initState () {
</script>

<template>
<Story title="HstCheckbox">
<Story
title="HstCheckbox"
:layout="{ type: 'single', iframe: false }"
>
<Variant
title="playground"
:init-state="initState"
Expand Down
54 changes: 4 additions & 50 deletions packages/histoire-controls/src/components/checkbox/HstCheckbox.vue
Expand Up @@ -5,36 +5,21 @@ export default {
</script>

<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import HstWrapper from '../HstWrapper.vue'
import HstSimpleCheckbox from './HstSimpleCheckbox.vue'
const props = defineProps<{
modelValue: boolean
title?: string
}>()
const emit = defineEmits({
const emits = defineEmits({
'update:modelValue': (newValue: boolean) => true,
})
function toggle () {
emit('update:modelValue', !props.modelValue)
animationEnabled.value = true
emits('update:modelValue', !props.modelValue)
}
// SVG check
const path = ref<SVGPathElement>()
const dasharray = ref(0)
const progress = computed(() => props.modelValue ? 1 : 0)
const dashoffset = computed(() => (1 - progress.value) * dasharray.value)
// animationEnabled prevents the animation from triggering on mounted
const animationEnabled = ref(false)
watch(path, () => {
dasharray.value = path.value.getTotalLength?.() ?? 21.21
})
</script>

<template>
Expand All @@ -47,38 +32,7 @@ watch(path, () => {
@keydown.enter.prevent="toggle()"
@keydown.space.prevent="toggle()"
>
<div class="htw-text-white htw-w-[16px] htw-h-[16px] htw-relative">
<div
class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out"
:class="[
modelValue
? 'htw-border-primary-500 htw-border-8'
: 'htw-border-black/25 dark:htw-border-white/25 htw-delay-150',
]"
/>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
class="htw-relative htw-z-10"
>
<path
ref="path"
d="m 4 12 l 5 5 l 10 -10"
fill="none"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out"
:class="[
animationEnabled ? 'htw-transition-all' : 'htw-transition-none',
{
'htw-delay-150': modelValue,
},
]"
:stroke-dasharray="dasharray"
:stroke-dashoffset="dashoffset"
/>
</svg>
</div>

<HstSimpleCheckbox :model-value="modelValue" />
<template #actions>
<slot name="actions" />
</template>
Expand Down
@@ -0,0 +1,48 @@
<script lang="ts" setup>
import HstCheckboxList from './HstCheckboxList.vue'
const options = {
'crash-bandicoot': 'Crash Bandicoot',
'the-last-of-us': 'The Last of Us',
'ghost-of-tsushima': 'Ghost of Tsushima',
}
const objectOptions = Object.keys(options).map(key => ({
label: options[key],
value: key,
}))
function initState () {
return {
characters: [],
}
}
</script>

<template>
<Story
title="HstCheckboxList"
:layout="{ type: 'single', iframe: false }"
>
<Variant
title="playground"
:init-state="initState"
>
<template #default="{ state }">
<HstCheckboxList
v-model="state.characters"
title="Label"
:options="objectOptions"
/>
</template>

<template #controls="{ state }">
<HstCheckboxList
v-model="state.characters"
title="Label"
:options="objectOptions"
/>
</template>
</Variant>
</Story>
</template>
@@ -0,0 +1,79 @@
<script lang="ts">
export default {
name: 'HstCheckboxList',
}
</script>

<script lang="ts" setup>
import { computed, ComputedRef } from 'vue'
import HstWrapper from '../HstWrapper.vue'
import { HstControlOption } from '../../types'
import HstSimpleCheckbox from './HstSimpleCheckbox.vue'
const props = defineProps<{
title?: string
modelValue: Array<string>
options: HstControlOption[]
}>()
const formattedOptions: ComputedRef<Record<string, string>> = computed(() => {
if (Array.isArray(props.options)) {
return Object.fromEntries(props.options.map((value: string | HstControlOption) => {
if (typeof value === 'string') {
return [value, value]
} else {
return [value.value, value.label]
}
}))
}
return props.options
})
const emits = defineEmits<{
(e: 'update:modelValue', value: Array<string>): void
}>()
function toggleOption (value: string) {
if (props.modelValue.includes(value)) {
emits('update:modelValue', props.modelValue.filter(element => element !== value))
} else {
emits('update:modelValue', [...props.modelValue, value])
}
}
</script>

<template>
<HstWrapper
role="group"
:title="title"
class="htw-cursor-text"
:class="$attrs.class"
:style="$attrs.style"
>
<div class="-htw-my-1">
<template
v-for="( label, value ) in formattedOptions"
:key="value"
>
<label
tabindex="0"
:for="`${value}-radio`"
class="htw-cursor-pointer htw-flex htw-items-center htw-relative htw-py-1 htw-group"
@keydown.enter.prevent="toggleOption(value)"
@keydown.space.prevent="toggleOption(value)"
@click="toggleOption(value)"
>
<HstSimpleCheckbox
:model-value="modelValue.includes(value)"
class="htw-mr-2"
/>
{{ label }}
</label>
</template>
</div>

<template #actions>
<slot name="actions" />
</template>
</HstWrapper>
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import HstSimpleCheckbox from './HstSimpleCheckbox.vue'
function initState () {
return {
checked: true,
}
}
</script>

<template>
<Story
title="internals/HstSimpleCheckbox"
:layout="{ type: 'single', iframe: false }"
>
<Variant
title="playground"
:init-state="initState"
>
<template #default="{ state }">
<HstSimpleCheckbox
v-model="state.checked"
with-toggle
/>
</template>
</Variant>
</Story>
</template>
@@ -0,0 +1,82 @@
<script lang="ts">
export default {
name: 'HstSimpleCheckbox',
}
</script>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
const props = defineProps<{
modelValue: boolean
withToggle?: boolean
}>()
const emits = defineEmits({
'update:modelValue': (newValue: boolean) => true,
})
function toggle () {
if (!props.withToggle) {
return
}
emits('update:modelValue', !props.modelValue)
}
watch(() => props.modelValue, () => {
animationEnabled.value = true
})
// SVG check
const path = ref<SVGPathElement>()
const dasharray = ref(0)
const progress = computed(() => props.modelValue ? 1 : 0)
const dashoffset = computed(() => (1 - progress.value) * dasharray.value)
// animationEnabled prevents the animation from triggering on mounted
const animationEnabled = ref(false)
watch(path, () => {
dasharray.value = path.value.getTotalLength?.() ?? 21.21
})
</script>

<template>
<div
class="htw-group htw-text-white htw-w-[16px] htw-h-[16px] htw-relative"
:class="{'htw-cursor-pointer': withToggle}"
@click="toggle"
>
<div
class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out group-hover:htw-border-primary-500 group-hover:dark:htw-border-primary-500"
:class="[
modelValue
? 'htw-border-primary-500 htw-border-8'
: 'htw-border-black/25 dark:htw-border-white/25 htw-delay-150',
]"
/>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
class="htw-relative htw-z-10"
>
<path
ref="path"
d="m 4 12 l 5 5 l 10 -10"
fill="none"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out"
:class="[
animationEnabled ? 'htw-transition-all' : 'htw-transition-none',
{
'htw-delay-150': modelValue,
},
]"
:stroke-dasharray="dasharray"
:stroke-dashoffset="dashoffset"
/>
</svg>
</div>
</template>
Expand Up @@ -11,8 +11,8 @@ exports[`HstCheckbox toggle to checked 1`] = `
</span>
<span class="htw-grow htw-flex htw-items-center htw-gap-1">
<span class="htw-block htw-grow">
<div class="htw-text-white htw-w-[16px] htw-h-[16px] htw-relative">
<div class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out htw-border-black/25 dark:htw-border-white/25 htw-delay-150">
<div class="htw-group htw-text-white htw-w-[16px] htw-h-[16px] htw-relative">
<div class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out group-hover:htw-border-primary-500 group-hover:dark:htw-border-primary-500 htw-border-black/25 dark:htw-border-white/25 htw-delay-150">
</div>
<svg
width="16"
Expand All @@ -23,7 +23,7 @@ exports[`HstCheckbox toggle to checked 1`] = `
<path
d="m 4 12 l 5 5 l 10 -10"
fill="none"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out htw-transition-all"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out htw-transition-none"
stroke-dasharray="21.21"
stroke-dashoffset="21.21"
>
Expand All @@ -46,8 +46,8 @@ exports[`HstCheckbox toggle to unchecked 1`] = `
</span>
<span class="htw-grow htw-flex htw-items-center htw-gap-1">
<span class="htw-block htw-grow">
<div class="htw-text-white htw-w-[16px] htw-h-[16px] htw-relative">
<div class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out htw-border-primary-500 htw-border-8">
<div class="htw-group htw-text-white htw-w-[16px] htw-h-[16px] htw-relative">
<div class="htw-border htw-border-solid group-active:htw-bg-gray-500/20 htw-rounded-sm htw-box-border htw-absolute htw-inset-0 htw-transition-border htw-duration-150 htw-ease-out group-hover:htw-border-primary-500 group-hover:dark:htw-border-primary-500 htw-border-primary-500 htw-border-8">
</div>
<svg
width="16"
Expand All @@ -58,7 +58,7 @@ exports[`HstCheckbox toggle to unchecked 1`] = `
<path
d="m 4 12 l 5 5 l 10 -10"
fill="none"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out htw-transition-all htw-delay-150"
class="htw-stroke-white htw-stroke-2 htw-duration-200 htw-ease-in-out htw-transition-none htw-delay-150"
stroke-dasharray="21.21"
stroke-dashoffset="0"
>
Expand Down
Expand Up @@ -22,7 +22,10 @@ function initState () {
</script>

<template>
<Story title="HstRadio">
<Story
title="HstRadio"
:layout="{ type: 'single', iframe: false }"
>
<Variant
title="playground"
:init-state="initState"
Expand Down

0 comments on commit ad39f96

Please sign in to comment.