fds-vue-core 2.1.4 → 2.1.6
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.
- package/components.d.ts +8 -0
- package/configs/tsconfig.base.json +2 -1
- package/dist/fds-vue-core.cjs.js +35 -15
- package/dist/fds-vue-core.cjs.js.map +1 -1
- package/dist/fds-vue-core.es.js +35 -15
- package/dist/fds-vue-core.es.js.map +1 -1
- package/dist/global-components.d.ts +35 -33
- package/package.json +23 -21
- package/src/.DS_Store +0 -0
- package/src/App.vue +133 -0
- package/src/apply.css +60 -0
- package/src/assets/icons.ts +517 -0
- package/src/components/Blocks/FdsBlockAlert/FdsBlockAlert.stories.ts +94 -0
- package/src/components/Blocks/FdsBlockAlert/FdsBlockAlert.vue +112 -0
- package/src/components/Blocks/FdsBlockAlert/types.ts +12 -0
- package/src/components/Blocks/FdsBlockContent/FdsBlockContent.stories.ts +110 -0
- package/src/components/Blocks/FdsBlockContent/FdsBlockContent.vue +66 -0
- package/src/components/Blocks/FdsBlockContent/types.ts +6 -0
- package/src/components/Blocks/FdsBlockExpander/FdsBlockExpander.stories.ts +123 -0
- package/src/components/Blocks/FdsBlockExpander/FdsBlockExpander.vue +87 -0
- package/src/components/Blocks/FdsBlockExpander/types.ts +8 -0
- package/src/components/Blocks/FdsBlockInfo/FdsBlockInfo.stories.ts +110 -0
- package/src/components/Blocks/FdsBlockInfo/FdsBlockInfo.vue +75 -0
- package/src/components/Blocks/FdsBlockInfo/types.ts +9 -0
- package/src/components/Blocks/FdsBlockLink/FdsBlockLink.css +9 -0
- package/src/components/Blocks/FdsBlockLink/FdsBlockLink.stories.ts +179 -0
- package/src/components/Blocks/FdsBlockLink/FdsBlockLink.vue +149 -0
- package/src/components/Blocks/FdsBlockLink/types.ts +14 -0
- package/src/components/Buttons/ButtonBaseProps.ts +18 -0
- package/src/components/Buttons/FdsButtonCopy/FdsButtonCopy.stories.ts +53 -0
- package/src/components/Buttons/FdsButtonCopy/FdsButtonCopy.vue +87 -0
- package/src/components/Buttons/FdsButtonCopy/types.ts +8 -0
- package/src/components/Buttons/FdsButtonDownload/FdsButtonDownload.stories.ts +111 -0
- package/src/components/Buttons/FdsButtonDownload/FdsButtonDownload.vue +187 -0
- package/src/components/Buttons/FdsButtonIcon/FdsButtonIcon.stories.ts +55 -0
- package/src/components/Buttons/FdsButtonIcon/FdsButtonIcon.vue +57 -0
- package/src/components/Buttons/FdsButtonIcon/types.ts +12 -0
- package/src/components/Buttons/FdsButtonMinor/FdsButtonMinor.stories.ts +68 -0
- package/src/components/Buttons/FdsButtonMinor/FdsButtonMinor.vue +126 -0
- package/src/components/Buttons/FdsButtonPrimary/FdsButtonPrimary.stories.ts +86 -0
- package/src/components/Buttons/FdsButtonPrimary/FdsButtonPrimary.vue +107 -0
- package/src/components/Buttons/FdsButtonSecondary/FdsButtonSecondary.stories.ts +68 -0
- package/src/components/Buttons/FdsButtonSecondary/FdsButtonSecondary.vue +107 -0
- package/src/components/FdsIcon/FdsIcon.stories.ts +69 -0
- package/src/components/FdsIcon/FdsIcon.vue +34 -0
- package/src/components/FdsIcon/types.ts +9 -0
- package/src/components/FdsModal/FdsModal.stories.ts +241 -0
- package/src/components/FdsModal/FdsModal.vue +269 -0
- package/src/components/FdsModal/types.ts +12 -0
- package/src/components/FdsPagination/FdsPagination.stories.ts +109 -0
- package/src/components/FdsPagination/FdsPagination.vue +193 -0
- package/src/components/FdsPagination/types.ts +6 -0
- package/src/components/FdsSearchSelect/FdsSearchSelect.stories.ts +428 -0
- package/src/components/FdsSearchSelect/FdsSearchSelect.vue +621 -0
- package/src/components/FdsSearchSelect/types.ts +25 -0
- package/src/components/FdsSpinner/FdsSpinner.stories.ts +31 -0
- package/src/components/FdsSpinner/FdsSpinner.vue +90 -0
- package/src/components/FdsSpinner/types.ts +6 -0
- package/src/components/FdsSticker/FdsSticker.stories.ts +148 -0
- package/src/components/FdsSticker/FdsSticker.vue +44 -0
- package/src/components/FdsSticker/types.ts +4 -0
- package/src/components/FdsTreeView/FdsTreeView.stories.ts +136 -0
- package/src/components/FdsTreeView/FdsTreeView.vue +162 -0
- package/src/components/FdsTreeView/TreeNode.vue +383 -0
- package/src/components/FdsTreeView/types.ts +141 -0
- package/src/components/FdsTreeView/useTreeState.ts +607 -0
- package/src/components/FdsTreeView/utils.ts +69 -0
- package/src/components/FdsTruncatedText/FdsTruncatedText.stories.ts +78 -0
- package/src/components/FdsTruncatedText/FdsTruncatedText.vue +85 -0
- package/src/components/FdsTruncatedText/types.ts +6 -0
- package/src/components/Form/FdsCheckbox/FdsCheckbox.stories.ts +275 -0
- package/src/components/Form/FdsCheckbox/FdsCheckbox.vue +155 -0
- package/src/components/Form/FdsCheckbox/types.ts +10 -0
- package/src/components/Form/FdsInput/FdsInput.stories.ts +319 -0
- package/src/components/Form/FdsInput/FdsInput.vue +233 -0
- package/src/components/Form/FdsInput/types.ts +25 -0
- package/src/components/Form/FdsRadio/FdsRadio.stories.ts +63 -0
- package/src/components/Form/FdsRadio/FdsRadio.vue +88 -0
- package/src/components/Form/FdsRadio/types.ts +12 -0
- package/src/components/Form/FdsSelect/FdsSelect.stories.ts +78 -0
- package/src/components/Form/FdsSelect/FdsSelect.vue +136 -0
- package/src/components/Form/FdsSelect/types.ts +13 -0
- package/src/components/Form/FdsTextarea/FdsTextarea.stories.ts +52 -0
- package/src/components/Form/FdsTextarea/FdsTextarea.vue +110 -0
- package/src/components/Form/FdsTextarea/types.ts +12 -0
- package/src/components/Table/FdsTable/FdsTable.stories.ts +221 -0
- package/src/components/Table/FdsTable/FdsTable.vue +25 -0
- package/src/components/Table/FdsTable/types.ts +4 -0
- package/src/components/Table/FdsTableHead/FdsTableHead.stories.ts +151 -0
- package/src/components/Table/FdsTableHead/FdsTableHead.vue +54 -0
- package/src/components/Table/FdsTableHead/types.ts +5 -0
- package/src/components/Tabs/FdsTabs/FdsTabs.stories.ts +247 -0
- package/src/components/Tabs/FdsTabs/FdsTabs.vue +27 -0
- package/src/components/Tabs/FdsTabs/types.ts +4 -0
- package/src/components/Tabs/FdsTabsItem/FdsTabsItem.vue +125 -0
- package/src/components/Tabs/FdsTabsItem/types.ts +16 -0
- package/src/components/Typography/FdsHeading/FdsHeading.stories.ts +93 -0
- package/src/components/Typography/FdsHeading/FdsHeading.vue +51 -0
- package/src/components/Typography/FdsHeading/types.ts +5 -0
- package/src/components/Typography/FdsListHeading/FdsListHeading.stories.ts +58 -0
- package/src/components/Typography/FdsListHeading/FdsListHeading.vue +62 -0
- package/src/components/Typography/FdsListHeading/types.ts +8 -0
- package/src/components/Typography/FdsSeparator/FdsSeparator.stories.ts +31 -0
- package/src/components/Typography/FdsSeparator/FdsSeparator.vue +5 -0
- package/src/components/Typography/FdsText/FdsText.stories.ts +66 -0
- package/src/components/Typography/FdsText/FdsText.vue +28 -0
- package/src/components/Typography/FdsText/types.ts +3 -0
- package/src/composables/useBoldQuery.ts +29 -0
- package/src/composables/useElementFinalSize.ts +24 -0
- package/src/composables/useHasSlots.ts +17 -0
- package/src/composables/useIsPid.ts +48 -0
- package/src/docs/Start/Start.mdx +12 -0
- package/src/docs/Usage.md +117 -0
- package/src/fonts.css +28 -0
- package/src/global-components.ts +75 -0
- package/src/index.ts +180 -0
- package/src/main.ts +7 -0
- package/src/slot-styles.css +93 -0
- package/src/style.css +89 -0
- package/src/tokens.css +252 -0
- package/dist/index.d.ts +0 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
3
|
+
import FdsTruncatedText from './FdsTruncatedText.vue'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FdsTruncatedText> = {
|
|
6
|
+
title: 'FDS/FdsTruncatedText',
|
|
7
|
+
component: FdsTruncatedText,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
open: { control: { type: 'boolean' } },
|
|
11
|
+
content: { control: { type: 'text' } },
|
|
12
|
+
btnExpand: { control: { type: 'text' } },
|
|
13
|
+
btnCollapse: { control: { type: 'text' } },
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
open: false,
|
|
17
|
+
content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
|
18
|
+
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
|
|
19
|
+
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
|
|
20
|
+
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
21
|
+
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
|
|
22
|
+
anim id est laborum.`,
|
|
23
|
+
btnExpand: 'Visa mer',
|
|
24
|
+
btnCollapse: 'Visa mindre',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default meta
|
|
29
|
+
type Story = StoryObj<typeof meta>
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
render: (args) => ({
|
|
33
|
+
components: { FdsTruncatedText },
|
|
34
|
+
setup: () => {
|
|
35
|
+
const open = ref(args.open)
|
|
36
|
+
const handleToggle = (data: { open: boolean }) => {
|
|
37
|
+
open.value = data.open
|
|
38
|
+
}
|
|
39
|
+
return { args: { ...args, open }, handleToggle }
|
|
40
|
+
},
|
|
41
|
+
template: `<FdsTruncatedText v-bind="args" @toggleHandler="handleToggle" />`,
|
|
42
|
+
}),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const WithSlot: Story = {
|
|
46
|
+
render: (args) => ({
|
|
47
|
+
components: { FdsTruncatedText },
|
|
48
|
+
setup: () => {
|
|
49
|
+
const open = ref(args.open)
|
|
50
|
+
const handleToggle = (data: { open: boolean }) => {
|
|
51
|
+
open.value = data.open
|
|
52
|
+
}
|
|
53
|
+
return { args: { ...args, open }, handleToggle }
|
|
54
|
+
},
|
|
55
|
+
template: `
|
|
56
|
+
<FdsTruncatedText v-bind="args" @toggleHandler="handleToggle">
|
|
57
|
+
Additional content that can be collapsed. This is a longer paragraph with more text to demonstrate the truncation functionality. It should be long enough to require the expand/collapse button.
|
|
58
|
+
</FdsTruncatedText>
|
|
59
|
+
`,
|
|
60
|
+
}),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Open: Story = {
|
|
64
|
+
render: (args) => ({
|
|
65
|
+
components: { FdsTruncatedText },
|
|
66
|
+
setup: () => {
|
|
67
|
+
const open = ref(args.open)
|
|
68
|
+
const handleToggle = (data: { open: boolean }) => {
|
|
69
|
+
open.value = data.open
|
|
70
|
+
}
|
|
71
|
+
return { args: { ...args, open }, handleToggle }
|
|
72
|
+
},
|
|
73
|
+
template: `<FdsTruncatedText v-bind="args" @toggleHandler="handleToggle" />`,
|
|
74
|
+
}),
|
|
75
|
+
args: {
|
|
76
|
+
open: true,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useHasSlot } from '@/composables/useHasSlots'
|
|
3
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
4
|
+
import FdsButtonMinor from '@/components/Buttons/FdsButtonMinor/FdsButtonMinor.vue'
|
|
5
|
+
import type { FdsTruncatedTextProps } from './types'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<FdsTruncatedTextProps>(), {
|
|
8
|
+
open: false,
|
|
9
|
+
btnExpand: 'Visa mer',
|
|
10
|
+
btnCollapse: 'Visa mindre',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits<{
|
|
14
|
+
(e: 'toggleHandler', data: { open: boolean }): void
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const isOpen = ref(props.open)
|
|
18
|
+
const contentRef = ref<HTMLDivElement | null>(null)
|
|
19
|
+
const hasSlotContent = useHasSlot()
|
|
20
|
+
const containerWidth = ref(0)
|
|
21
|
+
const truncated = ref(false)
|
|
22
|
+
const slotContentRef = ref<HTMLDivElement | null>(null)
|
|
23
|
+
|
|
24
|
+
const shouldShowButton = computed(() => {
|
|
25
|
+
if (hasSlotContent.value) return true
|
|
26
|
+
return truncated.value
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const toggleComponent = () => {
|
|
30
|
+
isOpen.value = !isOpen.value
|
|
31
|
+
emit('toggleHandler', { open: isOpen.value })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
watch(
|
|
35
|
+
() => props.open,
|
|
36
|
+
(newValue) => {
|
|
37
|
+
isOpen.value = newValue
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
onMounted(() => {
|
|
42
|
+
if (hasSlotContent.value && slotContentRef.value?.parentElement) {
|
|
43
|
+
containerWidth.value = slotContentRef.value.parentElement.offsetWidth
|
|
44
|
+
const threshold = slotContentRef.value.offsetWidth / 8
|
|
45
|
+
const slotText = slotContentRef.value.textContent || ''
|
|
46
|
+
if (threshold <= slotText.length) {
|
|
47
|
+
truncated.value = true
|
|
48
|
+
}
|
|
49
|
+
} else if (contentRef.value?.parentElement) {
|
|
50
|
+
containerWidth.value = contentRef.value.parentElement.offsetWidth
|
|
51
|
+
const threshold = contentRef.value.offsetWidth / 8
|
|
52
|
+
if (threshold <= props.content.length) {
|
|
53
|
+
truncated.value = true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<div class="block mb-4">
|
|
61
|
+
<div
|
|
62
|
+
v-if="hasSlotContent"
|
|
63
|
+
ref="slotContentRef"
|
|
64
|
+
:class="['content', { open: isOpen }]"
|
|
65
|
+
:style="{ maxWidth: `${containerWidth}px` }"
|
|
66
|
+
>
|
|
67
|
+
<slot />
|
|
68
|
+
</div>
|
|
69
|
+
<div
|
|
70
|
+
v-else
|
|
71
|
+
ref="contentRef"
|
|
72
|
+
:class="['content', { open: isOpen }]"
|
|
73
|
+
:style="{ maxWidth: `${containerWidth}px` }"
|
|
74
|
+
>
|
|
75
|
+
{{ content }}
|
|
76
|
+
</div>
|
|
77
|
+
<FdsButtonMinor
|
|
78
|
+
v-if="shouldShowButton"
|
|
79
|
+
:icon="isOpen ? 'arrowUp' : 'arrowDown'"
|
|
80
|
+
iconPos="right"
|
|
81
|
+
:text="isOpen ? btnCollapse : btnExpand"
|
|
82
|
+
@click="toggleComponent"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
import FdsCheckbox from './FdsCheckbox.vue'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FdsCheckbox> = {
|
|
6
|
+
title: 'FDS/Form/FdsCheckbox',
|
|
7
|
+
component: FdsCheckbox,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
label: { control: { type: 'text' } },
|
|
11
|
+
checked: { control: { type: 'boolean' } },
|
|
12
|
+
indeterminate: { control: { type: 'boolean' } },
|
|
13
|
+
disabled: { control: { type: 'boolean' } },
|
|
14
|
+
required: { control: { type: 'boolean' } },
|
|
15
|
+
value: { control: { type: 'text' } },
|
|
16
|
+
name: { control: { type: 'text' } },
|
|
17
|
+
id: { control: { type: 'text' } },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
label: 'Checkbox label',
|
|
21
|
+
checked: false,
|
|
22
|
+
indeterminate: false,
|
|
23
|
+
disabled: false,
|
|
24
|
+
required: false,
|
|
25
|
+
value: undefined,
|
|
26
|
+
name: 'checkbox-name',
|
|
27
|
+
id: 'checkbox-id',
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default meta
|
|
32
|
+
type Story = StoryObj<typeof meta>
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
checked: false,
|
|
37
|
+
label: 'Checkbox label',
|
|
38
|
+
},
|
|
39
|
+
render: (args) => ({
|
|
40
|
+
components: { FdsCheckbox },
|
|
41
|
+
setup() {
|
|
42
|
+
const checked = ref(args.checked)
|
|
43
|
+
const handleChange = (value: boolean) => {
|
|
44
|
+
checked.value = value
|
|
45
|
+
}
|
|
46
|
+
// Watch for args changes from controls
|
|
47
|
+
watch(
|
|
48
|
+
() => args.checked,
|
|
49
|
+
(newValue) => {
|
|
50
|
+
checked.value = newValue
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
return { args, checked, handleChange }
|
|
54
|
+
},
|
|
55
|
+
template: `
|
|
56
|
+
<FdsCheckbox
|
|
57
|
+
v-bind="args"
|
|
58
|
+
@change="handleChange"
|
|
59
|
+
/>
|
|
60
|
+
`,
|
|
61
|
+
}),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const Checked: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
checked: true,
|
|
67
|
+
},
|
|
68
|
+
render: (args) => ({
|
|
69
|
+
components: { FdsCheckbox },
|
|
70
|
+
setup() {
|
|
71
|
+
const checked = ref(args.checked)
|
|
72
|
+
const handleChange = (value: boolean) => {
|
|
73
|
+
checked.value = value
|
|
74
|
+
}
|
|
75
|
+
// Watch for args changes from controls
|
|
76
|
+
watch(
|
|
77
|
+
() => args.checked,
|
|
78
|
+
(newValue) => {
|
|
79
|
+
checked.value = newValue
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
return { args, checked, handleChange }
|
|
83
|
+
},
|
|
84
|
+
template: `
|
|
85
|
+
<FdsCheckbox
|
|
86
|
+
v-bind="args"
|
|
87
|
+
:checked="checked"
|
|
88
|
+
@change="handleChange"
|
|
89
|
+
>
|
|
90
|
+
Checked checkbox
|
|
91
|
+
</FdsCheckbox>
|
|
92
|
+
`,
|
|
93
|
+
}),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Indeterminate: Story = {
|
|
97
|
+
args: {
|
|
98
|
+
indeterminate: true,
|
|
99
|
+
},
|
|
100
|
+
render: (args) => ({
|
|
101
|
+
components: { FdsCheckbox },
|
|
102
|
+
setup() {
|
|
103
|
+
const checked = ref(args.checked)
|
|
104
|
+
const handleChange = (value: boolean) => {
|
|
105
|
+
checked.value = value
|
|
106
|
+
}
|
|
107
|
+
// Watch for args changes from controls
|
|
108
|
+
watch(
|
|
109
|
+
() => args.checked,
|
|
110
|
+
(newValue) => {
|
|
111
|
+
checked.value = newValue
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
return { args, checked, handleChange }
|
|
115
|
+
},
|
|
116
|
+
template: `
|
|
117
|
+
<FdsCheckbox
|
|
118
|
+
v-bind="args"
|
|
119
|
+
:checked="checked"
|
|
120
|
+
@change="handleChange"
|
|
121
|
+
>
|
|
122
|
+
Indeterminate checkbox
|
|
123
|
+
</FdsCheckbox>
|
|
124
|
+
`,
|
|
125
|
+
}),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const Disabled: Story = {
|
|
129
|
+
args: {
|
|
130
|
+
disabled: true,
|
|
131
|
+
checked: true,
|
|
132
|
+
},
|
|
133
|
+
render: (args) => ({
|
|
134
|
+
components: { FdsCheckbox },
|
|
135
|
+
setup() {
|
|
136
|
+
const checked = ref(args.checked)
|
|
137
|
+
const handleChange = (value: boolean) => {
|
|
138
|
+
checked.value = value
|
|
139
|
+
}
|
|
140
|
+
// Watch for args changes from controls
|
|
141
|
+
watch(
|
|
142
|
+
() => args.checked,
|
|
143
|
+
(newValue) => {
|
|
144
|
+
checked.value = newValue
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
return { args, checked, handleChange }
|
|
148
|
+
},
|
|
149
|
+
template: `
|
|
150
|
+
<FdsCheckbox
|
|
151
|
+
v-bind="args"
|
|
152
|
+
:checked="checked"
|
|
153
|
+
@change="handleChange"
|
|
154
|
+
>
|
|
155
|
+
Disabled checkbox
|
|
156
|
+
</FdsCheckbox>
|
|
157
|
+
`,
|
|
158
|
+
}),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const States: Story = {
|
|
162
|
+
render: () => ({
|
|
163
|
+
components: { FdsCheckbox },
|
|
164
|
+
setup() {
|
|
165
|
+
const checked1 = ref(false)
|
|
166
|
+
const checked2 = ref(true)
|
|
167
|
+
const checked3 = ref(false)
|
|
168
|
+
const checked4 = ref(false)
|
|
169
|
+
const checked5 = ref(true)
|
|
170
|
+
const handleChange1 = (value: boolean) => {
|
|
171
|
+
checked1.value = value
|
|
172
|
+
}
|
|
173
|
+
const handleChange2 = (value: boolean) => {
|
|
174
|
+
checked2.value = value
|
|
175
|
+
}
|
|
176
|
+
const handleChange3 = (value: boolean) => {
|
|
177
|
+
checked3.value = value
|
|
178
|
+
}
|
|
179
|
+
const handleChange4 = (value: boolean) => {
|
|
180
|
+
checked4.value = value
|
|
181
|
+
}
|
|
182
|
+
const handleChange5 = (value: boolean) => {
|
|
183
|
+
checked5.value = value
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
checked1,
|
|
187
|
+
checked2,
|
|
188
|
+
checked3,
|
|
189
|
+
checked4,
|
|
190
|
+
checked5,
|
|
191
|
+
handleChange1,
|
|
192
|
+
handleChange2,
|
|
193
|
+
handleChange3,
|
|
194
|
+
handleChange4,
|
|
195
|
+
handleChange5,
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
template: `
|
|
199
|
+
<div class="space-y-4">
|
|
200
|
+
<div class="flex items-center space-x-4">
|
|
201
|
+
<FdsCheckbox :checked="checked1" @change="handleChange1">
|
|
202
|
+
Unchecked
|
|
203
|
+
</FdsCheckbox>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="flex items-center space-x-4">
|
|
206
|
+
<FdsCheckbox :checked="checked2" @change="handleChange2">
|
|
207
|
+
Checked
|
|
208
|
+
</FdsCheckbox>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="flex items-center space-x-4">
|
|
211
|
+
<FdsCheckbox :checked="checked3" @change="handleChange3" indeterminate>
|
|
212
|
+
Indeterminate
|
|
213
|
+
</FdsCheckbox>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="flex items-center space-x-4">
|
|
216
|
+
<FdsCheckbox :checked="checked4" @change="handleChange4" disabled>
|
|
217
|
+
Disabled unchecked
|
|
218
|
+
</FdsCheckbox>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="flex items-center space-x-4">
|
|
221
|
+
<FdsCheckbox :checked="checked5" @change="handleChange5" disabled>
|
|
222
|
+
Disabled checked
|
|
223
|
+
</FdsCheckbox>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
`,
|
|
227
|
+
}),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const WithForm: Story = {
|
|
231
|
+
render: () => ({
|
|
232
|
+
components: { FdsCheckbox },
|
|
233
|
+
setup() {
|
|
234
|
+
const terms = ref(false)
|
|
235
|
+
const newsletter = ref(false)
|
|
236
|
+
const marketing = ref(true)
|
|
237
|
+
const handleTermsChange = (value: boolean) => {
|
|
238
|
+
terms.value = value
|
|
239
|
+
}
|
|
240
|
+
const handleNewsletterChange = (value: boolean) => {
|
|
241
|
+
newsletter.value = value
|
|
242
|
+
}
|
|
243
|
+
const handleMarketingChange = (value: boolean) => {
|
|
244
|
+
marketing.value = value
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
terms,
|
|
248
|
+
newsletter,
|
|
249
|
+
marketing,
|
|
250
|
+
handleTermsChange,
|
|
251
|
+
handleNewsletterChange,
|
|
252
|
+
handleMarketingChange,
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
template: `
|
|
256
|
+
<form class="space-y-4">
|
|
257
|
+
<div>
|
|
258
|
+
<FdsCheckbox :checked="terms" @change="handleTermsChange" name="terms" id="terms" required>
|
|
259
|
+
I agree to the terms and conditions
|
|
260
|
+
</FdsCheckbox>
|
|
261
|
+
</div>
|
|
262
|
+
<div>
|
|
263
|
+
<FdsCheckbox :checked="newsletter" @change="handleNewsletterChange" name="newsletter" id="newsletter">
|
|
264
|
+
Subscribe to newsletter
|
|
265
|
+
</FdsCheckbox>
|
|
266
|
+
</div>
|
|
267
|
+
<div>
|
|
268
|
+
<FdsCheckbox :checked="marketing" @change="handleMarketingChange" name="marketing" id="marketing">
|
|
269
|
+
Receive marketing emails
|
|
270
|
+
</FdsCheckbox>
|
|
271
|
+
</div>
|
|
272
|
+
</form>
|
|
273
|
+
`,
|
|
274
|
+
}),
|
|
275
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useHasSlot } from '@/composables/useHasSlots'
|
|
3
|
+
import { computed, nextTick, watch } from 'vue'
|
|
4
|
+
import type { FdsCheckboxProps } from './types'
|
|
5
|
+
|
|
6
|
+
// Support v-model as boolean (single checkbox) or array (checkbox group)
|
|
7
|
+
// Also support :checked prop for backward compatibility
|
|
8
|
+
const modelValue = defineModel<boolean | Array<string | number>>({
|
|
9
|
+
default: undefined,
|
|
10
|
+
required: false,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<FdsCheckboxProps>(), {
|
|
14
|
+
label: undefined,
|
|
15
|
+
checked: false,
|
|
16
|
+
indeterminate: false,
|
|
17
|
+
disabled: false,
|
|
18
|
+
value: undefined,
|
|
19
|
+
name: undefined,
|
|
20
|
+
id: undefined,
|
|
21
|
+
required: false,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
(e: 'change', value: boolean): void
|
|
26
|
+
(e: 'input', value: boolean): void
|
|
27
|
+
(e: 'update:checked', value: boolean): void
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const wrapperClasses = computed(() => ['block relative flex items-center mb-2 last:mb-0'])
|
|
31
|
+
const innerWrapperClasses = computed(() => [
|
|
32
|
+
'flex p-0.5 items-start rounded-md',
|
|
33
|
+
'hover:bg-blue_t-100 active:bg-blue_t-200',
|
|
34
|
+
'[&:has(:focus-visible)]:outline-2 [&:has(:focus-visible)]:outline-dashed [&:has(:focus-visible)]:-outline-offset-2 [&:has(:focus-visible)]:outline-blue-500',
|
|
35
|
+
props.disabled && 'hover:bg-transparent active:bg-transparent',
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
const checkboxClasses = computed(() => [
|
|
39
|
+
'rounded-md transition-colors duration-200 accent-blue-500 m-[3px]',
|
|
40
|
+
'peer z-2 bg-white min-w-[18px] min-h-[18px] focus-visible:outline-none',
|
|
41
|
+
props.disabled && 'cursor-not-allowed',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
const autoId = `fds-checkbox-${Math.random().toString(36).slice(2, 9)}`
|
|
45
|
+
const inputId = computed(() => props.id ?? autoId)
|
|
46
|
+
|
|
47
|
+
const hasLabelSlot = useHasSlot()
|
|
48
|
+
|
|
49
|
+
// Bridge for v-model (boolean or array) or :checked prop
|
|
50
|
+
const internalChecked = computed<boolean>({
|
|
51
|
+
get: () => {
|
|
52
|
+
if (modelValue.value === undefined) {
|
|
53
|
+
return props.checked
|
|
54
|
+
}
|
|
55
|
+
// If modelValue is an array, check if this checkbox's value is in it
|
|
56
|
+
if (Array.isArray(modelValue.value)) {
|
|
57
|
+
return props.value !== undefined && modelValue.value.includes(props.value)
|
|
58
|
+
}
|
|
59
|
+
// Otherwise treat as boolean
|
|
60
|
+
return modelValue.value as boolean
|
|
61
|
+
},
|
|
62
|
+
set: (checked: boolean) => {
|
|
63
|
+
if (modelValue.value === undefined) {
|
|
64
|
+
emit('update:checked', checked)
|
|
65
|
+
emit('change', checked)
|
|
66
|
+
emit('input', checked)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If modelValue is an array, add/remove this checkbox's value
|
|
71
|
+
if (Array.isArray(modelValue.value)) {
|
|
72
|
+
if (props.value === undefined) {
|
|
73
|
+
emit('update:checked', checked)
|
|
74
|
+
emit('change', checked)
|
|
75
|
+
emit('input', checked)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newArray = [...modelValue.value]
|
|
80
|
+
if (checked) {
|
|
81
|
+
// Add value if not already present
|
|
82
|
+
if (!newArray.includes(props.value)) {
|
|
83
|
+
newArray.push(props.value)
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// Remove value if present
|
|
87
|
+
const index = newArray.indexOf(props.value)
|
|
88
|
+
if (index > -1) {
|
|
89
|
+
newArray.splice(index, 1)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
modelValue.value = newArray
|
|
93
|
+
emit('update:checked', checked)
|
|
94
|
+
emit('change', checked)
|
|
95
|
+
emit('input', checked)
|
|
96
|
+
} else {
|
|
97
|
+
// Boolean mode
|
|
98
|
+
modelValue.value = checked
|
|
99
|
+
emit('update:checked', checked)
|
|
100
|
+
emit('change', checked)
|
|
101
|
+
emit('input', checked)
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Set indeterminate state when prop changes
|
|
107
|
+
function setIndeterminate() {
|
|
108
|
+
const checkbox = document.getElementById(inputId.value) as HTMLInputElement
|
|
109
|
+
if (checkbox) {
|
|
110
|
+
checkbox.indeterminate = props.indeterminate
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Watch for indeterminate prop changes
|
|
115
|
+
watch(
|
|
116
|
+
() => props.indeterminate,
|
|
117
|
+
() => {
|
|
118
|
+
nextTick(() => setIndeterminate())
|
|
119
|
+
},
|
|
120
|
+
{ immediate: true },
|
|
121
|
+
)
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<div :class="wrapperClasses">
|
|
126
|
+
<label
|
|
127
|
+
:for="inputId"
|
|
128
|
+
:class="[innerWrapperClasses, { 'cursor-not-allowed': disabled }]"
|
|
129
|
+
v-bind="$attrs"
|
|
130
|
+
>
|
|
131
|
+
<input
|
|
132
|
+
:id="inputId"
|
|
133
|
+
:name="name"
|
|
134
|
+
:value="value"
|
|
135
|
+
v-model="internalChecked"
|
|
136
|
+
:disabled="disabled"
|
|
137
|
+
:required="required"
|
|
138
|
+
type="checkbox"
|
|
139
|
+
:class="[checkboxClasses]"
|
|
140
|
+
/>
|
|
141
|
+
<span
|
|
142
|
+
v-if="hasLabelSlot || label"
|
|
143
|
+
class="relative inline-block leading-6 pl-1 select-none"
|
|
144
|
+
:class="{ 'cursor-not-allowed': disabled }"
|
|
145
|
+
>
|
|
146
|
+
<template v-if="hasLabelSlot">
|
|
147
|
+
<slot></slot>
|
|
148
|
+
</template>
|
|
149
|
+
<template v-else-if="label">
|
|
150
|
+
{{ label }}
|
|
151
|
+
</template>
|
|
152
|
+
</span>
|
|
153
|
+
</label>
|
|
154
|
+
</div>
|
|
155
|
+
</template>
|