@poveste/controls 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +5 -0
  3. package/dist/components/HstCopyIcon.story.vue.d.ts +2 -0
  4. package/dist/components/HstCopyIcon.vue.d.ts +7 -0
  5. package/dist/components/HstWrapper.vue.d.ts +20 -0
  6. package/dist/components/button/HstButton.story.vue.d.ts +2 -0
  7. package/dist/components/button/HstButton.vue.d.ts +13 -0
  8. package/dist/components/button/HstButtonGroup.story.vue.d.ts +2 -0
  9. package/dist/components/button/HstButtonGroup.vue.d.ts +22 -0
  10. package/dist/components/checkbox/HstCheckbox.story.vue.d.ts +2 -0
  11. package/dist/components/checkbox/HstCheckbox.vue.d.ts +19 -0
  12. package/dist/components/checkbox/HstCheckboxList.story.vue.d.ts +2 -0
  13. package/dist/components/checkbox/HstCheckboxList.vue.d.ts +22 -0
  14. package/dist/components/checkbox/HstSimpleCheckbox.story.vue.d.ts +2 -0
  15. package/dist/components/checkbox/HstSimpleCheckbox.vue.d.ts +12 -0
  16. package/dist/components/colorselect/HstColorSelect.story.vue.d.ts +2 -0
  17. package/dist/components/colorselect/HstColorSelect.vue.d.ts +19 -0
  18. package/dist/components/design-tokens/HstColorShades.story.vue.d.ts +2 -0
  19. package/dist/components/design-tokens/HstColorShades.vue.d.ts +19 -0
  20. package/dist/components/design-tokens/HstTokenGrid.story.vue.d.ts +2 -0
  21. package/dist/components/design-tokens/HstTokenGrid.vue.d.ts +26 -0
  22. package/dist/components/design-tokens/HstTokenList.story.vue.d.ts +2 -0
  23. package/dist/components/design-tokens/HstTokenList.vue.d.ts +21 -0
  24. package/dist/components/json/HstJson.story.vue.d.ts +2 -0
  25. package/dist/components/json/HstJson.vue.d.ts +19 -0
  26. package/dist/components/number/HstNumber.story.vue.d.ts +2 -0
  27. package/dist/components/number/HstNumber.vue.d.ts +19 -0
  28. package/dist/components/radio/HstRadio.story.vue.d.ts +2 -0
  29. package/dist/components/radio/HstRadio.vue.d.ts +22 -0
  30. package/dist/components/select/CustomSelect.vue.d.ts +22 -0
  31. package/dist/components/select/HstSelect.story.vue.d.ts +2 -0
  32. package/dist/components/select/HstSelect.vue.d.ts +22 -0
  33. package/dist/components/slider/HstSlider.story.vue.d.ts +2 -0
  34. package/dist/components/slider/HstSlider.vue.d.ts +16 -0
  35. package/dist/components/text/HstText.story.vue.d.ts +2 -0
  36. package/dist/components/text/HstText.vue.d.ts +19 -0
  37. package/dist/components/textarea/HstTextarea.story.vue.d.ts +2 -0
  38. package/dist/components/textarea/HstTextarea.vue.d.ts +19 -0
  39. package/dist/index.d.ts +1047 -0
  40. package/dist/index.es.css +1 -0
  41. package/dist/index.es.js +16392 -0
  42. package/dist/style-standalone.css +973 -0
  43. package/dist/types.d.ts +4 -0
  44. package/dist/utils.d.ts +1 -0
  45. package/package.json +72 -0
  46. package/postcss.config.cjs +8 -0
  47. package/src/components/HstCopyIcon.story.vue +26 -0
  48. package/src/components/HstCopyIcon.vue +38 -0
  49. package/src/components/HstWrapper.vue +41 -0
  50. package/src/components/button/HstButton.story.vue +30 -0
  51. package/src/components/button/HstButton.vue +26 -0
  52. package/src/components/button/HstButtonGroup.story.vue +79 -0
  53. package/src/components/button/HstButtonGroup.vue +71 -0
  54. package/src/components/checkbox/HstCheckbox.spec.ts +28 -0
  55. package/src/components/checkbox/HstCheckbox.story.vue +47 -0
  56. package/src/components/checkbox/HstCheckbox.vue +56 -0
  57. package/src/components/checkbox/HstCheckboxList.story.vue +49 -0
  58. package/src/components/checkbox/HstCheckboxList.vue +82 -0
  59. package/src/components/checkbox/HstSimpleCheckbox.story.vue +28 -0
  60. package/src/components/checkbox/HstSimpleCheckbox.vue +82 -0
  61. package/src/components/checkbox/__snapshots__/HstCheckbox.spec.ts.snap +5 -0
  62. package/src/components/colorselect/HstColorSelect.story.vue +28 -0
  63. package/src/components/colorselect/HstColorSelect.vue +89 -0
  64. package/src/components/design-tokens/HstColorShades.story.vue +392 -0
  65. package/src/components/design-tokens/HstColorShades.vue +101 -0
  66. package/src/components/design-tokens/HstTokenGrid.story.vue +36 -0
  67. package/src/components/design-tokens/HstTokenGrid.vue +88 -0
  68. package/src/components/design-tokens/HstTokenList.story.vue +81 -0
  69. package/src/components/design-tokens/HstTokenList.vue +62 -0
  70. package/src/components/json/HstJson.story.vue +41 -0
  71. package/src/components/json/HstJson.vue +151 -0
  72. package/src/components/number/HstNumber.story.vue +42 -0
  73. package/src/components/number/HstNumber.vue +95 -0
  74. package/src/components/radio/HstRadio.story.vue +51 -0
  75. package/src/components/radio/HstRadio.vue +109 -0
  76. package/src/components/select/CustomSelect.vue +119 -0
  77. package/src/components/select/HstSelect.story.vue +161 -0
  78. package/src/components/select/HstSelect.vue +40 -0
  79. package/src/components/slider/HstSlider.story.vue +37 -0
  80. package/src/components/slider/HstSlider.vue +103 -0
  81. package/src/components/text/HstText.story.vue +63 -0
  82. package/src/components/text/HstText.vue +44 -0
  83. package/src/components/textarea/HstTextarea.story.vue +35 -0
  84. package/src/components/textarea/HstTextarea.vue +44 -0
  85. package/src/end.d.ts +8 -0
  86. package/src/index.ts +54 -0
  87. package/src/style/main.css +17 -0
  88. package/src/types.ts +4 -0
  89. package/src/utils.ts +12 -0
  90. package/tailwind.config.cjs +13 -0
  91. package/tsconfig.json +53 -0
  92. package/vite.config.ts +46 -0
