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,87 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from 'vue'
3
+ import FdsButtonMinor from '@/components/Buttons/FdsButtonMinor/FdsButtonMinor.vue'
4
+ import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
5
+ import type { FdsCopyButtonProps } from './types'
6
+
7
+ const props = withDefaults(defineProps<FdsCopyButtonProps>(), {
8
+ value: '',
9
+ label: 'Kopiera',
10
+ copiedLabel: 'Kopierat!',
11
+ timeoutMs: 800,
12
+ disabled: false,
13
+ })
14
+
15
+ const isCopied = ref(false)
16
+ let resetTimer: ReturnType<typeof setTimeout> | null = null
17
+
18
+ async function copyToClipboard(text: string) {
19
+ try {
20
+ if (navigator.clipboard && navigator.clipboard.writeText) {
21
+ await navigator.clipboard.writeText(text)
22
+ return true
23
+ }
24
+ } catch {
25
+ // ignore and try fallback
26
+ }
27
+ const textarea = document.createElement('textarea')
28
+ textarea.value = text
29
+ textarea.style.position = 'fixed'
30
+ textarea.style.opacity = '0'
31
+ document.body.appendChild(textarea)
32
+ textarea.focus()
33
+ textarea.select()
34
+ let ok = false
35
+ try {
36
+ ok = document.execCommand('copy')
37
+ } catch {
38
+ ok = false
39
+ }
40
+ document.body.removeChild(textarea)
41
+ return ok
42
+ }
43
+
44
+ async function onClick() {
45
+ if (props.disabled) return
46
+ let textToCopy = props.value
47
+ if (props.targetId) {
48
+ const el = document.getElementById(props.targetId)
49
+ if (el) {
50
+ textToCopy = (el as HTMLElement).innerText || (el as HTMLElement).textContent || ''
51
+ }
52
+ }
53
+ if (!textToCopy) return
54
+ const ok = await copyToClipboard(textToCopy)
55
+ if (!ok) return
56
+ isCopied.value = true
57
+ if (resetTimer) clearTimeout(resetTimer)
58
+ resetTimer = setTimeout(() => (isCopied.value = false), props.timeoutMs)
59
+ }
60
+
61
+ onMounted(() => {
62
+ if (resetTimer) {
63
+ clearTimeout(resetTimer)
64
+ resetTimer = null
65
+ }
66
+ })
67
+
68
+ defineEmits<{ (e: 'click'): void }>()
69
+ </script>
70
+
71
+ <template>
72
+ <div
73
+ v-if="isCopied"
74
+ class="flex items-center gap-2 h-7"
75
+ >
76
+ <FdsIcon name="bigSuccess" />
77
+ <span>{{ props.copiedLabel }}</span>
78
+ </div>
79
+ <FdsButtonMinor
80
+ v-else
81
+ type="button"
82
+ :disabled="props.disabled"
83
+ icon="copy"
84
+ :text="isCopied ? props.copiedLabel : props.label"
85
+ @click="onClick"
86
+ />
87
+ </template>
@@ -0,0 +1,8 @@
1
+ export interface FdsCopyButtonProps {
2
+ value?: string
3
+ targetId?: string
4
+ label?: string
5
+ copiedLabel?: string
6
+ timeoutMs?: number
7
+ disabled?: boolean
8
+ }
@@ -0,0 +1,111 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import FdsButtonDownload from './FdsButtonDownload.vue'
3
+
4
+ const meta: Meta<typeof FdsButtonDownload> = {
5
+ title: 'FDS/Buttons/FdsButtonDownload',
6
+ component: FdsButtonDownload,
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.text === 'string') push(`text="${a.text}"`)
16
+ if (typeof a.iconPos === 'string' && a.iconPos !== 'left') push(`icon-pos="${a.iconPos}"`)
17
+ if (a.loading) push(':loading="true"')
18
+ if (a.disabled) push(':disabled="true"')
19
+ if (typeof a.href === 'string' && a.href) push(`href="${a.href}"`)
20
+ const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
21
+ return `<FdsButtonDownload${attrsStr} />`
22
+ },
23
+ },
24
+ },
25
+ },
26
+ argTypes: {
27
+ text: {
28
+ control: 'text',
29
+ description: 'Button text',
30
+ },
31
+ href: {
32
+ control: 'text',
33
+ description: 'Link URL',
34
+ },
35
+ loading: {
36
+ control: 'boolean',
37
+ description: 'Show loading spinner',
38
+ },
39
+ disabled: {
40
+ control: 'boolean',
41
+ description: 'Disable button',
42
+ },
43
+ },
44
+ args: {
45
+ text: 'Download',
46
+ href: '#',
47
+ loading: false,
48
+ disabled: false,
49
+ },
50
+ }
51
+
52
+ export default meta
53
+ type Story = StoryObj<typeof meta>
54
+
55
+ export const Default: Story = {
56
+ render: (args) => ({
57
+ components: { FdsButtonDownload },
58
+ setup() {
59
+ return { args }
60
+ },
61
+ template: '<FdsButtonDownload v-bind="args" />',
62
+ }),
63
+ args: {
64
+ text: 'Download',
65
+ href: '#',
66
+ },
67
+ }
68
+
69
+ export const WithText: Story = {
70
+ render: (args) => ({
71
+ components: { FdsButtonDownload },
72
+ setup() {
73
+ return { args }
74
+ },
75
+ template: '<FdsButtonDownload v-bind="args" />',
76
+ }),
77
+ args: {
78
+ text: 'Download file',
79
+ href: '/download.pdf',
80
+ },
81
+ }
82
+
83
+ export const Loading: Story = {
84
+ render: (args) => ({
85
+ components: { FdsButtonDownload },
86
+ setup() {
87
+ return { args }
88
+ },
89
+ template: '<FdsButtonDownload v-bind="args" />',
90
+ }),
91
+ args: {
92
+ text: 'Download',
93
+ href: '#',
94
+ loading: true,
95
+ },
96
+ }
97
+
98
+ export const Disabled: Story = {
99
+ render: (args) => ({
100
+ components: { FdsButtonDownload },
101
+ setup() {
102
+ return { args }
103
+ },
104
+ template: '<FdsButtonDownload v-bind="args" />',
105
+ }),
106
+ args: {
107
+ text: 'Download',
108
+ href: '#',
109
+ disabled: true,
110
+ },
111
+ }
@@ -0,0 +1,187 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
4
+ import type { FdsButtonBaseProps } from '@/components/Buttons/ButtonBaseProps'
5
+
6
+ defineOptions({
7
+ inheritAttrs: false,
8
+ })
9
+
10
+ interface DownloadOptions {
11
+ token: string
12
+ accept?: string
13
+ headerAuthKey?: string
14
+ headerAuthValuePrefix?: string
15
+ errorHandler?: (error: unknown) => void
16
+ onFinishCallback?: () => void
17
+ }
18
+
19
+ type FdsButtonDownloadProps = Omit<FdsButtonBaseProps, 'as' | 'icon'> & {
20
+ downloadOptions?: DownloadOptions
21
+ }
22
+
23
+ const props = withDefaults(defineProps<FdsButtonDownloadProps>(), {
24
+ loading: false,
25
+ disabled: false,
26
+ iconPos: 'left',
27
+ href: undefined,
28
+ downloadOptions: undefined,
29
+ })
30
+
31
+ const emit = defineEmits<{
32
+ (e: 'click', ev: MouseEvent): void
33
+ }>()
34
+
35
+ const isDownloading = ref(false)
36
+
37
+ const rootClasses = computed(() => [
38
+ 'inline-block transition-opacity duration-200',
39
+ props.disabled && 'opacity-20 pointer-events-none',
40
+ ])
41
+
42
+ const elBase = computed(() => [
43
+ 'box-border appearance-none w-full inline-flex items-center justify-center shadow-none p-0.5 text-base',
44
+ 'select-none m-0 rounded-md align-middle whitespace-nowrap no-underline',
45
+ 'transition-[box-shadow,border-color,background-color] duration-200 font-main text-base',
46
+ 'leading-5 tracking-normal',
47
+ ])
48
+
49
+ const stateClasses = computed(() => [
50
+ 'focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4',
51
+ 'focus-visible:outline-blue-500 hover:bg-blue_t-100 active:bg-blue_t-200',
52
+ ])
53
+
54
+ const iconOrderClasses = computed(() => (props.iconPos === 'right' ? 'order-2 ml-2 mr-0' : 'mr-2'))
55
+
56
+ const buttonClasses = computed(() => [
57
+ elBase.value,
58
+ stateClasses.value,
59
+ 'border-0 font-normal bg-transparent',
60
+ isDownloading.value ? 'cursor-not-allowed text-gray-500' : 'cursor-pointer text-blue-600',
61
+ ])
62
+
63
+ const iconFillClass = computed(() => (isDownloading.value ? 'fill-gray-500' : 'fill-blue-500'))
64
+
65
+ async function handleDownload(ev: MouseEvent) {
66
+ if (props.disabled || props.loading || isDownloading.value) {
67
+ ev.preventDefault()
68
+ return
69
+ }
70
+
71
+ // If no downloadOptions, just emit click and let default link behavior handle it
72
+ if (!props.downloadOptions || !props.href) {
73
+ emit('click', ev)
74
+ return
75
+ }
76
+
77
+ ev.preventDefault()
78
+
79
+ isDownloading.value = true
80
+
81
+ const options = props.downloadOptions
82
+ const authHeader: Record<string, string> = {}
83
+ authHeader[options.headerAuthKey || 'Authorization'] = `${options.headerAuthValuePrefix || 'Bearer '}${options.token}`
84
+
85
+ if (options.accept) {
86
+ authHeader.Accept = options.accept
87
+ }
88
+
89
+ try {
90
+ const response = await fetch(props.href, {
91
+ method: 'GET',
92
+ headers: authHeader,
93
+ })
94
+
95
+ if (!response.ok) {
96
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`)
97
+ }
98
+
99
+ // Get blob from response
100
+ const blob = await response.blob()
101
+ const url = URL.createObjectURL(blob)
102
+ const link = document.createElement('a')
103
+ link.href = url
104
+
105
+ // Try to get filename from Content-Disposition header
106
+ const contentDisposition = response.headers.get('content-disposition')
107
+ let fileName = props.href.substring(props.href.lastIndexOf('/') + 1)
108
+
109
+ if (contentDisposition) {
110
+ const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
111
+ if (fileNameMatch && fileNameMatch[1]) {
112
+ fileName = fileNameMatch[1].replace(/['"]/g, '')
113
+ // Decode URL encoding
114
+ fileName = fileName.replace(/\+/g, '%20')
115
+ fileName = decodeURIComponent(fileName)
116
+ }
117
+ }
118
+
119
+ link.setAttribute('download', fileName)
120
+ document.body.appendChild(link)
121
+
122
+ link.dispatchEvent(
123
+ new MouseEvent('click', {
124
+ bubbles: true,
125
+ cancelable: true,
126
+ view: window,
127
+ }),
128
+ )
129
+
130
+ setTimeout(() => {
131
+ document.body.removeChild(link)
132
+ URL.revokeObjectURL(url)
133
+ }, 100)
134
+
135
+ if (options.onFinishCallback) {
136
+ options.onFinishCallback()
137
+ }
138
+ } catch (error) {
139
+ if (options.errorHandler) {
140
+ options.errorHandler(error)
141
+ } else {
142
+ throw error
143
+ }
144
+ } finally {
145
+ isDownloading.value = false
146
+ }
147
+ }
148
+
149
+ function onClick(ev: MouseEvent) {
150
+ if (props.downloadOptions) {
151
+ handleDownload(ev)
152
+ } else {
153
+ if (props.disabled || props.loading) {
154
+ ev.preventDefault()
155
+ return
156
+ }
157
+ emit('click', ev)
158
+ }
159
+ }
160
+ </script>
161
+
162
+ <template>
163
+ <div
164
+ :class="rootClasses"
165
+ :aria-disabled="disabled ? 'true' : undefined"
166
+ >
167
+ <button
168
+ v-bind="$attrs"
169
+ :class="buttonClasses"
170
+ :disabled="disabled || loading || isDownloading"
171
+ type="button"
172
+ @click="onClick"
173
+ >
174
+ <span
175
+ :class="iconOrderClasses"
176
+ aria-hidden="true"
177
+ >
178
+ <FdsIcon
179
+ :class="iconFillClass"
180
+ name="download"
181
+ :size="24"
182
+ />
183
+ </span>
184
+ {{ text }}
185
+ </button>
186
+ </div>
187
+ </template>
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import FdsButtonIcon from './FdsButtonIcon.vue'
3
+ import icons from '@/assets/icons'
4
+
5
+ const meta: Meta<typeof FdsButtonIcon> = {
6
+ title: 'FDS/Buttons/FdsButtonIcon',
7
+ component: FdsButtonIcon,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ docs: {
11
+ source: {
12
+ transform: (_code: string, ctx: { args?: Record<string, unknown> }) => {
13
+ const a = (ctx.args || {}) as Record<string, unknown>
14
+ const attrs: string[] = []
15
+ const push = (s: string) => attrs.push(s)
16
+ if (typeof a.icon === 'string') push(`icon="${a.icon}"`)
17
+ if (typeof a.size === 'number' && a.size !== 24) push(`size="${a.size}"`)
18
+ if (a.loading) push(':loading="true"')
19
+ if (a.disabled) push(':disabled="true"')
20
+ if (typeof a.type === 'string' && a.type !== 'button') push(`type="${a.type}"`)
21
+ if (typeof a.ariaLabel === 'string') push(`aria-label="${a.ariaLabel}"`)
22
+ const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
23
+ return `<FdsButtonIcon${attrsStr} />`
24
+ },
25
+ },
26
+ },
27
+ },
28
+ argTypes: {
29
+ icon: { control: { type: 'select' }, options: Object.keys(icons) },
30
+ disabled: { control: { type: 'boolean' } },
31
+ loading: { control: { type: 'boolean' } },
32
+ type: { control: { type: 'select' }, options: ['button', 'submit', 'reset'] },
33
+ ariaLabel: { control: { type: 'text' } },
34
+ size: { control: { type: 'number' } },
35
+ },
36
+ args: {
37
+ icon: 'information',
38
+ size: 24,
39
+ disabled: false,
40
+ loading: false,
41
+ type: 'button',
42
+ ariaLabel: 'More options',
43
+ },
44
+ }
45
+
46
+ export default meta
47
+ type Story = StoryObj<typeof meta>
48
+
49
+ export const Default: Story = {
50
+ render: (args) => ({
51
+ components: { FdsButtonIcon },
52
+ setup: () => ({ args }),
53
+ template: '<FdsButtonIcon v-bind="args" />',
54
+ }),
55
+ }
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
4
+ import type { FdsIconButtonProps } from './types'
5
+
6
+ const props = withDefaults(defineProps<FdsIconButtonProps>(), {
7
+ size: 24,
8
+ disabled: false,
9
+ loading: false,
10
+ type: 'button',
11
+ ariaLabel: undefined,
12
+ })
13
+
14
+ // Remove padding of button (4px) from the size to get the icon size
15
+ const iconSize = computed(() => props.size - 4)
16
+
17
+ const emit = defineEmits<{
18
+ (e: 'click', ev: MouseEvent): void
19
+ }>()
20
+
21
+ const buttonClasses = computed(() => [
22
+ 'inline-flex items-center justify-center rounded-md p-[2px] max-w-[48px] max-h-[48px] transition-colors duration-200 fill-blue-500 hover:bg-blue_t-100 active:bg-blue_t-200 focus-visible:outline-dashed focus-visible:outline-2 outline-blue-500',
23
+ props.disabled ? 'opacity-20 cursor-not-allowed' : 'cursor-pointer',
24
+ ])
25
+
26
+ function onClick(ev: MouseEvent) {
27
+ if (props.disabled || props.loading) {
28
+ ev.preventDefault()
29
+ return
30
+ }
31
+ emit('click', ev)
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <button
37
+ :id="id"
38
+ :type="type"
39
+ :disabled="disabled"
40
+ :aria-label="ariaLabel"
41
+ :aria-disabled="ariaDisabled"
42
+ :class="buttonClasses"
43
+ @click="onClick"
44
+ >
45
+ <FdsIcon
46
+ v-if="!loading"
47
+ :size="iconSize"
48
+ :name="icon"
49
+ />
50
+ <FdsIcon
51
+ v-else
52
+ :size="iconSize"
53
+ name="spinner"
54
+ class="animate-spin"
55
+ />
56
+ </button>
57
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { FdsIconName } from '@/components/FdsIcon/types'
2
+
3
+ export interface FdsIconButtonProps {
4
+ icon: FdsIconName
5
+ size?: number
6
+ disabled?: boolean
7
+ loading?: boolean
8
+ type?: 'button' | 'submit' | 'reset'
9
+ ariaLabel?: string
10
+ id?: string
11
+ ariaDisabled?: boolean
12
+ }
@@ -0,0 +1,68 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import FdsButtonMinor from './FdsButtonMinor.vue'
3
+ import icons from '@/assets/icons'
4
+
5
+ const meta: Meta<typeof FdsButtonMinor> = {
6
+ title: 'FDS/Buttons/FdsButtonMinor',
7
+ component: FdsButtonMinor,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ docs: {
11
+ source: {
12
+ transform: (_code: string, ctx: { args?: Record<string, unknown> }) => {
13
+ const a = (ctx.args || {}) as Record<string, unknown>
14
+ const attrs: string[] = []
15
+ const push = (s: string) => attrs.push(s)
16
+ if (typeof a.text === 'string') push(`text="${a.text}"`)
17
+ if (typeof a.icon === 'string') push(`icon="${a.icon}"`)
18
+ if (typeof a.iconPos === 'string' && a.iconPos !== 'left') push(`icon-pos="${a.iconPos}"`)
19
+ if (a.invert) push(':invert="true"')
20
+ if (a.loading) push(':loading="true"')
21
+ if (a.disabled) push(':disabled="true"')
22
+ if (a.block) push(':block="true"')
23
+ if (typeof a.as === 'string' && a.as !== 'button') push(`as="${a.as}"`)
24
+ if (typeof a.type === 'string' && a.type !== 'button') push(`type="${a.type}"`)
25
+ if (typeof a.href === 'string' && a.href) push(`href="${a.href}"`)
26
+ const attrsStr = attrs.length ? ` ${attrs.join(' ')}` : ''
27
+ return `<FdsButtonMinor${attrsStr} />`
28
+ },
29
+ },
30
+ },
31
+ },
32
+ argTypes: {
33
+ text: { control: { type: 'text' } },
34
+ icon: { control: { type: 'select' }, options: Object.keys(icons) },
35
+ iconPos: { control: { type: 'inline-radio' }, options: ['left', 'right'] },
36
+ invert: { control: { type: 'boolean' } },
37
+ loading: { control: { type: 'boolean' } },
38
+ disabled: { control: { type: 'boolean' } },
39
+ block: { control: { type: 'boolean' } },
40
+ textSelection: { control: { type: 'boolean' } },
41
+ as: { control: { type: 'select' }, options: ['button', 'a', 'router-link'] },
42
+ type: { control: { type: 'select' }, options: ['button', 'submit', 'reset'] },
43
+ href: { control: { type: 'text' } },
44
+ },
45
+ args: {
46
+ text: 'Minor',
47
+ invert: false,
48
+ loading: false,
49
+ disabled: false,
50
+ block: false,
51
+ textSelection: false,
52
+ icon: undefined,
53
+ iconPos: 'left',
54
+ as: 'button',
55
+ type: 'button',
56
+ },
57
+ }
58
+
59
+ export default meta
60
+ type Story = StoryObj<typeof meta>
61
+
62
+ export const Default: Story = {
63
+ render: (args) => ({
64
+ components: { FdsButtonMinor },
65
+ setup: () => ({ args }),
66
+ template: '<FdsButtonMinor v-bind="args" />',
67
+ }),
68
+ }