@flitsmeister/design-system 1.0.1

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 (158) hide show
  1. package/.github/workflows/npm-publish.yml +34 -0
  2. package/README.md +3 -0
  3. package/package.json +65 -0
  4. package/postcss.config.js +20 -0
  5. package/svgo.config.js +37 -0
  6. package/tailwind.config.js +146 -0
  7. package/web/.eslintrc +5 -0
  8. package/web/App.vue +60 -0
  9. package/web/components/CookieConsent.vue +115 -0
  10. package/web/components/Flag.vue +47 -0
  11. package/web/components/Icon.vue +31 -0
  12. package/web/components/LanguageSwitcher.vue +77 -0
  13. package/web/components/PermissionToggle.vue +49 -0
  14. package/web/components/button-with-loader.vue +26 -0
  15. package/web/components/fmxButton.vue +225 -0
  16. package/web/components/fmxInput.vue +133 -0
  17. package/web/components/loader.vue +22 -0
  18. package/web/components/modal.vue +61 -0
  19. package/web/components/navbar.vue +69 -0
  20. package/web/icons/bold/add.svg +3 -0
  21. package/web/icons/bold/check.svg +3 -0
  22. package/web/icons/bold/checkbox-checked.svg +4 -0
  23. package/web/icons/bold/checkbox-unchecked.svg +3 -0
  24. package/web/icons/bold/chevron-backward.svg +3 -0
  25. package/web/icons/bold/chevron-down.svg +3 -0
  26. package/web/icons/bold/chevron-forward.svg +3 -0
  27. package/web/icons/bold/chevron-up.svg +3 -0
  28. package/web/icons/bold/close-1.svg +3 -0
  29. package/web/icons/bold/close.svg +4 -0
  30. package/web/icons/bold/dot.svg +3 -0
  31. package/web/icons/bold/open-external.svg +3 -0
  32. package/web/icons/bold/prominent-1.svg +3 -0
  33. package/web/icons/bold/prominent.svg +3 -0
  34. package/web/icons/bold/question.svg +4 -0
  35. package/web/icons/bold/remove.svg +3 -0
  36. package/web/icons/bold/search.svg +3 -0
  37. package/web/icons/bold/spinner.svg +3 -0
  38. package/web/icons/bold/warning.svg +3 -0
  39. package/web/icons/duotone/check.svg +4 -0
  40. package/web/icons/filled/achievements.svg +3 -0
  41. package/web/icons/filled/add.svg +3 -0
  42. package/web/icons/filled/app-settings.svg +3 -0
  43. package/web/icons/filled/calendar.svg +7 -0
  44. package/web/icons/filled/car-simplified.svg +3 -0
  45. package/web/icons/filled/check.svg +3 -0
  46. package/web/icons/filled/clock.svg +3 -0
  47. package/web/icons/filled/close.svg +3 -0
  48. package/web/icons/filled/control.svg +7 -0
  49. package/web/icons/filled/danger.svg +7 -0
  50. package/web/icons/filled/delete.svg +3 -0
  51. package/web/icons/filled/deviceOne.svg +3 -0
  52. package/web/icons/filled/deviceTwo.svg +3 -0
  53. package/web/icons/filled/disputeFine.svg +3 -0
  54. package/web/icons/filled/email.svg +3 -0
  55. package/web/icons/filled/gasPrices.svg +3 -0
  56. package/web/icons/filled/hazard.svg +3 -0
  57. package/web/icons/filled/highway.svg +3 -0
  58. package/web/icons/filled/info.svg +4 -0
  59. package/web/icons/filled/lock-close.svg +3 -0
  60. package/web/icons/filled/lock-open.svg +3 -0
  61. package/web/icons/filled/lock.svg +3 -0
  62. package/web/icons/filled/microphone.svg +4 -0
  63. package/web/icons/filled/notificaiton-unread.svg +4 -0
  64. package/web/icons/filled/notificaiton.svg +3 -0
  65. package/web/icons/filled/parking.svg +3 -0
  66. package/web/icons/filled/play.svg +8 -0
  67. package/web/icons/filled/question.svg +3 -0
  68. package/web/icons/filled/rating-empty.svg +3 -0
  69. package/web/icons/filled/rating-full.svg +3 -0
  70. package/web/icons/filled/rating-half.svg +4 -0
  71. package/web/icons/filled/remove.svg +3 -0
  72. package/web/icons/filled/return-location.svg +3 -0
  73. package/web/icons/filled/support.svg +3 -0
  74. package/web/icons/filled/toll.svg +3 -0
  75. package/web/icons/filled/tripHistory.svg +3 -0
  76. package/web/icons/filled/truck.svg +3 -0
  77. package/web/icons/filled/user-pin.svg +3 -0
  78. package/web/icons/filled/user-profile.svg +3 -0
  79. package/web/icons/filled/warning.svg +4 -0
  80. package/web/icons/filled/zone-default.svg +3 -0
  81. package/web/icons/filled/zone-indoor.svg +3 -0
  82. package/web/icons/filled/zone-street.svg +3 -0
  83. package/web/icons/stroke/arrow-Down.svg +3 -0
  84. package/web/icons/stroke/arrow-Up.svg +3 -0
  85. package/web/icons/stroke/arrow-left.svg +3 -0
  86. package/web/icons/stroke/arrow-right.svg +3 -0
  87. package/web/icons/stroke/check.svg +3 -0
  88. package/web/icons/stroke/checkbox-checked.svg +4 -0
  89. package/web/icons/stroke/checkbox-unchecked.svg +3 -0
  90. package/web/icons/stroke/chevron-backward.svg +3 -0
  91. package/web/icons/stroke/chevron-down.svg +3 -0
  92. package/web/icons/stroke/chevron-forward.svg +3 -0
  93. package/web/icons/stroke/chevron-up.svg +3 -0
  94. package/web/icons/stroke/clock.svg +3 -0
  95. package/web/icons/stroke/close-1.svg +3 -0
  96. package/web/icons/stroke/close.svg +4 -0
  97. package/web/icons/stroke/download.svg +4 -0
  98. package/web/icons/stroke/email.svg +4 -0
  99. package/web/icons/stroke/hamburger.svg +3 -0
  100. package/web/icons/stroke/login.svg +4 -0
  101. package/web/icons/stroke/logout.svg +3 -0
  102. package/web/icons/stroke/more.svg +5 -0
  103. package/web/icons/stroke/open-external.svg +3 -0
  104. package/web/icons/stroke/question.svg +4 -0
  105. package/web/icons/stroke/search.svg +3 -0
  106. package/web/icons/stroke/spinner.svg +3 -0
  107. package/web/icons/stroke/warning.svg +3 -0
  108. package/web/index.html +25 -0
  109. package/web/index.js +55 -0
  110. package/web/locales/da.json +197 -0
  111. package/web/locales/de.json +197 -0
  112. package/web/locales/en.json +333 -0
  113. package/web/locales/fi.json +197 -0
  114. package/web/locales/fr.json +197 -0
  115. package/web/locales/nl.json +333 -0
  116. package/web/locales/no.json +197 -0
  117. package/web/locales/pl.json +197 -0
  118. package/web/locales/sv.json +197 -0
  119. package/web/pages/buttons.html +32 -0
  120. package/web/pages/buttons.vue +18 -0
  121. package/web/pages/colors.html +32 -0
  122. package/web/pages/colors.vue +29 -0
  123. package/web/pages/icons.vue +49 -0
  124. package/web/pages/index.html +25 -0
  125. package/web/pages/index.vue +15 -0
  126. package/web/pages/input.html +47 -0
  127. package/web/pages/input.vue +34 -0
  128. package/web/router.js +20 -0
  129. package/web/static/apple-touch-icon.png +0 -0
  130. package/web/static/download.png +0 -0
  131. package/web/static/facebook-login.png +0 -0
  132. package/web/static/facebook-migrate.png +0 -0
  133. package/web/static/favicon-16x16.png +0 -0
  134. package/web/static/favicon-32x32.png +0 -0
  135. package/web/static/favicon.ico +0 -0
  136. package/web/static/fleet-bg.png +0 -0
  137. package/web/static/flitsmeister-logo-small.png +0 -0
  138. package/web/static/flitsmeister-logo.png +0 -0
  139. package/web/static/flitsmeister_app_icon.png +0 -0
  140. package/web/static/fm-dash-logo.svg +19 -0
  141. package/web/static/icon-facebook.png +0 -0
  142. package/web/static/index.png +0 -0
  143. package/web/static/login-bg.png +0 -0
  144. package/web/static/no_invoices.png +0 -0
  145. package/web/static/person-shrugging.png +0 -0
  146. package/web/static/pro_confetti@2x.png +0 -0
  147. package/web/static/pro_star@2x.png +0 -0
  148. package/web/static/sso/bmfleet.png +0 -0
  149. package/web/static/sso/pitstop.png +0 -0
  150. package/web/static/sso/truckmeister.png +0 -0
  151. package/web/static/sso/webshop.png +0 -0
  152. package/web/static/triplogging_whatsnew.png +0 -0
  153. package/web/store.js +79 -0
  154. package/web/styles/app.scss +9 -0
  155. package/web/styles/flashmaster/fm-design-system.json +4414 -0
  156. package/web/styles/flashmaster/primitives.json +604 -0
  157. package/web/styles/flashmaster.scss +162 -0
  158. package/webpack.config.js +161 -0
