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,126 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FdsButtonBaseProps } from '@/components/Buttons/ButtonBaseProps'
|
|
3
|
+
import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
|
|
4
|
+
import FdsSpinner from '@/components/FdsSpinner/FdsSpinner.vue'
|
|
5
|
+
import { computed, useAttrs } from 'vue'
|
|
6
|
+
|
|
7
|
+
defineOptions({
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const attrs = useAttrs()
|
|
12
|
+
|
|
13
|
+
interface FdsButtonMinorProps extends FdsButtonBaseProps {
|
|
14
|
+
invert?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<FdsButtonMinorProps>(), {
|
|
18
|
+
loading: false,
|
|
19
|
+
disabled: false,
|
|
20
|
+
block: false,
|
|
21
|
+
state: undefined,
|
|
22
|
+
icon: undefined,
|
|
23
|
+
iconPos: 'left',
|
|
24
|
+
textSelection: false,
|
|
25
|
+
as: 'button',
|
|
26
|
+
href: undefined,
|
|
27
|
+
target: undefined,
|
|
28
|
+
rel: undefined,
|
|
29
|
+
type: 'button',
|
|
30
|
+
invert: false,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
(e: 'click', ev: MouseEvent): void
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
const rootClasses = computed(() => [
|
|
38
|
+
'inline-block transition-opacity duration-200',
|
|
39
|
+
props.disabled && 'opacity-20 pointer-events-none',
|
|
40
|
+
props.block && 'block w-full',
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
const elBase =
|
|
44
|
+
'box-border appearance-none inline-flex items-start justify-center w-fit cursor-pointer shadow-none p-0.5 text-base select-none m-0 rounded-md text-left align-start no-underline transition-[box-shadow,border-color,background-color] duration-200 font-main font-bold text-base leading-5 tracking-normal focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500'
|
|
45
|
+
|
|
46
|
+
const variantClasses = computed(() =>
|
|
47
|
+
props.invert
|
|
48
|
+
? 'border-0 font-normal text-white bg-transparent focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white hover:bg-white-100 active:bg-white-200'
|
|
49
|
+
: 'border-0 font-normal text-blue-600 bg-transparent focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500 hover:bg-blue_t-100 active:bg-blue_t-200',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const blockElClasses = computed(() => props.block && 'w-full justify-center')
|
|
53
|
+
|
|
54
|
+
const iconOrderClasses = computed(() =>
|
|
55
|
+
props.icon && props.iconPos === 'right' ? 'order-2 ml-2 mr-0' : props.icon && 'mr-2',
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const buttonClasses = computed(() => [
|
|
59
|
+
elBase,
|
|
60
|
+
variantClasses.value,
|
|
61
|
+
blockElClasses.value,
|
|
62
|
+
props.textSelection && 'select-text',
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
const iconFillClass = computed(() => (props.invert ? 'fill-white' : 'fill-blue-500'))
|
|
66
|
+
|
|
67
|
+
function onClick(ev: MouseEvent) {
|
|
68
|
+
if (props.disabled || props.loading) {
|
|
69
|
+
ev.preventDefault()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
emit('click', ev)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const linkAttrs = computed(() => {
|
|
76
|
+
if (props.as === 'a') return { href: props.href }
|
|
77
|
+
if (props.as === 'router-link') return { to: props.href }
|
|
78
|
+
return {}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const componentAttrs = computed(() => ({
|
|
82
|
+
...linkAttrs.value,
|
|
83
|
+
...attrs,
|
|
84
|
+
}))
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<div
|
|
89
|
+
:class="rootClasses"
|
|
90
|
+
:aria-disabled="disabled ? 'true' : undefined"
|
|
91
|
+
>
|
|
92
|
+
<component
|
|
93
|
+
:is="as"
|
|
94
|
+
v-bind="componentAttrs"
|
|
95
|
+
:type="as === 'button' ? type : undefined"
|
|
96
|
+
:disabled="as === 'button' ? disabled : undefined"
|
|
97
|
+
:target="as === 'a' ? target : undefined"
|
|
98
|
+
:rel="as === 'a' ? rel : undefined"
|
|
99
|
+
:class="buttonClasses"
|
|
100
|
+
@click="onClick"
|
|
101
|
+
>
|
|
102
|
+
<template v-if="loading">
|
|
103
|
+
<FdsSpinner
|
|
104
|
+
class="mr-2"
|
|
105
|
+
size="24px"
|
|
106
|
+
color="inherit"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
109
|
+
<template v-else-if="icon">
|
|
110
|
+
<span
|
|
111
|
+
:class="iconOrderClasses"
|
|
112
|
+
aria-hidden="true"
|
|
113
|
+
>
|
|
114
|
+
<FdsIcon
|
|
115
|
+
:class="iconFillClass"
|
|
116
|
+
:name="icon"
|
|
117
|
+
:size="24"
|
|
118
|
+
/>
|
|
119
|
+
</span>
|
|
120
|
+
</template>
|
|
121
|
+
<span class="pt-0.5">
|
|
122
|
+
{{ text }}
|
|
123
|
+
</span>
|
|
124
|
+
</component>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import FdsButtonPrimary from './FdsButtonPrimary.vue'
|
|
3
|
+
import icons from '@/assets/icons'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FdsButtonPrimary> = {
|
|
6
|
+
title: 'FDS/Buttons/FdsButtonPrimary',
|
|
7
|
+
component: FdsButtonPrimary,
|
|
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.size === 'string' && a.size !== 'm') push(`size="${a.size}"`)
|
|
18
|
+
if (typeof a.icon === 'string') push(`icon="${a.icon}"`)
|
|
19
|
+
if (typeof a.iconPos === 'string' && a.iconPos !== 'left') push(`icon-pos="${a.iconPos}"`)
|
|
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 `<FdsButtonPrimary${attrsStr} />`
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
argTypes: {
|
|
33
|
+
text: { control: { type: 'text' } },
|
|
34
|
+
size: { control: { type: 'select' }, options: ['sm', 'md', 'lg'] },
|
|
35
|
+
icon: { control: { type: 'select' }, options: Object.keys(icons) },
|
|
36
|
+
iconPos: { control: { type: 'inline-radio' }, options: ['left', 'right'] },
|
|
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'] },
|
|
42
|
+
type: { control: { type: 'select' }, options: ['button', 'submit', 'reset'] },
|
|
43
|
+
href: { control: { type: 'text' } },
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
text: 'Primary',
|
|
47
|
+
size: 'md',
|
|
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: { FdsButtonPrimary },
|
|
65
|
+
setup: () => ({ args }),
|
|
66
|
+
template: '<FdsButtonPrimary v-bind="args" />',
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const WithIcon: Story = {
|
|
71
|
+
args: { icon: 'arrowRight', text: 'With icon' },
|
|
72
|
+
render: (args) => ({
|
|
73
|
+
components: { FdsButtonPrimary },
|
|
74
|
+
setup: () => ({ args }),
|
|
75
|
+
template: '<FdsButtonPrimary v-bind="args" />',
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const Sizes: Story = {
|
|
80
|
+
render: (args) => ({
|
|
81
|
+
components: { FdsButtonPrimary },
|
|
82
|
+
setup: () => ({ args }),
|
|
83
|
+
template:
|
|
84
|
+
'<div class="flex gap-3 flex-wrap">\n <FdsButtonPrimary v-bind="args" size="s" text="Small" />\n <FdsButtonPrimary v-bind="args" size="m" text="Medium" />\n <FdsButtonPrimary v-bind="args" size="l" text="Large" />\n</div>',
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
|
|
4
|
+
import FdsSpinner from '@/components/FdsSpinner/FdsSpinner.vue'
|
|
5
|
+
import type { FdsButtonBaseProps } from '@/components/Buttons/ButtonBaseProps'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<FdsButtonBaseProps>(), {
|
|
8
|
+
loading: false,
|
|
9
|
+
disabled: false,
|
|
10
|
+
block: false,
|
|
11
|
+
state: undefined,
|
|
12
|
+
icon: undefined,
|
|
13
|
+
iconPos: 'left',
|
|
14
|
+
size: 'md',
|
|
15
|
+
textSelection: false,
|
|
16
|
+
as: 'button',
|
|
17
|
+
href: undefined,
|
|
18
|
+
type: 'button',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
(e: 'click', ev: MouseEvent): void
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const rootClasses = computed(() => [
|
|
26
|
+
'inline-block transition-opacity duration-200',
|
|
27
|
+
props.disabled && 'opacity-20 pointer-events-none',
|
|
28
|
+
props.block && 'block w-full',
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
const elBase =
|
|
32
|
+
'box-border appearance-none inline-flex items-center justify-center cursor-pointer select-none w-full min-h-0 min-w-12 m-0 rounded-lg text-center align-middle whitespace-nowrap no-underline shadow-[0_2px_4px_rgba(12,72,153,0.12)] transition-[box-shadow,border-color,background-color] duration-200 font-main font-bold text-base leading-5 tracking-normal focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500'
|
|
33
|
+
|
|
34
|
+
const sizeClasses: Record<string, string> = {
|
|
35
|
+
sm: 'text-sm h-7 px-3',
|
|
36
|
+
md: 'text-base h-12 px-4',
|
|
37
|
+
lg: 'text-lg h-[68px] px-6',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const variantClasses =
|
|
41
|
+
'bg-red-600 border border-red-700 text-white hover:bg-red-700 active:bg-red-800 active:border-red-800'
|
|
42
|
+
|
|
43
|
+
const blockElClasses = computed(() => props.block && 'w-full justify-center')
|
|
44
|
+
|
|
45
|
+
const iconOrderClasses = computed(() => (props.icon && props.iconPos === 'right' ? 'order-2' : ''))
|
|
46
|
+
|
|
47
|
+
const buttonClasses = computed(() => [
|
|
48
|
+
elBase,
|
|
49
|
+
sizeClasses[props.size || 'm'],
|
|
50
|
+
variantClasses,
|
|
51
|
+
blockElClasses.value,
|
|
52
|
+
props.textSelection && 'select-text',
|
|
53
|
+
(props.icon || props.text) && 'gap-2',
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
const iconFillClass = 'fill-white'
|
|
57
|
+
|
|
58
|
+
function onClick(ev: MouseEvent) {
|
|
59
|
+
if (props.disabled || props.loading) {
|
|
60
|
+
ev.preventDefault()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
emit('click', ev)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const linkAttrs = computed(() => {
|
|
67
|
+
if (props.as === 'a') return { href: props.href }
|
|
68
|
+
if (props.as === 'router-link') return { to: props.href }
|
|
69
|
+
return {}
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div
|
|
75
|
+
:class="rootClasses"
|
|
76
|
+
:aria-disabled="disabled ? 'true' : undefined"
|
|
77
|
+
>
|
|
78
|
+
<component
|
|
79
|
+
:is="as"
|
|
80
|
+
v-bind="linkAttrs"
|
|
81
|
+
:type="as === 'button' ? type : undefined"
|
|
82
|
+
:disabled="as === 'button' ? disabled : undefined"
|
|
83
|
+
:class="buttonClasses"
|
|
84
|
+
@click="onClick"
|
|
85
|
+
>
|
|
86
|
+
<template v-if="loading">
|
|
87
|
+
<FdsSpinner
|
|
88
|
+
size="24px"
|
|
89
|
+
color="inherit"
|
|
90
|
+
/>
|
|
91
|
+
</template>
|
|
92
|
+
<template v-else-if="icon">
|
|
93
|
+
<span
|
|
94
|
+
:class="iconOrderClasses"
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
>
|
|
97
|
+
<FdsIcon
|
|
98
|
+
:class="iconFillClass"
|
|
99
|
+
:name="icon"
|
|
100
|
+
:size="24"
|
|
101
|
+
/>
|
|
102
|
+
</span>
|
|
103
|
+
</template>
|
|
104
|
+
<span v-if="text">{{ text }}</span>
|
|
105
|
+
</component>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import FdsButtonSecondary from './FdsButtonSecondary.vue'
|
|
3
|
+
import icons from '@/assets/icons'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FdsButtonSecondary> = {
|
|
6
|
+
title: 'FDS/Buttons/FdsButtonSecondary',
|
|
7
|
+
component: FdsButtonSecondary,
|
|
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.size === 'string' && a.size !== 'm') push(`size="${a.size}"`)
|
|
18
|
+
if (typeof a.icon === 'string') push(`icon="${a.icon}"`)
|
|
19
|
+
if (typeof a.iconPos === 'string' && a.iconPos !== 'left') push(`icon-pos="${a.iconPos}"`)
|
|
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 `<FdsButtonSecondary${attrsStr} />`
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
argTypes: {
|
|
33
|
+
text: { control: { type: 'text' } },
|
|
34
|
+
size: { control: { type: 'select' }, options: ['sm', 'md', 'lg'] },
|
|
35
|
+
icon: { control: { type: 'select' }, options: Object.keys(icons) },
|
|
36
|
+
iconPos: { control: { type: 'inline-radio' }, options: ['left', 'right'] },
|
|
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'] },
|
|
42
|
+
type: { control: { type: 'select' }, options: ['button', 'submit', 'reset'] },
|
|
43
|
+
href: { control: { type: 'text' } },
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
text: 'Secondary',
|
|
47
|
+
size: 'md',
|
|
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: { FdsButtonSecondary },
|
|
65
|
+
setup: () => ({ args }),
|
|
66
|
+
template: '<FdsButtonSecondary v-bind="args" />',
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import FdsIcon from '@/components/FdsIcon/FdsIcon.vue'
|
|
4
|
+
import FdsSpinner from '@/components/FdsSpinner/FdsSpinner.vue'
|
|
5
|
+
import type { FdsButtonBaseProps } from '@/components/Buttons/ButtonBaseProps'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<FdsButtonBaseProps>(), {
|
|
8
|
+
loading: false,
|
|
9
|
+
disabled: false,
|
|
10
|
+
block: false,
|
|
11
|
+
state: undefined,
|
|
12
|
+
icon: undefined,
|
|
13
|
+
iconPos: 'left',
|
|
14
|
+
size: 'md',
|
|
15
|
+
textSelection: false,
|
|
16
|
+
as: 'button',
|
|
17
|
+
href: undefined,
|
|
18
|
+
type: 'button',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
(e: 'click', ev: MouseEvent): void
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const rootClasses = computed(() => [
|
|
26
|
+
'inline-block transition-opacity duration-200',
|
|
27
|
+
props.disabled && 'opacity-20 pointer-events-none',
|
|
28
|
+
props.block && 'block w-full',
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
const elBase =
|
|
32
|
+
'box-border appearance-none inline-flex items-center justify-center cursor-pointer select-none w-full min-h-0 min-w-12 m-0 rounded-lg text-center align-middle whitespace-nowrap no-underline shadow-[0_2px_4px_rgba(12,72,153,0.12)] transition-[box-shadow,border-color,background-color] duration-200 font-main font-bold text-base leading-5 tracking-normal focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500'
|
|
33
|
+
|
|
34
|
+
const sizeClasses: Record<string, string> = {
|
|
35
|
+
sm: 'text-sm h-7 px-3',
|
|
36
|
+
md: 'text-base h-12 px-4',
|
|
37
|
+
lg: 'text-lg h-[68px] px-6',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const variantClasses =
|
|
41
|
+
'bg-white border-2 border-blue-500 text-blue-600 hover:border-blue-600 active:bg-blue-600 active:border-blue-600 active:text-white'
|
|
42
|
+
|
|
43
|
+
const blockElClasses = computed(() => props.block && 'w-full justify-center')
|
|
44
|
+
|
|
45
|
+
const iconOrderClasses = computed(() => (props.icon && props.iconPos === 'right' ? 'order-2' : ''))
|
|
46
|
+
|
|
47
|
+
const buttonClasses = computed(() => [
|
|
48
|
+
elBase,
|
|
49
|
+
sizeClasses[props.size || 'm'],
|
|
50
|
+
variantClasses,
|
|
51
|
+
blockElClasses.value,
|
|
52
|
+
props.textSelection && 'select-text',
|
|
53
|
+
(props.icon || props.text) && 'gap-2',
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
const iconFillClass = 'fill-blue-500'
|
|
57
|
+
|
|
58
|
+
function onClick(ev: MouseEvent) {
|
|
59
|
+
if (props.disabled || props.loading) {
|
|
60
|
+
ev.preventDefault()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
emit('click', ev)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const linkAttrs = computed(() => {
|
|
67
|
+
if (props.as === 'a') return { href: props.href }
|
|
68
|
+
if (props.as === 'router-link') return { to: props.href }
|
|
69
|
+
return {}
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div
|
|
75
|
+
:class="rootClasses"
|
|
76
|
+
:aria-disabled="disabled ? 'true' : undefined"
|
|
77
|
+
>
|
|
78
|
+
<component
|
|
79
|
+
:is="as"
|
|
80
|
+
v-bind="linkAttrs"
|
|
81
|
+
:type="as === 'button' ? type : undefined"
|
|
82
|
+
:disabled="as === 'button' ? disabled : undefined"
|
|
83
|
+
:class="buttonClasses"
|
|
84
|
+
@click="onClick"
|
|
85
|
+
>
|
|
86
|
+
<template v-if="loading">
|
|
87
|
+
<FdsSpinner
|
|
88
|
+
size="24px"
|
|
89
|
+
color="inherit"
|
|
90
|
+
/>
|
|
91
|
+
</template>
|
|
92
|
+
<template v-else-if="icon">
|
|
93
|
+
<span
|
|
94
|
+
:class="iconOrderClasses"
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
>
|
|
97
|
+
<FdsIcon
|
|
98
|
+
:class="iconFillClass"
|
|
99
|
+
:name="icon"
|
|
100
|
+
:size="24"
|
|
101
|
+
/>
|
|
102
|
+
</span>
|
|
103
|
+
</template>
|
|
104
|
+
<span v-if="text">{{ text }}</span>
|
|
105
|
+
</component>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import icons from '@/assets/icons'
|
|
4
|
+
import FdsIcon from './FdsIcon.vue'
|
|
5
|
+
import type { FdsIconName } from './types'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof FdsIcon> = {
|
|
8
|
+
title: 'FDS/FdsIcon',
|
|
9
|
+
component: FdsIcon,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
parameters: {},
|
|
12
|
+
argTypes: {
|
|
13
|
+
name: { control: { type: 'select' }, options: Object.keys(icons) },
|
|
14
|
+
size: { control: { type: 'text' } },
|
|
15
|
+
title: { control: { type: 'text' } },
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
name: (Object.keys(icons)[0] || '') as FdsIconName,
|
|
19
|
+
size: 24,
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default meta
|
|
24
|
+
type Story = StoryObj<typeof meta>
|
|
25
|
+
|
|
26
|
+
export const Basic: Story = {
|
|
27
|
+
render: (args) => ({
|
|
28
|
+
components: { FdsIcon },
|
|
29
|
+
setup: () => ({ args }),
|
|
30
|
+
template: '<FdsIcon v-bind="args" />',
|
|
31
|
+
}),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const IconGallery: Story = {
|
|
35
|
+
parameters: {},
|
|
36
|
+
render: (args: { name?: string; size?: number | string; title?: string }) => ({
|
|
37
|
+
components: { FdsIcon },
|
|
38
|
+
setup() {
|
|
39
|
+
const iconEntries = ref(Object.keys(icons))
|
|
40
|
+
const selectedName = ref(args.name || iconEntries.value[0] || '')
|
|
41
|
+
function onPick(n: string) {
|
|
42
|
+
selectedName.value = n
|
|
43
|
+
}
|
|
44
|
+
const codeSnippet = computed(() => `<FdsIcon name="${selectedName.value}" size="${args.size}" />`)
|
|
45
|
+
return { args, iconEntries, onPick, codeSnippet, selectedName }
|
|
46
|
+
},
|
|
47
|
+
template: `
|
|
48
|
+
<div>
|
|
49
|
+
<div class="grid grid-cols-5 sm:grid-cols-8 md:grid-cols-10 gap-4">
|
|
50
|
+
<button
|
|
51
|
+
v-for="n in iconEntries"
|
|
52
|
+
:key="n"
|
|
53
|
+
type="button"
|
|
54
|
+
class="flex flex-col items-center gap-2 p-3 rounded border hover:bg-blue-100 focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500"
|
|
55
|
+
@click="onPick(n)"
|
|
56
|
+
:aria-label="'Välj ikon ' + n"
|
|
57
|
+
>
|
|
58
|
+
<FdsIcon :name="n" :size="28" />
|
|
59
|
+
<span class="text-[11px] text-gray-700 truncate max-w-[90px]">{{ n }}</span>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="mt-4 text-sm">Vald ikon: <code class="px-1 py-0.5 bg-gray-100 rounded">{{ selectedName }}</code></div>
|
|
63
|
+
<div class="mt-2">
|
|
64
|
+
<FdsIcon :name="selectedName" :size="args.size" :title="args.title" />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
`,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import icons, { type IconMap } from '@/assets/icons'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import type { FdsIconProps } from './types'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<FdsIconProps>(), {
|
|
7
|
+
size: 24,
|
|
8
|
+
title: undefined,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function normalizeSvg(svg: string): string {
|
|
12
|
+
// Replace explicit width/height with 100% to allow container sizing
|
|
13
|
+
return svg.replace(/width="(.*?)"/g, 'width="100%"').replace(/height="(.*?)"/g, 'height="100%"')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const svgHtml = computed(() => {
|
|
17
|
+
const map = icons as IconMap
|
|
18
|
+
const entry = map[props.name]
|
|
19
|
+
if (!entry) return ''
|
|
20
|
+
const raw = typeof entry === 'function' ? entry() : entry
|
|
21
|
+
const withTitle = props.title ? raw.replace('<svg', `<svg aria-label="${props.title}" role="img"`) : raw
|
|
22
|
+
return normalizeSvg(withTitle)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const pixelSize = computed(() => (typeof props.size === 'number' ? `${props.size}px` : props.size))
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<span
|
|
30
|
+
class="inline-flex items-center justify-center align-middle"
|
|
31
|
+
:style="{ width: pixelSize, height: pixelSize }"
|
|
32
|
+
v-html="svgHtml"
|
|
33
|
+
></span>
|
|
34
|
+
</template>
|