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,110 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import icons from '@/assets/icons'
|
|
3
|
+
import FdsBlockInfo from './FdsBlockInfo.vue'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FdsBlockInfo> = {
|
|
6
|
+
title: 'FDS/Blocks/FdsBlockInfo',
|
|
7
|
+
component: FdsBlockInfo,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
heading: { control: { type: 'text' } },
|
|
11
|
+
size: { control: { type: 'select' }, options: ['small', 'large'] },
|
|
12
|
+
icon: { control: { type: 'select' }, options: Object.keys(icons) },
|
|
13
|
+
headerInfo: {
|
|
14
|
+
control: { type: 'text' },
|
|
15
|
+
description: 'Header info slot (HTML only in Storybook, supports Vue components when consumed)',
|
|
16
|
+
},
|
|
17
|
+
default: { control: { type: 'text' } },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
heading: 'About this component',
|
|
21
|
+
size: 'small',
|
|
22
|
+
icon: undefined,
|
|
23
|
+
headerInfo: '',
|
|
24
|
+
header: 'About this component',
|
|
25
|
+
default:
|
|
26
|
+
'The Info Block can contain various types and amounts of content. We recommend using multiple instances of it for grouping related content.',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default meta
|
|
31
|
+
type Story = StoryObj<typeof meta>
|
|
32
|
+
|
|
33
|
+
const blockInfoTransform = (storyContext: {
|
|
34
|
+
args?: {
|
|
35
|
+
heading?: string
|
|
36
|
+
headerInfo?: string
|
|
37
|
+
default?: string
|
|
38
|
+
icon?: string
|
|
39
|
+
tight?: boolean
|
|
40
|
+
}
|
|
41
|
+
}) => {
|
|
42
|
+
const args = storyContext?.args || {}
|
|
43
|
+
const attrs = []
|
|
44
|
+
|
|
45
|
+
if (args.icon) attrs.push(`icon="${args.icon}"`)
|
|
46
|
+
if (args.tight) attrs.push(':tight="true"')
|
|
47
|
+
|
|
48
|
+
const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
|
|
49
|
+
let content = ''
|
|
50
|
+
|
|
51
|
+
if (args.headerInfo || args.default) {
|
|
52
|
+
content = '\n'
|
|
53
|
+
if (args.headerInfo) {
|
|
54
|
+
content += ` <template #headerInfo>\n <div v-html="${args.headerInfo}" />\n </template>\n`
|
|
55
|
+
}
|
|
56
|
+
if (args.default) {
|
|
57
|
+
content += ` <p>${args.default}</p>\n`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `<FdsBlockInfo${attrsStr}>${content}</FdsBlockInfo>`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const Default: Story = {
|
|
65
|
+
render: (args) => ({
|
|
66
|
+
components: { FdsBlockInfo },
|
|
67
|
+
setup: () => ({ args }),
|
|
68
|
+
template: `
|
|
69
|
+
<FdsBlockInfo v-bind="args">
|
|
70
|
+
<template #headerInfo>
|
|
71
|
+
<div v-if="args.headerInfo" v-html="args.headerInfo"></div>
|
|
72
|
+
</template>
|
|
73
|
+
<p>{{ args.default }}</p>
|
|
74
|
+
</FdsBlockInfo>`,
|
|
75
|
+
}),
|
|
76
|
+
parameters: {
|
|
77
|
+
docs: {
|
|
78
|
+
source: {
|
|
79
|
+
transform: blockInfoTransform,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
args: {
|
|
84
|
+
heading: 'About this component',
|
|
85
|
+
default:
|
|
86
|
+
'The Info Block can contain various types and amounts of content. We recommend using multiple instances of it for grouping related content.',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const WithIcon: Story = {
|
|
91
|
+
render: () => ({
|
|
92
|
+
components: { FdsBlockInfo },
|
|
93
|
+
template: `
|
|
94
|
+
<FdsBlockInfo heading="About this component" icon="information" size="large">
|
|
95
|
+
<p>The Info Block can contain various types and amounts of content. We recommend using multiple instances of it for grouping related content.</p>
|
|
96
|
+
</FdsBlockInfo>
|
|
97
|
+
`,
|
|
98
|
+
}),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const Secondary: Story = {
|
|
102
|
+
render: () => ({
|
|
103
|
+
components: { FdsBlockInfo },
|
|
104
|
+
template: `
|
|
105
|
+
<FdsBlockInfo heading="About this component" icon="alert" color="blue">
|
|
106
|
+
<p>The Info Block can contain various types and amounts of content. We recommend using multiple instances of it for grouping related content.</p>
|
|
107
|
+
</FdsBlockInfo>
|
|
108
|
+
`,
|
|
109
|
+
}),
|
|
110
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
|
|
3
|
+
import { useHasSlot } from '@/composables/useHasSlots'
|
|
4
|
+
import { computed, type Slot } from 'vue'
|
|
5
|
+
import type { FdsBlockInfoProps } from './types'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<FdsBlockInfoProps>(), {
|
|
8
|
+
icon: undefined,
|
|
9
|
+
size: 'small',
|
|
10
|
+
heading: undefined,
|
|
11
|
+
dataTestid: undefined,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const hasSlot = useHasSlot()
|
|
15
|
+
|
|
16
|
+
const contentClasses = computed(() => [
|
|
17
|
+
'rounded-md bg-blue_t-100 mb-4',
|
|
18
|
+
!hasSlot.value && 'mb-0!',
|
|
19
|
+
props.size === 'small' && 'p-4',
|
|
20
|
+
props.size === 'small' && hasSlot.value && 'pb-6',
|
|
21
|
+
props.size === 'large' && props.icon && 'p-6',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const smallIconSize = 24
|
|
25
|
+
const largeIconSize = 48
|
|
26
|
+
|
|
27
|
+
const autoId = `fds-content-block-${Math.random().toString(36).slice(2, 9)}`
|
|
28
|
+
const contentBlockId = computed(() => props.id ?? autoId)
|
|
29
|
+
|
|
30
|
+
defineSlots<{
|
|
31
|
+
default?: Slot
|
|
32
|
+
header?: Slot
|
|
33
|
+
headerInfo?: Slot
|
|
34
|
+
}>()
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div :id="contentBlockId" :class="contentClasses">
|
|
39
|
+
<!-- Large icon layout: icon left, header + content right -->
|
|
40
|
+
<div v-if="size === 'large' && icon" class="flex items-start gap-4">
|
|
41
|
+
<FdsIcon :name="icon" :size="largeIconSize" class="fill-blue-500" />
|
|
42
|
+
<div class="flex-1">
|
|
43
|
+
<div v-if="heading" class="mb-1">
|
|
44
|
+
<header class="flex items-start justify-between gap-4">
|
|
45
|
+
<h3 class="m-0 text-base font-main font-bold tracking-wide">{{ heading }}</h3>
|
|
46
|
+
<div class="flex items-start gap-3">
|
|
47
|
+
<slot name="headerInfo" />
|
|
48
|
+
</div>
|
|
49
|
+
</header>
|
|
50
|
+
</div>
|
|
51
|
+
<div v-if="hasSlot" class="mb-0-last-child">
|
|
52
|
+
<slot />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Small icon or no icon: icon in header only, content below -->
|
|
58
|
+
<div v-else>
|
|
59
|
+
<div v-if="heading" :class="{ 'mb-4': hasSlot }">
|
|
60
|
+
<header class="flex items-start justify-between gap-4">
|
|
61
|
+
<div class="flex items-center gap-3">
|
|
62
|
+
<FdsIcon v-if="icon && size === 'small'" :name="icon" :size="smallIconSize" class="fill-blue-500" />
|
|
63
|
+
<h3 class="m-0 text-base font-main font-bold tracking-wide">{{ heading }}</h3>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="flex items-start gap-3">
|
|
66
|
+
<slot name="headerInfo" />
|
|
67
|
+
</div>
|
|
68
|
+
</header>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="mb-0-last-child">
|
|
71
|
+
<slot />
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* Vanilla CSS version (for consuming repos) */
|
|
2
|
+
.fds-interaction-block__inner__meta > * {
|
|
3
|
+
margin-bottom: 0 !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Vue :deep() version - only used when imported in Vue SFC */
|
|
7
|
+
:deep(.fds-interaction-block__inner__meta > *) {
|
|
8
|
+
margin-bottom: 0 !important;
|
|
9
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import icons from '@/assets/icons'
|
|
3
|
+
import FdsSticker from '@/components/FdsSticker/FdsSticker.vue'
|
|
4
|
+
import FdsBlockLink from './FdsBlockLink.vue'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof FdsBlockLink> = {
|
|
7
|
+
title: 'FDS/Blocks/FdsBlockLink',
|
|
8
|
+
component: FdsBlockLink,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
label: { control: { type: 'text' } },
|
|
12
|
+
arrow: { control: { type: 'boolean' } },
|
|
13
|
+
disabled: { control: { type: 'boolean' } },
|
|
14
|
+
download: { control: { type: 'text' } },
|
|
15
|
+
href: { control: { type: 'text' } },
|
|
16
|
+
target: { control: { type: 'select' }, options: ['', '_blank', '_self', '_parent', '_top'] },
|
|
17
|
+
rel: { control: { type: 'text' } },
|
|
18
|
+
icon: { control: { type: 'select' }, options: Object.keys(icons) },
|
|
19
|
+
interactive: { control: { type: 'boolean' } },
|
|
20
|
+
default: { control: { type: 'text' } },
|
|
21
|
+
as: { control: { type: 'select' }, options: ['button', 'a', 'router-link'] },
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
label: 'Enter some text',
|
|
25
|
+
arrow: false,
|
|
26
|
+
disabled: false,
|
|
27
|
+
download: undefined,
|
|
28
|
+
href: '/url',
|
|
29
|
+
target: undefined,
|
|
30
|
+
rel: undefined,
|
|
31
|
+
icon: undefined,
|
|
32
|
+
interactive: true,
|
|
33
|
+
default: 'Nothing will happen',
|
|
34
|
+
as: 'router-link',
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default meta
|
|
39
|
+
type Story = StoryObj<typeof meta>
|
|
40
|
+
|
|
41
|
+
const blockLinkTransform = (storyContext: {
|
|
42
|
+
args?: {
|
|
43
|
+
label?: string
|
|
44
|
+
icon?: string
|
|
45
|
+
arrow?: boolean
|
|
46
|
+
disabled?: boolean
|
|
47
|
+
href?: string
|
|
48
|
+
interactive?: boolean
|
|
49
|
+
default?: string
|
|
50
|
+
as?: 'router-link' | 'a' | 'button'
|
|
51
|
+
target?: string
|
|
52
|
+
rel?: string
|
|
53
|
+
download?: string
|
|
54
|
+
}
|
|
55
|
+
}) => {
|
|
56
|
+
const args = storyContext?.args || {}
|
|
57
|
+
const attrs = []
|
|
58
|
+
|
|
59
|
+
if (args.label) attrs.push(`label="${args.label}"`)
|
|
60
|
+
if (args.icon) attrs.push(`icon="${args.icon}"`)
|
|
61
|
+
if (args.arrow) attrs.push(':arrow="true"')
|
|
62
|
+
if (args.disabled) attrs.push(':disabled="true"')
|
|
63
|
+
if (args.href) attrs.push(`href="${args.href}"`)
|
|
64
|
+
if (!args.interactive) attrs.push(':interactive="false"')
|
|
65
|
+
if (args.as && args.as !== 'router-link') attrs.push(`as="${args.as}"`)
|
|
66
|
+
|
|
67
|
+
const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
|
|
68
|
+
const content = args.default ? `\n <p>${args.default}</p>\n` : ''
|
|
69
|
+
return `<FdsBlockLink${attrsStr}>${content}</FdsBlockLink>`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Default: Story = {
|
|
73
|
+
render: (args) => ({
|
|
74
|
+
components: { FdsBlockLink },
|
|
75
|
+
setup: () => ({ args }),
|
|
76
|
+
template: `<FdsBlockLink v-bind="args">{{ args.default }}</FdsBlockLink>`,
|
|
77
|
+
}),
|
|
78
|
+
parameters: {
|
|
79
|
+
docs: {
|
|
80
|
+
source: {
|
|
81
|
+
transform: blockLinkTransform,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
args: {
|
|
86
|
+
label: 'Enter some text',
|
|
87
|
+
default: 'Nothing will happen',
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const WithIcon: Story = {
|
|
92
|
+
render: () => ({
|
|
93
|
+
components: { FdsBlockLink },
|
|
94
|
+
template: `
|
|
95
|
+
<FdsBlockLink label="Settings" icon="settings" href="/settings">
|
|
96
|
+
<p>Click to go to settings page.</p>
|
|
97
|
+
</FdsBlockLink>
|
|
98
|
+
`,
|
|
99
|
+
}),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const WithArrow: Story = {
|
|
103
|
+
render: () => ({
|
|
104
|
+
components: { FdsBlockLink },
|
|
105
|
+
template: `
|
|
106
|
+
<FdsBlockLink label="Continue" arrow href="/next">
|
|
107
|
+
<p>This block has an arrow indicating it's clickable.</p>
|
|
108
|
+
</FdsBlockLink>
|
|
109
|
+
`,
|
|
110
|
+
}),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const WithSticker: Story = {
|
|
114
|
+
render: () => ({
|
|
115
|
+
components: { FdsBlockLink, FdsSticker },
|
|
116
|
+
template: `
|
|
117
|
+
<FdsBlockLink label="New Feature" href="/feature">
|
|
118
|
+
<p>Check out our latest feature!</p>
|
|
119
|
+
<template #sticker>
|
|
120
|
+
<FdsSticker variant="blue">New</FdsSticker>
|
|
121
|
+
</template>
|
|
122
|
+
</FdsBlockLink>
|
|
123
|
+
`,
|
|
124
|
+
}),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const Disabled: Story = {
|
|
128
|
+
render: () => ({
|
|
129
|
+
components: { FdsBlockLink },
|
|
130
|
+
template: `
|
|
131
|
+
<FdsBlockLink label="Disabled Block" disabled>
|
|
132
|
+
<p>This block is disabled and cannot be interacted with.</p>
|
|
133
|
+
</FdsBlockLink>
|
|
134
|
+
`,
|
|
135
|
+
}),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const NonInteractive: Story = {
|
|
139
|
+
render: () => ({
|
|
140
|
+
components: { FdsBlockLink },
|
|
141
|
+
template: `
|
|
142
|
+
<FdsBlockLink label="Information Only" :interactive="false">
|
|
143
|
+
<p>This block is not interactive - it's just for display.</p>
|
|
144
|
+
</FdsBlockLink>
|
|
145
|
+
`,
|
|
146
|
+
}),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const ExternalLink: Story = {
|
|
150
|
+
render: () => ({
|
|
151
|
+
components: { FdsBlockLink },
|
|
152
|
+
template: `
|
|
153
|
+
<FdsBlockLink
|
|
154
|
+
label="External Link"
|
|
155
|
+
href="https://example.com"
|
|
156
|
+
target="_blank"
|
|
157
|
+
rel="noreferrer noopener"
|
|
158
|
+
>
|
|
159
|
+
<p>This link opens in a new tab.</p>
|
|
160
|
+
</FdsBlockLink>
|
|
161
|
+
`,
|
|
162
|
+
}),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const Download: Story = {
|
|
166
|
+
render: () => ({
|
|
167
|
+
components: { FdsBlockLink },
|
|
168
|
+
template: `
|
|
169
|
+
<FdsBlockLink
|
|
170
|
+
label="Download File"
|
|
171
|
+
icon="download"
|
|
172
|
+
href="/files/document.pdf"
|
|
173
|
+
download="document.pdf"
|
|
174
|
+
>
|
|
175
|
+
<p>Click to download the PDF file.</p>
|
|
176
|
+
</FdsBlockLink>
|
|
177
|
+
`,
|
|
178
|
+
}),
|
|
179
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useHasSlot } from '@/composables/useHasSlots'
|
|
3
|
+
import { computed, useAttrs } from 'vue'
|
|
4
|
+
import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
|
|
5
|
+
import type { FdsInteractionBlockProps } from './types'
|
|
6
|
+
|
|
7
|
+
defineOptions({
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const attrs = useAttrs()
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<FdsInteractionBlockProps>(), {
|
|
14
|
+
arrow: false,
|
|
15
|
+
disabled: false,
|
|
16
|
+
download: undefined,
|
|
17
|
+
href: undefined,
|
|
18
|
+
target: undefined,
|
|
19
|
+
rel: undefined,
|
|
20
|
+
icon: undefined,
|
|
21
|
+
interactive: true,
|
|
22
|
+
as: 'router-link',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const hasSlot = useHasSlot()
|
|
26
|
+
const hasStickerSlot = useHasSlot('sticker')
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
(e: 'click', event: Event): void
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
const innerClasses = computed(() => [
|
|
33
|
+
props.disabled ? 'cursor-not-allowed shadow-none hover:border-transparent active:bg-transparent' : 'cursor-pointer',
|
|
34
|
+
!props.interactive && 'cursor-auto',
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
const slotWrapperClasses = computed(() => [
|
|
38
|
+
'flex flex-col sm:flex-row justify-between h-fit gap-y-2 gap-x-4',
|
|
39
|
+
!hasSlot.value && 'justify-end',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const headerClasses = computed(() => [
|
|
43
|
+
!props.arrow && !props.icon && 'mb-2',
|
|
44
|
+
!hasSlot.value && 'mb-0!',
|
|
45
|
+
props.icon && 'flex-1',
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
const contentClasses = computed(() => [props.icon ? 'items-start gap-3' : 'flex-col'])
|
|
49
|
+
const containerClasses = computed(() => [props.icon && 'flex-1'])
|
|
50
|
+
const iconClasses = computed(() => [props.disabled && 'text-gray-700'])
|
|
51
|
+
const arrowClasses = computed(() => [props.disabled && 'hidden'])
|
|
52
|
+
const headerTextClasses = computed(() => [props.disabled ? 'text-gray-700' : 'text-blue-600'])
|
|
53
|
+
|
|
54
|
+
function handleClick(event: Event) {
|
|
55
|
+
if (props.disabled || !props.interactive) {
|
|
56
|
+
event.preventDefault()
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
emit('click', event)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
63
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
handleClick(event)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const linkAttrs = computed(() => {
|
|
70
|
+
if (props.as === 'a') return { href: props.href }
|
|
71
|
+
if (props.as === 'router-link') return { to: props.href }
|
|
72
|
+
// Auto-detect: if href exists and no 'as' specified, use 'a'
|
|
73
|
+
if (props.href && !props.as) return { href: props.href }
|
|
74
|
+
return {}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const componentAttrs = computed(() => ({
|
|
78
|
+
...linkAttrs.value,
|
|
79
|
+
...attrs,
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
const componentType = computed(() => {
|
|
83
|
+
if (props.as) return props.as
|
|
84
|
+
return props.href ? 'a' : 'button'
|
|
85
|
+
})
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<component
|
|
90
|
+
:is="componentType"
|
|
91
|
+
v-bind="componentAttrs"
|
|
92
|
+
class="box-border appearance-none text-left w-full flex items-start bg-white p-[calc(1rem-2px)] mb-3 text-blue-600 shadow-lg shadow-blue-200 rounded-2xl no-underline border-2 border-transparent transition-all duration-200 hover:border-blue-600 hover:border-2 active:border-transparent active:shadow-none active:bg-blue_t-100 focus-visible:border-blue-500 focus-visible:border-dashed focus-visible:outline-0"
|
|
93
|
+
:class="innerClasses"
|
|
94
|
+
:target="componentType === 'a' ? target : undefined"
|
|
95
|
+
:rel="componentType === 'a' ? rel : undefined"
|
|
96
|
+
:download="componentType === 'a' ? download : undefined"
|
|
97
|
+
:disabled="componentType === 'button' ? disabled : undefined"
|
|
98
|
+
:aria-disabled="disabled"
|
|
99
|
+
@click="handleClick"
|
|
100
|
+
@keydown="handleKeydown"
|
|
101
|
+
>
|
|
102
|
+
<div
|
|
103
|
+
class="flex w-full flex-1"
|
|
104
|
+
:class="contentClasses"
|
|
105
|
+
>
|
|
106
|
+
<FdsIcon
|
|
107
|
+
v-if="icon"
|
|
108
|
+
:name="icon"
|
|
109
|
+
:size="24"
|
|
110
|
+
class="flex items-center justify-center w-6 h-6 fill-blue-500"
|
|
111
|
+
:class="iconClasses"
|
|
112
|
+
/>
|
|
113
|
+
<div
|
|
114
|
+
class="flex flex-col text-gray-700 font-normal text-base leading-6 tracking-normal"
|
|
115
|
+
:class="containerClasses"
|
|
116
|
+
>
|
|
117
|
+
<div
|
|
118
|
+
class="flex items-center gap-4"
|
|
119
|
+
:class="headerClasses"
|
|
120
|
+
>
|
|
121
|
+
<h3
|
|
122
|
+
class="flex-1 font-heading text-lg tracking-normal m-0 leading-md"
|
|
123
|
+
:class="headerTextClasses"
|
|
124
|
+
>
|
|
125
|
+
{{ label }}
|
|
126
|
+
</h3>
|
|
127
|
+
</div>
|
|
128
|
+
<div :class="slotWrapperClasses">
|
|
129
|
+
<div class="mb-0-last-child">
|
|
130
|
+
<slot />
|
|
131
|
+
</div>
|
|
132
|
+
<div
|
|
133
|
+
v-if="hasStickerSlot"
|
|
134
|
+
class="flex items-end"
|
|
135
|
+
>
|
|
136
|
+
<slot name="sticker" />
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<FdsIcon
|
|
142
|
+
v-if="arrow"
|
|
143
|
+
name="arrowRight"
|
|
144
|
+
:size="20"
|
|
145
|
+
class="ml-3 fill-blue-500"
|
|
146
|
+
:class="arrowClasses"
|
|
147
|
+
/>
|
|
148
|
+
</component>
|
|
149
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FdsIconName } from '@/components/FdsIcon/types'
|
|
2
|
+
|
|
3
|
+
export interface FdsInteractionBlockProps {
|
|
4
|
+
label: string
|
|
5
|
+
arrow?: boolean
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
download?: string
|
|
8
|
+
href?: string
|
|
9
|
+
target?: string
|
|
10
|
+
rel?: string
|
|
11
|
+
icon?: FdsIconName
|
|
12
|
+
interactive?: boolean
|
|
13
|
+
as?: 'button' | 'a' | 'router-link'
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FdsIconName } from '@/components/FdsIcon/types'
|
|
2
|
+
|
|
3
|
+
export interface FdsButtonBaseProps {
|
|
4
|
+
text: string
|
|
5
|
+
loading?: boolean
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
block?: boolean
|
|
8
|
+
state?: 'hover' | 'focus' | 'active' | undefined
|
|
9
|
+
icon?: FdsIconName | undefined
|
|
10
|
+
iconPos?: 'right' | 'left'
|
|
11
|
+
size?: 'sm' | 'md' | 'lg'
|
|
12
|
+
textSelection?: boolean
|
|
13
|
+
as?: 'button' | 'a' | 'router-link'
|
|
14
|
+
href?: string | undefined
|
|
15
|
+
target?: string
|
|
16
|
+
rel?: string
|
|
17
|
+
type?: 'button' | 'submit' | 'reset'
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import FdsButtonCopy from './FdsButtonCopy.vue'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof FdsButtonCopy> = {
|
|
5
|
+
title: 'FDS/Buttons/FdsButtonCopy',
|
|
6
|
+
component: FdsButtonCopy,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
source: {
|
|
11
|
+
transform: (_code: string, ctx: { args?: Record<string, unknown> }) => {
|
|
12
|
+
const a = (ctx.args || {}) as Record<string, unknown>
|
|
13
|
+
const attrs: string[] = []
|
|
14
|
+
const push = (s: string) => attrs.push(s)
|
|
15
|
+
if (typeof a.value === 'string') push(`value="${a.value}"`)
|
|
16
|
+
if (typeof a.targetId === 'string') push(`target-id="${a.targetId}"`)
|
|
17
|
+
if (typeof a.label === 'string') push(`label="${a.label}"`)
|
|
18
|
+
if (typeof a.copiedLabel === 'string') push(`copied-label="${a.copiedLabel}"`)
|
|
19
|
+
if (typeof a.timeoutMs === 'number' && a.timeoutMs !== 1500) push(`:timeout-ms="${a.timeoutMs}"`)
|
|
20
|
+
if (a.disabled) push(':disabled="true"')
|
|
21
|
+
const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
|
|
22
|
+
return `<FdsButtonCopy${attrsStr} />`
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
argTypes: {
|
|
28
|
+
value: { control: { type: 'text' } },
|
|
29
|
+
targetId: { control: { type: 'text' } },
|
|
30
|
+
label: { control: { type: 'text' } },
|
|
31
|
+
copiedLabel: { control: { type: 'text' } },
|
|
32
|
+
timeoutMs: { control: { type: 'number' } },
|
|
33
|
+
disabled: { control: { type: 'boolean' } },
|
|
34
|
+
},
|
|
35
|
+
args: {
|
|
36
|
+
value: 'Text att kopiera',
|
|
37
|
+
label: 'Kopiera',
|
|
38
|
+
copiedLabel: 'Kopierat!',
|
|
39
|
+
timeoutMs: 800,
|
|
40
|
+
disabled: false,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default meta
|
|
45
|
+
type Story = StoryObj<typeof meta>
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {
|
|
48
|
+
render: (args) => ({
|
|
49
|
+
components: { FdsButtonCopy },
|
|
50
|
+
setup: () => ({ args }),
|
|
51
|
+
template: '<FdsButtonCopy v-bind="args" />',
|
|
52
|
+
}),
|
|
53
|
+
}
|