@@ -0,0 +1,34 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 22
18
+ - run: npm ci
19
+ - run: npm run build
20
+
21
+ publish-npm:
22
+ needs: build
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 22
29
+ registry-url: https://registry.npmjs.org/
30
+ - run: npm ci
31
+ - run: npm run build
32
+ - run: npm publish
33
+ env:
34
+ NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Flitsmeister design system
2
+ Contains support for Flitsmeister design system colors, typography, buttons, icons and input
3
+
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@flitsmeister/design-system",
3
+ "version": "1.0.1",
4
+ "description": "Flitsmeister design system and demo site",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "MODE=development webpack serve",
8
+ "build": "MODE=production webpack",
9
+ "build-dev": "MODE=development webpack",
10
+ "test": "exit 0;"
11
+ },
12
+ "author": "Flitsmeister",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "@fullhuman/postcss-purgecss": "^5.0.0",
16
+ "@intlify/vue-i18n-loader": "~4.2.0",
17
+ "autoprefixer": "~10.4.13",
18
+ "axios": "~1.3.2",
19
+ "clean-webpack-plugin": "~4.0.0",
20
+ "copy-webpack-plugin": "~11.0.0",
21
+ "css-loader": "~6.7.3",
22
+ "dotenv-webpack": "~8.0.1",
23
+ "es6-promise": "~4.2.8",
24
+ "flagpack-core": "^2.0.0",
25
+ "html-webpack-plugin": "~5.5.0",
26
+ "node-sass": "~8.0.0",
27
+ "postcss": "~8.4.21",
28
+ "postcss-loader": "~7.0.2",
29
+ "sass-loader": "~13.2.0",
30
+ "style-loader": "~3.3.1",
31
+ "tailwindcss": "~3.2.6",
32
+ "uuid": "~9.0.0",
33
+ "vue": "~3.2.47",
34
+ "vue-i18n": "~9.2.2",
35
+ "vue-loader": "~17.0.1",
36
+ "vue-router": "~4.1.6",
37
+ "vue-style-loader": "~4.1.3",
38
+ "vue-template-compiler": "~2.7.14",
39
+ "vuex": "~4.1.0",
40
+ "webpack": "~5.75.0",
41
+ "webpack-cli": "~5.0.1"
42
+ },
43
+ "devDependencies": {
44
+ "@flitsmeister/eslint": "~1.2.1",
45
+ "svg-sprite-loader": "~6.0.11",
46
+ "svgo": "~3.3.2",
47
+ "svgo-loader": "~4.0.0",
48
+ "webpack-dev-server": "~4.11.1"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/flitsmeister/design-system.git"
53
+ },
54
+ "keywords": [
55
+ "flitsmeister",
56
+ "design",
57
+ "styling",
58
+ "colors",
59
+ "icons"
60
+ ],
61
+ "bugs": {
62
+ "url": "https://github.com/flitsmeister/design-system/issues"
63
+ },
64
+ "homepage": "https://github.com/flitsmeister/design-system#readme"
65
+ }
@@ -0,0 +1,20 @@
1
+ const Autoprefixer = require('autoprefixer')
2
+ const TailwindCSS = require('tailwindcss')
3
+ const PostCSSPurgeCSS = require('@fullhuman/postcss-purgecss')
4
+
5
+ const purgecss = PostCSSPurgeCSS({
6
+ content: ['./web/**/*.html', './web/**/*.vue', './web/**/*.js'],
7
+ // Include any special characters you're using in this regular expression.
8
+ // See: https://tailwindcss.com/docs/controlling-file-size/#understanding-the-regex
9
+ defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
10
+ // Whitelist auto generated classes for transitions and router links.
11
+ // From: https://github.com/ky-is/vue-cli-plugin-tailwind
12
+ whitelistPatterns: [/-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/]
13
+ })
14
+
15
+ module.exports = {
16
+ plugins: [
17
+ TailwindCSS,
18
+ Autoprefixer,
19
+ ...(process.env.NODE_ENV === 'production' ? [purgecss] : [])]
20
+ }
package/svgo.config.js ADDED
@@ -0,0 +1,37 @@
1
+ /* eslint-disable strict */
2
+ module.exports = {
3
+ plugins: [
4
+ {
5
+ name: 'preset-default',
6
+ params: {
7
+ overrides: {
8
+ // disable plugins
9
+ removeViewBox: false,
10
+ }
11
+ }
12
+ }
13
+ /* trial attempt at replacing fill and stroke with currentColor - this does work but requires some additional work
14
+ {
15
+ name: 'customReplaceFillAndStroke',
16
+ type: 'perItem',
17
+ fn: (node) => {
18
+ console.log('Processing SVGO customReplace ', node);
19
+ if (node.type === 'element') {
20
+ node.attributes = Object.fromEntries(
21
+ Object.entries(node.attributes).map(([name, value]) => {
22
+ if (
23
+ (name === 'fill' || name === 'stroke') &&
24
+ (value === 'black' || value === '#161616')
25
+ ) {
26
+ console.log('Replacing', name, value, 'with currentColor');
27
+ return [name, 'currentColor'];
28
+ }
29
+ return [name, value];
30
+ })
31
+ );
32
+ }
33
+ },
34
+ },
35
+ */
36
+ ]
37
+ }
@@ -0,0 +1,146 @@
1
+ /* eslint-disable strict */
2
+ const colors = require('tailwindcss/colors')
3
+ const importedPrimitives = require('./web/styles/flashmaster/primitives.json')
4
+
5
+ if (!importedPrimitives[0].Primitives.modes.default.color) {
6
+ throw new Error('Primitives not found in tailwind.config.js')
7
+ }
8
+
9
+ const primitives = importedPrimitives[0].Primitives.modes.default.color
10
+
11
+ const mapPrimitivesToTailwindColors = (primitivesData) => {
12
+ const tailwindColors = {}
13
+
14
+ for (const [colorName, shades] of Object.entries(primitivesData)) {
15
+ tailwindColors[colorName.toLowerCase()] = {}
16
+
17
+ for (const [shadeName, shadeValue] of Object.entries(shades)) {
18
+ const match = shadeName.match(/\d+/) // Extract the number from the shade name
19
+ if (match) {
20
+ const shadeNumber = match[0]
21
+ tailwindColors[colorName.toLowerCase()][shadeNumber] = shadeValue.$value
22
+ }
23
+ }
24
+ }
25
+
26
+ return tailwindColors
27
+ }
28
+
29
+ const customColors = mapPrimitivesToTailwindColors(primitives)
30
+
31
+ module.exports = {
32
+ content: ['./web/**/*.html', './web/**/*.vue', './web/**/*.jsx', './web/**/*.js'],
33
+ corePlugins: {
34
+ float: false
35
+ },
36
+
37
+ /* NOT REQUIRED FOR FLASHMASTER: darkMode is only being set to 'class' for this demo site */
38
+ darkMode: 'class',
39
+
40
+ /* REQUIRED FOR FLASHMASTER */
41
+ theme: {
42
+ container: {
43
+ center: true
44
+ },
45
+ colors: {
46
+ inherit: colors.inherit,
47
+ current: colors.current,
48
+ transparent: colors.transparent,
49
+ black: colors.black,
50
+ white: colors.white,
51
+ ...customColors, // Add custom colors from primitives.json
52
+ // designsystem token colors
53
+ surface: {
54
+ defaulthighest: 'var(--surface-defaultHighest)',
55
+ defaulthigher: 'var(--surface-defaultHigher)',
56
+ default: 'var(--surface-default)',
57
+ defaultlower: 'var(--surface-defaultLower)',
58
+ defaultlowest: 'var(--surface-defaultLowest)',
59
+ brand: 'var(--surface-brand)',
60
+ brandalt: 'var(--surface-brandAlt)',
61
+ pro: 'var(--surface-pro)',
62
+ proalt: 'var(--surface-proAlt)',
63
+ proplus: 'var(--surface-proPlus)',
64
+ danger: 'var(--surface-danger)',
65
+ dangeralt: 'var(--surface-dangerAlt)',
66
+ warning: 'var(--surface-warning)',
67
+ warningalt: 'var(--surface-warningAlt)',
68
+ attention: 'var(--surface-attention)',
69
+ info: 'var(--surface-info)',
70
+ infoalt: 'var(--surface-infoAlt)',
71
+ success: 'var(--surface-success)',
72
+ successalt: 'var(--surface-successAlt)'
73
+ },
74
+ content: {
75
+ default: 'var(--content-default)',
76
+ defaultalt: 'var(--content-defaultAlt)',
77
+ weak: 'var(--content-weak)',
78
+ label: 'var(--content-label)',
79
+ brand: 'var(--content-brand)',
80
+ pro: 'var(--content-pro)',
81
+ proplus: 'var(--content-proPlus)',
82
+ danger: 'var(--content-danger)',
83
+ warning: 'var(--content-warning)',
84
+ attention: 'var(--content-attention)',
85
+ info: 'var(--content-info)',
86
+ success: 'var(--content-success)'
87
+ },
88
+ outline: {
89
+ default: 'var(--outline-default)',
90
+ brand: 'var(--outline-brand)',
91
+ danger: 'var(--outline-danger)',
92
+ dangeralt: 'var(--outline-dangerAlt)',
93
+ warning: 'var(--outline-warning)',
94
+ warningalt: 'var(--outline-warningAlt)',
95
+ info: 'var(--outline-info)',
96
+ success: 'var(--outline-success)'
97
+ },
98
+ feature: {
99
+ speedcam: 'var(--feature-speedcam)',
100
+ speedcheck: 'var(--feature-speedcheck)',
101
+ parking: 'var(--feature-parking)',
102
+ parkingalt: 'var(--feature-parkingAlt)',
103
+ police: 'var(--feature-police)',
104
+ firetruck: 'var(--feature-firetruck)',
105
+ ambulance: 'var(--feature-ambulance)',
106
+ emergency: 'var(--feature-emergency)',
107
+ signal: 'var(--feature-signal)'
108
+ }
109
+ },
110
+ extend: {
111
+ spacing: {
112
+ /*
113
+ 1 - nudge / Tailwind "px"
114
+ 2 - xxxs / Tailwind "0.5"
115
+ 4 - xxs / Tailwind "1"
116
+ 8 - xs / Tailwind "2"
117
+ 12 - sm / Tailwind "3"
118
+ 16 - md / Tailwind "4"
119
+ 24 - lg / Tailwind "6"
120
+ 32 - xl / Tailwind "8"
121
+ 48 - xxl / Tailwind "12"
122
+ 64 - xxxl / Tailwind "16"
123
+ */
124
+ nudge: '1px',
125
+ xxxs: '2px',
126
+ xxs: '4px',
127
+ xs: '8px',
128
+ sm: '12px',
129
+ md: '16px',
130
+ lg: '24px',
131
+ xl: '32px',
132
+ xxl: '48px',
133
+ xxxl: '64px'
134
+ },
135
+ fontSize: {
136
+ // the other fontsize tokens (xs, sm, md, lg & xl) already happen to match with those of Tailwind :D
137
+ // in hindsight, it seems that these are barely even used...
138
+ xxl: '2.5rem', // 40px
139
+ xxxl: '3.5', // 56px
140
+ xxxxl: '4rem' // 64px
141
+ }
142
+ }
143
+ },
144
+ variants: {},
145
+ plugins: []
146
+ }
package/web/.eslintrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "parserOptions": {
3
+ "sourceType": "module"
4
+ }
5
+ }
package/web/App.vue ADDED
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <div class="transition-all duration-300 min-h-screen bg-surface-default text-content-default">
3
+ <Navbar />
4
+ <div id="navtarget">
5
+ <router-view />
6
+ </div>
7
+ </div>
8
+ <div id="iconsprite" v-html="spriteContent"></div>
9
+ </template>
10
+
11
+ <script>
12
+ import Navbar from './components/navbar.vue'
13
+
14
+ // Import all SVG files in the icons directory
15
+ const requireAll = (requireContext) => requireContext.keys().map(requireContext);
16
+ const req = require.context('./icons', true, /\.svg$/);
17
+ requireAll(req);
18
+
19
+ export default {
20
+ name: 'flitsmeister-theme',
21
+ components: { Navbar },
22
+ data() {
23
+ return {
24
+ spriteContent: ''
25
+ };
26
+ },
27
+ mounted() {
28
+ fetch('/sprite.svg')
29
+ .then(response => response.text())
30
+ .then(sprite => {
31
+ this.spriteContent = sprite;
32
+ });
33
+ }
34
+ }
35
+ </script>
36
+
37
+ <style src="./styles/app.scss" lang="scss" />
38
+ <style lang="scss" module>
39
+ #app {
40
+ min-height: 100%;
41
+ height: 100%;
42
+ }
43
+
44
+ #iconsprite {
45
+ display: none;
46
+
47
+ // override fill and/or stroke color, only when it's already set
48
+ symbol:not([id^="duotone-"]) {
49
+ path, rect, circle, ellipse, polygon, polyline, line {
50
+ &[fill] {
51
+ fill: currentColor;
52
+ }
53
+ &[stroke] {
54
+ stroke: currentColor;
55
+ }
56
+ }
57
+ }
58
+ /* might still need to look at 'mask' elements */
59
+ }
60
+ </style>
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <div
3
+ class="cookie-settings z-10 transition-all duration-300 overflow-hidden shadow-lg border border-neutral-100 fixed flex flex-col gap-2 bottom-4 right-4 bg-white max-w-[360px] rounded-2xl px-4 py-3"
4
+ :class="{ 'translate-y-8 opacity-0 pointer-events-none': !settings.display }"
5
+ >
6
+ <span class="text-blue font-semibold">
7
+ {{ $t('cookies.title') }}
8
+ </span>
9
+ <div class="text-gray-800 text-sm font-light">
10
+ {{ $t('cookies.description') }}
11
+ </div>
12
+
13
+
14
+ <div v-if="settings.showAll === false" class="flex items-center gap-2 mt-2 w-full justify-evenly">
15
+ <button class="bg-blue text-sm text-white font-medium py-2 rounded-xl w-full" @click="toggleSetting('display')">
16
+ {{ $t('cookies.refuse') }}
17
+ </button>
18
+ <button class="bg-blue text-sm text-white font-medium py-2 rounded-xl w-full" @click="toggleSetting('showAll')">
19
+ {{ $t('cookies.customize') }}
20
+ </button>
21
+ <button class="bg-blue text-sm text-white font-medium py-2 rounded-xl w-full" @click="saveSettings(true)">
22
+ {{ $t('cookies.accept') }}
23
+ </button>
24
+ </div>
25
+
26
+ <Transition name="slide-up">
27
+ <form v-if="settings.showAll" class="flex flex-col gap-2" @submit.prevent="saveSettings">
28
+ <PermissionToggle
29
+ :title="this.$i18n.t('cookies.permissionsections.essentials.title')"
30
+ :description="this.$i18n.t('cookies.permissionsections.essentials.description')"
31
+ :value="settings.essentials"
32
+ :onToggle="() => toggleSetting('essentials')"
33
+ />
34
+ <PermissionToggle
35
+ :title="this.$i18n.t('cookies.permissionsections.preferences.title')"
36
+ :description="this.$i18n.t('cookies.permissionsections.preferences.description')"
37
+ :value="settings.preferences"
38
+ :onToggle="() => toggleSetting('preferences')"
39
+ />
40
+ <PermissionToggle
41
+ :title="this.$i18n.t('cookies.permissionsections.analytics.title')"
42
+ :description="this.$i18n.t('cookies.permissionsections.analytics.description')"
43
+ :value="settings.analytics"
44
+ :onToggle="() => toggleSetting('analytics')"
45
+ />
46
+ <PermissionToggle
47
+ :title="this.$i18n.t('cookies.permissionsections.marketing.title')"
48
+ :description="this.$i18n.t('cookies.permissionsections.marketing.description')"
49
+ :value="settings.marketing"
50
+ :onToggle="() => toggleSetting('marketing')"
51
+ />
52
+ <button type="submit" class="bg-blue text-white font-semibold py-2 px-4 rounded" @click="() => saveSettings">
53
+ {{ $t('cookies.save') }}
54
+ </button>
55
+ </form>
56
+ </Transition>
57
+
58
+ </div>
59
+ </template>
60
+
61
+ <script>
62
+ import { mapState, mapActions } from 'vuex';
63
+ import PermissionToggle from './PermissionToggle.vue';
64
+
65
+ export default {
66
+ components: {
67
+ PermissionToggle,
68
+ },
69
+ computed: {
70
+ ...mapState({
71
+ settings: (state) => state.cookieSettings,
72
+ }),
73
+ },
74
+ methods: {
75
+ ...mapActions(['toggleSetting', 'saveCookieSettings']),
76
+ async saveSettings(acceptAll = false) {
77
+ if (acceptAll === true) {
78
+ await this.toggleSetting({key: 'analytics', value: true});
79
+ await this.toggleSetting({key: 'marketing', value: true});
80
+ await this.toggleSetting({key: 'preferences', value: true});
81
+ }
82
+
83
+ await this.toggleSetting({key: 'display', value: false});
84
+ await this.saveCookieSettings(this.settings);
85
+ this.updateGTMConsent(this.settings);
86
+ },
87
+ updateGTMConsent(settings) {
88
+ gtag('consent', 'update', {
89
+ 'functionality_storage': settings.essentials ? 'granted' : 'denied',
90
+ 'security_storage': settings.essentials ? 'granted' : 'denied',
91
+ 'personalization_storage': settings.preferences ? 'granted' : 'denied',
92
+ 'analytics_storage': settings.analytics ? 'granted' : 'denied',
93
+ 'ad_storage': settings.marketing ? 'granted' : 'denied',
94
+ 'ad_user_data': settings.marketing ? 'granted' : 'denied',
95
+ 'ad_personalization': settings.marketing ? 'granted' : 'denied'
96
+ });
97
+ },
98
+ },
99
+ };
100
+ </script>
101
+
102
+ <style scoped>
103
+ .slide-up-enter-active,
104
+ .slide-up-leave-active {
105
+ transition: all 0.5s ease;
106
+ transform-origin: top;
107
+ max-height: 550px;
108
+ }
109
+
110
+ .slide-up-enter-from,
111
+ .slide-up-leave-to {
112
+ transform: translateY(100%);
113
+ max-height: 42px;
114
+ }
115
+ </style>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <img :src="flagSrc" :alt="countryCode" :class="sizeClass" v-if="flagSrc" />
3
+ <span v-else class="text-red-500">x</span>
4
+ </template>
5
+
6
+ <script>
7
+ import { isoToCountryCode } from 'flagpack-core';
8
+
9
+ export default {
10
+ props: {
11
+ countryCode: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ size: {
16
+ type: String,
17
+ default: 'medium',
18
+ validator: value => ['s', 'm', 'l'].includes(value)
19
+ }
20
+ },
21
+ computed: {
22
+ flagSrc() {
23
+ try {
24
+ const alpha2Code = isoToCountryCode(this.countryCode.toUpperCase(), 'alpha2');
25
+ if (!alpha2Code) {
26
+ throw new Error(`No alpha2 code found for country code: ${this.countryCode}`);
27
+ }
28
+ return require(`flagpack-core/svg/${this.size[0]}/${alpha2Code}.svg`);
29
+ } catch (error) {
30
+ console.error(error.message);
31
+ return null;
32
+ }
33
+ },
34
+ sizeClass() {
35
+ return {
36
+ s: 'w-4 h-3',
37
+ m: 'w-5 h-4',
38
+ l: 'w-8 h-6'
39
+ }[this.size];
40
+ }
41
+ }
42
+ };
43
+ </script>
44
+
45
+ <style scoped>
46
+ /* Add any necessary styles here */
47
+ </style>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <svg v-if="name" :class="iconClass">
3
+ <use :xlink:href="`#${name}`"></use>
4
+ </svg>
5
+ </template>
6
+
7
+ <script>
8
+ export default {
9
+ name: 'Icon',
10
+ props: {
11
+ name: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ iconClass: {
16
+ type: String,
17
+ default: ''
18
+ }
19
+ }
20
+ };
21
+ </script>
22
+
23
+ <style scoped>
24
+ svg {
25
+ /* inherit parent element font size and color by default */
26
+ width: 1em;
27
+ height: 1em;
28
+ fill: currentColor;
29
+ display: inline-block;
30
+ }
31
+ </style>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <div class="z-10 fixed top-4 right-4 bg-white px-4 py-3">
3
+ <div class="relative inline-block text-left">
4
+ <div>
5
+ <button @click="toggleDropdown" type="button" :title="languages[currentLanguage].name" class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-2xl text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" id="menu-button" aria-expanded="true" aria-haspopup="true">
6
+ <Flag :countryCode="(languages[currentLanguage].flagiso) ? languages[currentLanguage].flagiso : currentLanguage" size="m" class="inline-block mr-2 my-auto" />
7
+
8
+ <svg class="-mr-1 ml-3 mt-1 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
9
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
10
+ </svg>
11
+ </button>
12
+ </div>
13
+
14
+ <div v-if="dropdownOpen" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
15
+ <div class="py-1" role="none">
16
+ <a v-for="(language, key) in languages" :key="key" @click="changeLanguage(key)" :title="language.name" class="text-gray-700 block px-4 py-2 text-sm font-normal cursor-pointer hover:bg-gray-100" role="menuitem" tabindex="-1">
17
+ <Flag :countryCode="(language.flagiso) ? language.flagiso : key" size="s" class="inline-block mr-2" />
18
+ {{ language.name }}
19
+ </a>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script>
27
+ import Flag from './Flag.vue';
28
+
29
+ export default {
30
+ components: {
31
+ Flag
32
+ },
33
+ data() {
34
+ return {
35
+ currentLanguage: this.$i18n.locale,
36
+ dropdownOpen: false,
37
+ languages: {
38
+ en: { name: 'English', flagiso: 'gb-ukm' },
39
+ da: { name: 'Dansk', flagiso: 'dk' },
40
+ de: { name: 'Deutsch' },
41
+ fi: { name: 'Suomi' },
42
+ fr: { name: 'Français' },
43
+ nl: { name: 'Nederlands' },
44
+ no: { name: 'Norsk' },
45
+ pl: { name: 'Polski' },
46
+ sv: { name: 'Svenska' },
47
+ es: { name: 'Español' }
48
+ }
49
+ };
50
+ },
51
+ methods: {
52
+ toggleDropdown() {
53
+ this.dropdownOpen = !this.dropdownOpen;
54
+ },
55
+ changeLanguage(languageKey) {
56
+ this.currentLanguage = languageKey;
57
+ this.$i18n.locale = languageKey;
58
+ this.dropdownOpen = false;
59
+ },
60
+ handleClickOutside(event) {
61
+ if (!this.$el.contains(event.target)) {
62
+ this.dropdownOpen = false;
63
+ }
64
+ }
65
+ },
66
+ mounted() {
67
+ document.addEventListener('click', this.handleClickOutside);
68
+ },
69
+ beforeUnmount() {
70
+ document.removeEventListener('click', this.handleClickOutside);
71
+ }
72
+ };
73
+ </script>
74
+
75
+ <style scoped>
76
+ /* Add any necessary styles here */
77
+ </style>