@@ -0,0 +1,4 @@
1
+ export interface HstControlOption {
2
+ label: string;
3
+ value: any;
4
+ }
@@ -0,0 +1 @@
1
+ export declare const isDark: import("vue").Ref<boolean, boolean>;
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@poveste/controls",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Prebuilt controls components",
6
+ "author": {
7
+ "name": "Sorin Gitlan"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "url": "https://github.com/poveste-dev/poveste.git",
12
+ "type": "git",
13
+ "directory": "packages/poveste-controls"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "sideEffects": [
19
+ "*.css",
20
+ "*.postcss",
21
+ "*.vue"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.es.js"
27
+ },
28
+ "./*": "./*"
29
+ },
30
+ "main": "./dist/index.es.js",
31
+ "module": "./dist/index.es.js",
32
+ "types": "./dist/index.d.ts",
33
+ "dependencies": {
34
+ "@codemirror/commands": "^6.7.1",
35
+ "@codemirror/lang-json": "^6.0.1",
36
+ "@codemirror/language": "^6.10.6",
37
+ "@codemirror/lint": "^6.8.4",
38
+ "@codemirror/state": "^6.4.1",
39
+ "@codemirror/theme-one-dark": "^6.1.2",
40
+ "@codemirror/view": "^6.35.0",
41
+ "@poveste/shared": "^0.1.0",
42
+ "@poveste/vendors": "^0.1.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.10.1",
46
+ "@vitejs/plugin-vue": "^5.2.1",
47
+ "@vue/runtime-dom": "^3.5.26",
48
+ "@vue/test-utils": "^2.4.6",
49
+ "@vueuse/core": "^12.0.0",
50
+ "autoprefixer": "^10.4.20",
51
+ "concurrently": "^9.1.0",
52
+ "floating-vue": "5.2.2",
53
+ "jsdom": "^27.4.0",
54
+ "postcss": "^8.4.49",
55
+ "postcss-import": "^16.1.0",
56
+ "tailwindcss": "^3.4.16",
57
+ "typescript": "5.6.3",
58
+ "vite": "^7.3.0",
59
+ "vitest": "4.0.16",
60
+ "vue": "^3.5.26",
61
+ "vue-tsc": "^2.1.10"
62
+ },
63
+ "scripts": {
64
+ "build": "rimraf dist && pnpm run build:css && vite build && pnpm run build:types",
65
+ "build:css": "tailwindcss -i src/style/main.css -o ./dist/style-standalone.css -c tailwind.config.cjs --postcss postcss.config.cjs",
66
+ "build:types": "vue-tsc --declaration --emitDeclarationOnly",
67
+ "watch": "concurrently \"vite build --watch\" \"pnpm run build:types --watch\"",
68
+ "watch:full": "concurrently \"vite build --watch\" \"pnpm run build:css --watch\" \"pnpm run build:types --watch\"",
69
+ "test": "vitest run",
70
+ "test:dev": "vitest"
71
+ }
72
+ }
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ plugins: [
3
+ require('postcss-import'),
4
+ require('tailwindcss/nesting'),
5
+ require('tailwindcss')('./tailwind.config.cjs'),
6
+ require('autoprefixer'),
7
+ ],
8
+ }
@@ -0,0 +1,26 @@
1
+ <script lang="ts" setup>
2
+ import HstCopyIcon from './HstCopyIcon.vue'
3
+
4
+ function initState() {
5
+ return {
6
+ content: 'Text fo copy',
7
+ }
8
+ }
9
+ </script>
10
+
11
+ <template>
12
+ <Story
13
+ title="internals/HstCopyIcon"
14
+ >
15
+ <Variant
16
+ title="default"
17
+ :init-state="initState"
18
+ >
19
+ <template #default="{ state }">
20
+ <HstCopyIcon
21
+ :content="state.content"
22
+ />
23
+ </template>
24
+ </Variant>
25
+ </Story>
26
+ </template>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstCopyIcon',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import type { Awaitable } from '@poveste/shared'
9
+ import { Icon } from '@iconify/vue'
10
+ import { useClipboard } from '@vueuse/core'
11
+ import { VTooltip as vTooltip } from 'floating-vue'
12
+
13
+ const props = defineProps<{
14
+ content: string | (() => Awaitable<string>)
15
+ }>()
16
+
17
+ const { copy, copied } = useClipboard()
18
+
19
+ async function action() {
20
+ const content = typeof props.content === 'function' ? await props.content() : props.content
21
+ copy(content)
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <Icon
27
+ v-tooltip="{
28
+ content: 'Copied!',
29
+ triggers: [],
30
+ shown: copied,
31
+ distance: 12,
32
+ delay: 0,
33
+ }"
34
+ icon="carbon:copy-file"
35
+ class="htw-w-4 htw-h-4 htw-opacity-50 hover:htw-opacity-100 hover:htw-text-primary-500 htw-cursor-pointer"
36
+ @click="action()"
37
+ />
38
+ </template>
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstWrapper',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { VTooltip as vTooltip } from 'floating-vue'
9
+
10
+ withDefaults(defineProps<{
11
+ title?: string
12
+ tag?: string
13
+ }>(), {
14
+ title: undefined,
15
+ tag: 'label',
16
+ })
17
+ </script>
18
+
19
+ <template>
20
+ <component
21
+ :is="tag"
22
+ class="histoire-wrapper htw-p-2 hover:htw-bg-primary-100 dark:hover:htw-bg-primary-800 htw-flex htw-gap-2 htw-flex-wrap"
23
+ >
24
+ <span
25
+ v-tooltip="{
26
+ content: title,
27
+ placement: 'left',
28
+ distance: 12,
29
+ }"
30
+ class="htw-w-28 htw-whitespace-nowrap htw-text-ellipsis htw-overflow-hidden htw-shrink-0"
31
+ >
32
+ {{ title }}
33
+ </span>
34
+ <span class="htw-grow htw-max-w-full htw-flex htw-items-center htw-gap-1">
35
+ <span class="htw-block htw-grow htw-max-w-full">
36
+ <slot />
37
+ </span>
38
+ <slot name="actions" />
39
+ </span>
40
+ </component>
41
+ </template>
@@ -0,0 +1,30 @@
1
+ <script lang="ts" setup>
2
+ import HstButton from './HstButton.vue'
3
+
4
+ const variants: Array<{ name: string, bind?: unknown }> = [
5
+ { name: 'Default' },
6
+ { name: 'Primary', bind: { color: 'primary' } },
7
+ { name: 'Flat', bind: { color: 'flat' } },
8
+ ]
9
+ </script>
10
+
11
+ <template>
12
+ <Story
13
+ title="HstButton"
14
+ group="controls"
15
+ :layout="{ type: 'grid', width: '200px', iframe: false }"
16
+ >
17
+ <Variant
18
+ v-for="(variant, key) of variants"
19
+ :key="key"
20
+ :title="variant.name"
21
+ >
22
+ <HstButton
23
+ v-bind="variant.bind as {}"
24
+ class="htw-p-2"
25
+ >
26
+ Click me!
27
+ </HstButton>
28
+ </Variant>
29
+ </Story>
30
+ </template>
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstButton',
4
+ }
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ const colors = {
9
+ default: 'htw-bg-gray-200 dark:htw-bg-gray-750 htw-text-gray-900 dark:htw-text-gray-100 hover:htw-bg-primary-200 dark:hover:htw-bg-primary-900',
10
+ primary: 'htw-bg-primary-500 hover:htw-bg-primary-600 htw-text-white dark:htw-text-black',
11
+ flat: 'htw-bg-transparent hover:htw-bg-gray-500/20 htw-text-gray-900 dark:htw-text-gray-100',
12
+ }
13
+
14
+ defineProps<{
15
+ color?: keyof typeof colors
16
+ }>()
17
+ </script>
18
+
19
+ <template>
20
+ <button
21
+ class="histoire-button htw-cursor-pointer htw-rounded-sm htw-border-none"
22
+ :class="colors[color ?? 'default']"
23
+ >
24
+ <slot />
25
+ </button>
26
+ </template>
@@ -0,0 +1,79 @@
1
+ <script lang="ts" setup>
2
+ import { ref } from 'vue'
3
+ import HstButtonGroup from './HstButtonGroup.vue'
4
+
5
+ const options = {
6
+ slow: 'Slow',
7
+ fast: 'Fast',
8
+ max: 'Max',
9
+ }
10
+
11
+ const flatOptions = Object.keys(options)
12
+
13
+ const objectOptions = Object.keys(options).map(key => ({
14
+ label: options[key],
15
+ value: key,
16
+ }))
17
+
18
+ function initState() {
19
+ return {
20
+ speed: flatOptions[0],
21
+ }
22
+ }
23
+
24
+ const count = ref('0')
25
+ </script>
26
+
27
+ <template>
28
+ <Story
29
+ title="HstButtonGroup"
30
+ group="controls"
31
+ :layout="{ type: 'single', iframe: false }"
32
+ >
33
+ <Variant
34
+ title="playground"
35
+ :init-state="initState"
36
+ >
37
+ <template #default="{ state }">
38
+ <pre>{{ { speed: state.speed } }}</pre>
39
+ <HstButtonGroup
40
+ v-model="state.speed"
41
+ title="Label"
42
+ :options="objectOptions"
43
+ />
44
+ </template>
45
+
46
+ <template #controls="{ state }">
47
+ <HstButtonGroup
48
+ v-model="state.speed"
49
+ title="Label"
50
+ :options="objectOptions"
51
+ />
52
+ </template>
53
+ </Variant>
54
+
55
+ <Variant
56
+ title="Object options"
57
+ :init-state="initState"
58
+ >
59
+ <template #default="{ state }">
60
+ <pre>{{ { speed: state.speed } }}</pre>
61
+ <HstButtonGroup
62
+ v-model="state.speed"
63
+ title="Label"
64
+ :options="options"
65
+ />
66
+ </template>
67
+ </Variant>
68
+
69
+ <Variant
70
+ title="Should retain order"
71
+ >
72
+ <pre>{{ { count } }}</pre>
73
+ <HstButtonGroup
74
+ v-model="count"
75
+ :options="[{ label: 'Low', value: '-25' }, { label: 'Regular', value: '0' }, { label: 'High', value: '200' }]"
76
+ />
77
+ </Variant>
78
+ </Story>
79
+ </template>
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstButtonGroup',
4
+ }
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import type { ComputedRef } from 'vue'
9
+ import type { HstControlOption } from '../../types'
10
+ import { computed } from 'vue'
11
+ import HstWrapper from '../HstWrapper.vue'
12
+ import HstButton from './HstButton.vue'
13
+
14
+ const props = defineProps<{
15
+ title?: string
16
+ modelValue?: string
17
+ options: string[] | number[] | HstControlOption[] | Record<string, string | number>
18
+ }>()
19
+
20
+ const formattedOptions: ComputedRef<HstControlOption[]> = computed(() => {
21
+ if (Array.isArray(props.options)) {
22
+ return props.options.map((value: string | number | HstControlOption) => {
23
+ if (typeof value === 'string' || typeof value === 'number') {
24
+ return { value, label: String(value) }
25
+ }
26
+ else {
27
+ return value
28
+ }
29
+ })
30
+ }
31
+ else {
32
+ return Object.keys(props.options).map((value: string) => ({
33
+ value,
34
+ label: props.options[value],
35
+ }))
36
+ }
37
+ })
38
+
39
+ const emit = defineEmits<{
40
+ (e: 'update:modelValue', value: string): void
41
+ }>()
42
+
43
+ function selectOption(value: string) {
44
+ emit('update:modelValue', value)
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <HstWrapper
50
+ tag="div"
51
+ role="group"
52
+ :title="title"
53
+ class="histoire-button-group htw-flex-nowrap htw-items-center"
54
+ >
55
+ <div class="htw-flex htw-gap-px htw-border htw-border-solid htw-border-black/25 dark:htw-border-white/25 htw-rounded-sm htw-p-px">
56
+ <HstButton
57
+ v-for="{ label, value } of formattedOptions"
58
+ :key="value"
59
+ class="htw-px-1 htw-h-[22px] htw-flex-1 !htw-rounded-[3px]"
60
+ :color="value === modelValue ? 'primary' : 'flat'"
61
+ :rounded="false"
62
+ @click="selectOption(value)"
63
+ >
64
+ {{ label }}
65
+ </HstButton>
66
+ </div>
67
+ <template #actions>
68
+ <slot name="actions" />
69
+ </template>
70
+ </HstWrapper>
71
+ </template>
@@ -0,0 +1,28 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import HstCheckbox from './HstCheckbox.vue'
3
+
4
+ describe('hstCheckbox', () => {
5
+ it('toggle to checked', async () => {
6
+ const wrapper = mount(HstCheckbox, {
7
+ props: {
8
+ modelValue: false,
9
+ title: 'Label',
10
+ },
11
+ })
12
+ await wrapper.trigger('click')
13
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([true])
14
+ expect(wrapper.html()).toMatchSnapshot()
15
+ })
16
+
17
+ it('toggle to unchecked', async () => {
18
+ const wrapper = mount(HstCheckbox, {
19
+ props: {
20
+ modelValue: true,
21
+ title: 'Label',
22
+ },
23
+ })
24
+ await wrapper.trigger('click')
25
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([false])
26
+ expect(wrapper.html()).toMatchSnapshot()
27
+ })
28
+ })
@@ -0,0 +1,47 @@
1
+ <script lang="ts" setup>
2
+ import HstCheckbox from './HstCheckbox.vue'
3
+
4
+ function initState() {
5
+ return {
6
+ booleanChecked: false,
7
+ stringChecked: 'false',
8
+ }
9
+ }
10
+ </script>
11
+
12
+ <template>
13
+ <Story
14
+ title="HstCheckbox"
15
+ group="controls"
16
+ :layout="{ type: 'single', iframe: false }"
17
+ >
18
+ <Variant
19
+ title="playground"
20
+ :init-state="initState"
21
+ >
22
+ <template #default="{ state }">
23
+ <HstCheckbox
24
+ v-model="state.booleanChecked"
25
+ title="Boolean"
26
+ />
27
+ {{ { value: state.booleanChecked } }}
28
+ <HstCheckbox
29
+ v-model="state.stringChecked"
30
+ title="String"
31
+ />
32
+ {{ { value: state.stringChecked } }}
33
+ </template>
34
+
35
+ <template #controls="{ state }">
36
+ <HstCheckbox
37
+ v-model="state.booleanChecked"
38
+ title="Boolean"
39
+ />
40
+ <HstCheckbox
41
+ v-model="state.stringChecked"
42
+ title="String"
43
+ />
44
+ </template>
45
+ </Variant>
46
+ </Story>
47
+ </template>
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstCheckbox',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { computed } from 'vue'
9
+ import HstWrapper from '../HstWrapper.vue'
10
+ import HstSimpleCheckbox from './HstSimpleCheckbox.vue'
11
+
12
+ type Booleanish = boolean | 'true' | 'false'
13
+
14
+ const props = defineProps<{
15
+ modelValue?: Booleanish | null
16
+ title?: string
17
+ }>()
18
+
19
+ const emit = defineEmits({
20
+ 'update:modelValue': (newValue: Booleanish) => true,
21
+ })
22
+
23
+ function toggle() {
24
+ if (typeof props.modelValue === 'string') {
25
+ emit('update:modelValue', props.modelValue === 'false' ? 'true' : 'false')
26
+ return
27
+ }
28
+
29
+ emit('update:modelValue', !props.modelValue)
30
+ }
31
+
32
+ const isTrue = computed(() => {
33
+ if (typeof props.modelValue === 'string') {
34
+ return props.modelValue !== 'false'
35
+ }
36
+
37
+ return props.modelValue
38
+ })
39
+ </script>
40
+
41
+ <template>
42
+ <HstWrapper
43
+ role="checkbox"
44
+ tabindex="0"
45
+ class="histoire-checkbox htw-cursor-pointer htw-items-center"
46
+ :title="title"
47
+ @click="toggle()"
48
+ @keydown.enter.prevent="toggle()"
49
+ @keydown.space.prevent="toggle()"
50
+ >
51
+ <HstSimpleCheckbox :model-value="isTrue" />
52
+ <template #actions>
53
+ <slot name="actions" />
54
+ </template>
55
+ </HstWrapper>
56
+ </template>
@@ -0,0 +1,49 @@
1
+ <script lang="ts" setup>
2
+ import HstCheckboxList from './HstCheckboxList.vue'
3
+
4
+ const options = {
5
+ 'crash-bandicoot': 'Crash Bandicoot',
6
+ 'the-last-of-us': 'The Last of Us',
7
+ 'ghost-of-tsushima': 'Ghost of Tsushima',
8
+ }
9
+
10
+ const objectOptions = Object.keys(options).map(key => ({
11
+ label: options[key],
12
+ value: key,
13
+ }))
14
+
15
+ function initState() {
16
+ return {
17
+ characters: [],
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <Story
24
+ title="HstCheckboxList"
25
+ group="controls"
26
+ :layout="{ type: 'single', iframe: false }"
27
+ >
28
+ <Variant
29
+ title="playground"
30
+ :init-state="initState"
31
+ >
32
+ <template #default="{ state }">
33
+ <HstCheckboxList
34
+ v-model="state.characters"
35
+ title="Label"
36
+ :options="objectOptions"
37
+ />
38
+ </template>
39
+
40
+ <template #controls="{ state }">
41
+ <HstCheckboxList
42
+ v-model="state.characters"
43
+ title="Label"
44
+ :options="objectOptions"
45
+ />
46
+ </template>
47
+ </Variant>
48
+ </Story>
49
+ </template>
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstCheckboxList',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import type { ComputedRef } from 'vue'
9
+ import type { HstControlOption } from '../../types'
10
+ import { computed } from 'vue'
11
+ import HstWrapper from '../HstWrapper.vue'
12
+ import HstSimpleCheckbox from './HstSimpleCheckbox.vue'
13
+
14
+ const props = defineProps<{
15
+ title?: string
16
+ modelValue: Array<string>
17
+ options: string[] | HstControlOption[]
18
+ }>()
19
+
20
+ const formattedOptions: ComputedRef<Record<string, string>> = computed(() => {
21
+ if (Array.isArray(props.options)) {
22
+ return Object.fromEntries(props.options.map((value: string | HstControlOption) => {
23
+ if (typeof value === 'string') {
24
+ return [value, value]
25
+ }
26
+ else {
27
+ return [value.value, value.label]
28
+ }
29
+ }))
30
+ }
31
+ return props.options
32
+ })
33
+
34
+ const emit = defineEmits<{
35
+ (e: 'update:modelValue', value: Array<string>): void
36
+ }>()
37
+
38
+ function toggleOption(value: string) {
39
+ if (props.modelValue.includes(value)) {
40
+ emit('update:modelValue', props.modelValue.filter(element => element !== value))
41
+ }
42
+ else {
43
+ emit('update:modelValue', [...props.modelValue, value])
44
+ }
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <HstWrapper
50
+ role="group"
51
+ :title="title"
52
+ class="histoire-checkbox-list htw-cursor-text"
53
+ :class="$attrs.class"
54
+ :style="$attrs.style"
55
+ >
56
+ <div class="-htw-my-1">
57
+ <template
58
+ v-for="(label, value) in formattedOptions"
59
+ :key="value"
60
+ >
61
+ <label
62
+ tabindex="0"
63
+ :for="`${value}-radio`"
64
+ class="htw-cursor-pointer htw-flex htw-items-center htw-relative htw-py-1 htw-group"
65
+ @keydown.enter.prevent="toggleOption(value)"
66
+ @keydown.space.prevent="toggleOption(value)"
67
+ @click="toggleOption(value)"
68
+ >
69
+ <HstSimpleCheckbox
70
+ :model-value="modelValue.includes(value)"
71
+ class="htw-mr-2"
72
+ />
73
+ {{ label }}
74
+ </label>
75
+ </template>
76
+ </div>
77
+
78
+ <template #actions>
79
+ <slot name="actions" />
80
+ </template>
81
+ </HstWrapper>
82
+ </template>