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 <[email protected]>

Related to #30
  • Loading branch information
hugoattal authored Oct 4, 2022
1 parent 84bb3da commit ad39f96
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 58 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit ad39f96

Please sign in to comment.