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.
Files changed (121) hide show
  1. package/components.d.ts +8 -0
  2. package/configs/tsconfig.base.json +2 -1
  3. package/dist/fds-vue-core.cjs.js +35 -15
  4. package/dist/fds-vue-core.cjs.js.map +1 -1
  5. package/dist/fds-vue-core.es.js +35 -15
  6. package/dist/fds-vue-core.es.js.map +1 -1
  7. package/dist/global-components.d.ts +35 -33
  8. package/package.json +23 -21
  9. package/src/.DS_Store +0 -0
  10. package/src/App.vue +133 -0
  11. package/src/apply.css +60 -0
  12. package/src/assets/icons.ts +517 -0
  13. package/src/components/Blocks/FdsBlockAlert/FdsBlockAlert.stories.ts +94 -0
  14. package/src/components/Blocks/FdsBlockAlert/FdsBlockAlert.vue +112 -0
  15. package/src/components/Blocks/FdsBlockAlert/types.ts +12 -0
  16. package/src/components/Blocks/FdsBlockContent/FdsBlockContent.stories.ts +110 -0
  17. package/src/components/Blocks/FdsBlockContent/FdsBlockContent.vue +66 -0
  18. package/src/components/Blocks/FdsBlockContent/types.ts +6 -0
  19. package/src/components/Blocks/FdsBlockExpander/FdsBlockExpander.stories.ts +123 -0
  20. package/src/components/Blocks/FdsBlockExpander/FdsBlockExpander.vue +87 -0
  21. package/src/components/Blocks/FdsBlockExpander/types.ts +8 -0
  22. package/src/components/Blocks/FdsBlockInfo/FdsBlockInfo.stories.ts +110 -0
  23. package/src/components/Blocks/FdsBlockInfo/FdsBlockInfo.vue +75 -0
  24. package/src/components/Blocks/FdsBlockInfo/types.ts +9 -0
  25. package/src/components/Blocks/FdsBlockLink/FdsBlockLink.css +9 -0
  26. package/src/components/Blocks/FdsBlockLink/FdsBlockLink.stories.ts +179 -0
  27. package/src/components/Blocks/FdsBlockLink/FdsBlockLink.vue +149 -0
  28. package/src/components/Blocks/FdsBlockLink/types.ts +14 -0
  29. package/src/components/Buttons/ButtonBaseProps.ts +18 -0
  30. package/src/components/Buttons/FdsButtonCopy/FdsButtonCopy.stories.ts +53 -0
  31. package/src/components/Buttons/FdsButtonCopy/FdsButtonCopy.vue +87 -0
  32. package/src/components/Buttons/FdsButtonCopy/types.ts +8 -0
  33. package/src/components/Buttons/FdsButtonDownload/FdsButtonDownload.stories.ts +111 -0
  34. package/src/components/Buttons/FdsButtonDownload/FdsButtonDownload.vue +187 -0
  35. package/src/components/Buttons/FdsButtonIcon/FdsButtonIcon.stories.ts +55 -0
  36. package/src/components/Buttons/FdsButtonIcon/FdsButtonIcon.vue +57 -0
  37. package/src/components/Buttons/FdsButtonIcon/types.ts +12 -0
  38. package/src/components/Buttons/FdsButtonMinor/FdsButtonMinor.stories.ts +68 -0
  39. package/src/components/Buttons/FdsButtonMinor/FdsButtonMinor.vue +126 -0
  40. package/src/components/Buttons/FdsButtonPrimary/FdsButtonPrimary.stories.ts +86 -0
  41. package/src/components/Buttons/FdsButtonPrimary/FdsButtonPrimary.vue +107 -0
  42. package/src/components/Buttons/FdsButtonSecondary/FdsButtonSecondary.stories.ts +68 -0
  43. package/src/components/Buttons/FdsButtonSecondary/FdsButtonSecondary.vue +107 -0
  44. package/src/components/FdsIcon/FdsIcon.stories.ts +69 -0
  45. package/src/components/FdsIcon/FdsIcon.vue +34 -0
  46. package/src/components/FdsIcon/types.ts +9 -0
  47. package/src/components/FdsModal/FdsModal.stories.ts +241 -0
  48. package/src/components/FdsModal/FdsModal.vue +269 -0
  49. package/src/components/FdsModal/types.ts +12 -0
  50. package/src/components/FdsPagination/FdsPagination.stories.ts +109 -0
  51. package/src/components/FdsPagination/FdsPagination.vue +193 -0
  52. package/src/components/FdsPagination/types.ts +6 -0
  53. package/src/components/FdsSearchSelect/FdsSearchSelect.stories.ts +428 -0
  54. package/src/components/FdsSearchSelect/FdsSearchSelect.vue +621 -0
  55. package/src/components/FdsSearchSelect/types.ts +25 -0
  56. package/src/components/FdsSpinner/FdsSpinner.stories.ts +31 -0
  57. package/src/components/FdsSpinner/FdsSpinner.vue +90 -0
  58. package/src/components/FdsSpinner/types.ts +6 -0
  59. package/src/components/FdsSticker/FdsSticker.stories.ts +148 -0
  60. package/src/components/FdsSticker/FdsSticker.vue +44 -0
  61. package/src/components/FdsSticker/types.ts +4 -0
  62. package/src/components/FdsTreeView/FdsTreeView.stories.ts +136 -0
  63. package/src/components/FdsTreeView/FdsTreeView.vue +162 -0
  64. package/src/components/FdsTreeView/TreeNode.vue +383 -0
  65. package/src/components/FdsTreeView/types.ts +141 -0
  66. package/src/components/FdsTreeView/useTreeState.ts +607 -0
  67. package/src/components/FdsTreeView/utils.ts +69 -0
  68. package/src/components/FdsTruncatedText/FdsTruncatedText.stories.ts +78 -0
  69. package/src/components/FdsTruncatedText/FdsTruncatedText.vue +85 -0
  70. package/src/components/FdsTruncatedText/types.ts +6 -0
  71. package/src/components/Form/FdsCheckbox/FdsCheckbox.stories.ts +275 -0
  72. package/src/components/Form/FdsCheckbox/FdsCheckbox.vue +155 -0
  73. package/src/components/Form/FdsCheckbox/types.ts +10 -0
  74. package/src/components/Form/FdsInput/FdsInput.stories.ts +319 -0
  75. package/src/components/Form/FdsInput/FdsInput.vue +233 -0
  76. package/src/components/Form/FdsInput/types.ts +25 -0
  77. package/src/components/Form/FdsRadio/FdsRadio.stories.ts +63 -0
  78. package/src/components/Form/FdsRadio/FdsRadio.vue +88 -0
  79. package/src/components/Form/FdsRadio/types.ts +12 -0
  80. package/src/components/Form/FdsSelect/FdsSelect.stories.ts +78 -0
  81. package/src/components/Form/FdsSelect/FdsSelect.vue +136 -0
  82. package/src/components/Form/FdsSelect/types.ts +13 -0
  83. package/src/components/Form/FdsTextarea/FdsTextarea.stories.ts +52 -0
  84. package/src/components/Form/FdsTextarea/FdsTextarea.vue +110 -0
  85. package/src/components/Form/FdsTextarea/types.ts +12 -0
  86. package/src/components/Table/FdsTable/FdsTable.stories.ts +221 -0
  87. package/src/components/Table/FdsTable/FdsTable.vue +25 -0
  88. package/src/components/Table/FdsTable/types.ts +4 -0
  89. package/src/components/Table/FdsTableHead/FdsTableHead.stories.ts +151 -0
  90. package/src/components/Table/FdsTableHead/FdsTableHead.vue +54 -0
  91. package/src/components/Table/FdsTableHead/types.ts +5 -0
  92. package/src/components/Tabs/FdsTabs/FdsTabs.stories.ts +247 -0
  93. package/src/components/Tabs/FdsTabs/FdsTabs.vue +27 -0
  94. package/src/components/Tabs/FdsTabs/types.ts +4 -0
  95. package/src/components/Tabs/FdsTabsItem/FdsTabsItem.vue +125 -0
  96. package/src/components/Tabs/FdsTabsItem/types.ts +16 -0
  97. package/src/components/Typography/FdsHeading/FdsHeading.stories.ts +93 -0
  98. package/src/components/Typography/FdsHeading/FdsHeading.vue +51 -0
  99. package/src/components/Typography/FdsHeading/types.ts +5 -0
  100. package/src/components/Typography/FdsListHeading/FdsListHeading.stories.ts +58 -0
  101. package/src/components/Typography/FdsListHeading/FdsListHeading.vue +62 -0
  102. package/src/components/Typography/FdsListHeading/types.ts +8 -0
  103. package/src/components/Typography/FdsSeparator/FdsSeparator.stories.ts +31 -0
  104. package/src/components/Typography/FdsSeparator/FdsSeparator.vue +5 -0
  105. package/src/components/Typography/FdsText/FdsText.stories.ts +66 -0
  106. package/src/components/Typography/FdsText/FdsText.vue +28 -0
  107. package/src/components/Typography/FdsText/types.ts +3 -0
  108. package/src/composables/useBoldQuery.ts +29 -0
  109. package/src/composables/useElementFinalSize.ts +24 -0
  110. package/src/composables/useHasSlots.ts +17 -0
  111. package/src/composables/useIsPid.ts +48 -0
  112. package/src/docs/Start/Start.mdx +12 -0
  113. package/src/docs/Usage.md +117 -0
  114. package/src/fonts.css +28 -0
  115. package/src/global-components.ts +75 -0
  116. package/src/index.ts +180 -0
  117. package/src/main.ts +7 -0
  118. package/src/slot-styles.css +93 -0
  119. package/src/style.css +89 -0
  120. package/src/tokens.css +252 -0
  121. 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
+ import type { FdsIconName } from '@/components/FdsIcon/types'
2
+
3
+ export interface FdsBlockInfoProps {
4
+ heading?: string
5
+ icon?: FdsIconName
6
+ size?: 'large' | 'small'
7
+ id?: string
8
+ dataTestid?: string
9
+ }
@@ -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
+ }