spoko-design-system 1.3.7 → 1.4.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.
@@ -1,4 +1,4 @@
1
- name: Code Quality
1
+ name: Code Quality & Analysis
2
2
 
3
3
  on:
4
4
  push:
@@ -36,4 +36,37 @@ jobs:
36
36
  run: pnpm run lint
37
37
 
38
38
  - name: Type check
39
- run: pnpm run check
39
+ run: pnpm run check
40
+
41
+ sonarcloud:
42
+ name: SonarCloud Analysis
43
+ runs-on: ubuntu-latest
44
+
45
+ steps:
46
+ - name: Checkout code
47
+ uses: actions/checkout@v5
48
+ with:
49
+ fetch-depth: 0 # Shallow clones should be disabled for better analysis
50
+
51
+ - name: Setup pnpm
52
+ uses: pnpm/action-setup@v4
53
+ with:
54
+ version: 10.17.1
55
+
56
+ - name: Setup Node.js
57
+ uses: actions/setup-node@v5
58
+ with:
59
+ node-version: '22'
60
+ cache: 'pnpm'
61
+
62
+ - name: Install dependencies
63
+ run: pnpm install --frozen-lockfile
64
+
65
+ - name: Build project
66
+ run: pnpm run build
67
+
68
+ - name: SonarCloud Scan
69
+ uses: SonarSource/sonarcloud-github-action@master
70
+ env:
71
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
72
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [1.4.1](https://github.com/polo-blue/sds/compare/v1.4.0...v1.4.1) (2025-10-20)
2
+
3
+ ## [1.4.0](https://github.com/polo-blue/sds/compare/v1.3.7...v1.4.0) (2025-10-20)
4
+
5
+ ### Features
6
+
7
+ * enhance components with flexible layouts and click-to-copy functionality ([3dfa718](https://github.com/polo-blue/sds/commit/3dfa718f7f4355c0beed4032e84d01a2c2c4a547))
8
+
1
9
  ## [1.3.7](https://github.com/polo-blue/sds/compare/v1.3.6...v1.3.7) (2025-10-08)
2
10
 
3
11
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoko-design-system",
3
- "version": "1.3.7",
3
+ "version": "1.4.1",
4
4
  "private": false,
5
5
  "main": "./index.ts",
6
6
  "module": "./index.ts",
@@ -51,33 +51,33 @@
51
51
  "spoko design system"
52
52
  ],
53
53
  "dependencies": {
54
- "@algolia/client-search": "^5.40.0",
55
- "@astrojs/mdx": "^4.3.6",
56
- "@astrojs/node": "^9.4.4",
54
+ "@algolia/client-search": "^5.40.1",
55
+ "@astrojs/mdx": "^4.3.7",
56
+ "@astrojs/node": "^9.5.0",
57
57
  "@astrojs/sitemap": "^3.6.0",
58
- "@astrojs/ts-plugin": "^1.10.4",
58
+ "@astrojs/ts-plugin": "^1.10.5",
59
59
  "@astrojs/vue": "^5.1.1",
60
- "@docsearch/css": "^4.1.0",
60
+ "@docsearch/css": "^4.2.0",
61
61
  "@iconify-json/ant-design": "^1.2.5",
62
62
  "@iconify-json/bi": "^1.2.6",
63
63
  "@iconify-json/bx": "^1.2.2",
64
- "@iconify-json/carbon": "^1.2.13",
65
- "@iconify-json/circle-flags": "^1.2.8",
64
+ "@iconify-json/carbon": "^1.2.14",
65
+ "@iconify-json/circle-flags": "^1.2.10",
66
66
  "@iconify-json/ei": "^1.2.2",
67
67
  "@iconify-json/el": "^1.2.2",
68
68
  "@iconify-json/eos-icons": "^1.2.4",
69
69
  "@iconify-json/et": "^1.2.1",
70
70
  "@iconify-json/flowbite": "^1.2.7",
71
- "@iconify-json/fluent": "^1.2.32",
71
+ "@iconify-json/fluent": "^1.2.33",
72
72
  "@iconify-json/fluent-emoji": "1.2.6",
73
73
  "@iconify-json/ic": "^1.2.4",
74
74
  "@iconify-json/icon-park-outline": "^1.2.4",
75
75
  "@iconify-json/la": "^1.2.1",
76
- "@iconify-json/lucide": "^1.2.68",
77
- "@iconify-json/material-symbols-light": "^1.2.40",
76
+ "@iconify-json/lucide": "^1.2.70",
77
+ "@iconify-json/material-symbols-light": "^1.2.42",
78
78
  "@iconify-json/mdi": "^1.2.3",
79
79
  "@iconify-json/noto-v1": "^1.2.5",
80
- "@iconify-json/octicon": "^1.2.14",
80
+ "@iconify-json/octicon": "^1.2.16",
81
81
  "@iconify-json/ph": "^1.2.2",
82
82
  "@iconify-json/simple-icons": "^1.2.54",
83
83
  "@iconify-json/streamline": "^1.2.5",
@@ -85,19 +85,19 @@
85
85
  "@iconify-json/streamline-freehand-color": "^1.2.2",
86
86
  "@iconify-json/system-uicons": "^1.2.4",
87
87
  "@iconify-json/uil": "^1.2.3",
88
- "@iconify-json/vscode-icons": "^1.2.30",
89
- "@iconify/json": "^2.2.392",
88
+ "@iconify-json/vscode-icons": "^1.2.32",
89
+ "@iconify/json": "^2.2.397",
90
90
  "@iconify/vue": "^5.0.0",
91
91
  "@playform/compress": "^0.2.0",
92
92
  "@playform/inline": "^0.1.2",
93
- "@unocss/astro": "66.5.2",
94
- "@unocss/preset-attributify": "66.5.2",
95
- "@unocss/preset-typography": "66.5.2",
96
- "@unocss/preset-uno": "66.5.2",
97
- "@unocss/preset-web-fonts": "66.5.2",
98
- "@unocss/preset-wind": "66.5.2",
99
- "@unocss/reset": "66.5.2",
100
- "@vite-pwa/astro": "^1.1.0",
93
+ "@unocss/astro": "66.5.4",
94
+ "@unocss/preset-attributify": "66.5.4",
95
+ "@unocss/preset-typography": "66.5.4",
96
+ "@unocss/preset-uno": "66.5.4",
97
+ "@unocss/preset-web-fonts": "66.5.4",
98
+ "@unocss/preset-wind": "66.5.4",
99
+ "@unocss/reset": "66.5.4",
100
+ "@vite-pwa/astro": "^1.1.1",
101
101
  "@vueuse/core": "^13.9.0",
102
102
  "astro-icon": "^1.1.5",
103
103
  "astro-meta-tags": "^0.4.0",
@@ -106,30 +106,30 @@
106
106
  "astro-remote": "^0.3.4",
107
107
  "dotenv": "^17.2.3",
108
108
  "swiper": "^12.0.2",
109
- "unocss": "66.5.2",
109
+ "unocss": "66.5.4",
110
110
  "vue": "^3.5.22"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@semantic-release/changelog": "^6.0.3",
114
114
  "@semantic-release/git": "^10.0.1",
115
115
  "@types/gtag.js": "^0.0.20",
116
- "@types/node": "^24.7.0",
117
- "@typescript-eslint/eslint-plugin": "^8.46.0",
118
- "@typescript-eslint/parser": "^8.46.0",
119
- "@unocss/transformer-variant-group": "66.5.2",
116
+ "@types/node": "^24.8.1",
117
+ "@typescript-eslint/eslint-plugin": "^8.46.1",
118
+ "@typescript-eslint/parser": "^8.46.1",
119
+ "@unocss/transformer-variant-group": "66.5.4",
120
120
  "@vitejs/plugin-vue": "^6.0.1",
121
121
  "@vue/compiler-sfc": "^3.5.22",
122
122
  "@vue/eslint-config-typescript": "^14.6.0",
123
- "astro": "^5.14.1",
123
+ "astro": "^5.14.6",
124
124
  "conventional-changelog-conventionalcommits": "^9.1.0",
125
- "eslint": "^9.37.0",
125
+ "eslint": "^9.38.0",
126
126
  "eslint-plugin-astro": "^1.3.1",
127
- "eslint-plugin-vue": "^10.5.0",
127
+ "eslint-plugin-vue": "^10.5.1",
128
128
  "prettier": "^3.6.2",
129
129
  "prettier-plugin-astro": "^0.14.1",
130
- "semantic-release": "^24.2.9",
130
+ "semantic-release": "^25.0.1",
131
131
  "unocss": "^0.65.0",
132
- "vite": "^7.1.9"
132
+ "vite": "^7.1.11"
133
133
  },
134
134
  "packageManager": "pnpm@10.17.1",
135
135
  "pnpm": {
@@ -1,8 +1,30 @@
1
1
  <script setup lang="ts">
2
+ import { ref } from 'vue';
2
3
  import { colors } from './../../uno-config/theme/colors';
3
4
 
4
5
  // Get color categories
5
6
  const colorCategories = Object.entries(colors);
7
+
8
+ // Track copied state for visual feedback
9
+ const copiedItem = ref<string | null>(null);
10
+
11
+ const copyToClipboard = async (text: string, id: string) => {
12
+ try {
13
+ await navigator.clipboard.writeText(text);
14
+ copiedItem.value = id;
15
+ setTimeout(() => {
16
+ copiedItem.value = null;
17
+ }, 2000);
18
+ } catch (err) {
19
+ console.error('Failed to copy:', err);
20
+ }
21
+ };
22
+
23
+ const getColorClass = (category: string, name: string) => {
24
+ // Generate Tailwind/UnoCSS class name
25
+ const colorName = name === 'default' ? category : `${category}-${name}`;
26
+ return colorName;
27
+ };
6
28
  </script>
7
29
 
8
30
  <template>
@@ -11,22 +33,82 @@ const colorCategories = Object.entries(colors);
11
33
  v-for="[category, shades] in colorCategories"
12
34
  :key="category"
13
35
  >
14
- <h3 class="capitalize">
36
+ <h3 class="capitalize text-xl font-bold mb-4">
15
37
  {{ category }}
16
38
  </h3>
17
- <div class="grid grid-cols-3 md:grid-cols-9">
39
+ <div class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-5 gap-x-6 gap-y-10">
18
40
  <div
19
41
  v-for="(value, name) in shades"
20
42
  :key="name"
21
- class="mb-6"
43
+ class="group relative"
22
44
  >
23
- <div
24
- class="h-12 transition-all"
45
+ <!-- Color Swatch -->
46
+ <button
47
+ class="w-full h-10 rounded-lg shadow-md transition-all duration-200 hover:shadow-xl hover:scale-105 cursor-pointer relative overflow-hidden"
25
48
  :style="`background: ${value}`"
26
- />
27
- <div class="text-sm flex flex-col text-center font-mono text-slate-500">
28
- <span>{{ name }}</span>
29
- <span class="uppercase text-xs">{{ value }}</span>
49
+ :title="`Click to copy hex: ${value}`"
50
+ @click="copyToClipboard(value, `${category}-${name}-hex`)"
51
+ >
52
+ <!-- Copied indicator -->
53
+ <div
54
+ v-if="copiedItem === `${category}-${name}-hex`"
55
+ class="absolute inset-0 bg-black/50 flex items-center justify-center text-white font-bold text-sm"
56
+ >
57
+ ✓ Copied!
58
+ </div>
59
+ </button>
60
+
61
+ <!-- Color Info -->
62
+ <div class="mt-2 space-y-1">
63
+ <!-- Color Name -->
64
+ <div class="text-sm font-medium text-gray-700 text-center">
65
+ {{ name }}
66
+ </div>
67
+
68
+ <!-- Copy Buttons -->
69
+ <div class="flex gap-1 text-xs font-mono">
70
+ <!-- Copy Hex -->
71
+ <button
72
+ class="flex-1 px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-colors text-gray-600 hover:text-gray-900 relative"
73
+ :title="`Copy hex: ${value}`"
74
+ @click="copyToClipboard(value, `${category}-${name}-hex`)"
75
+ >
76
+ <span
77
+ v-if="copiedItem === `${category}-${name}-hex`"
78
+ class="text-green-600"
79
+ >✓</span>
80
+ <span
81
+ v-else
82
+ class="uppercase text-xs"
83
+ >{{ value }}</span>
84
+ </button>
85
+
86
+ <!-- Copy Class Name (text-*) -->
87
+ <button
88
+ class="flex-1 px-2 py-1 bg-blue-100 hover:bg-blue-200 rounded transition-colors text-blue-700 hover:text-blue-900 relative"
89
+ :title="`Copy class: text-${getColorClass(category, name)}`"
90
+ @click="copyToClipboard(`text-${getColorClass(category, name)}`, `${category}-${name}-class`)"
91
+ >
92
+ <span
93
+ v-if="copiedItem === `${category}-${name}-class`"
94
+ class="text-green-600"
95
+ >✓</span>
96
+ <span v-else>text-</span>
97
+ </button>
98
+
99
+ <!-- Copy Class Name (bg-*) -->
100
+ <button
101
+ class="flex-1 px-2 py-1 bg-purple-100 hover:bg-purple-200 rounded transition-colors text-purple-700 hover:text-purple-900 relative"
102
+ :title="`Copy class: bg-${getColorClass(category, name)}`"
103
+ @click="copyToClipboard(`bg-${getColorClass(category, name)}`, `${category}-${name}-bg`)"
104
+ >
105
+ <span
106
+ v-if="copiedItem === `${category}-${name}-bg`"
107
+ class="text-green-600"
108
+ >✓</span>
109
+ <span v-else>bg-</span>
110
+ </button>
111
+ </div>
30
112
  </div>
31
113
  </div>
32
114
  </div>
@@ -1,11 +1,40 @@
1
1
  ---
2
- const { id, open } = Astro.props;
3
2
  import Button from '../components/Button.vue';
3
+
4
+ interface Props {
5
+ id: string;
6
+ open?: string;
7
+ title?: string;
8
+ maxWidth?: string;
9
+ showXButton?: boolean;
10
+ showTrigger?: boolean;
11
+
12
+ // Action buttons
13
+ showActions?: boolean;
14
+ cancelText?: string;
15
+ confirmText?: string;
16
+ confirmPrimary?: boolean;
17
+ }
18
+
19
+ const {
20
+ id,
21
+ open = 'Open modal',
22
+ title,
23
+ maxWidth = '600px',
24
+ showXButton = true,
25
+ showTrigger = true,
26
+ showActions = false,
27
+ cancelText = 'Cancel',
28
+ confirmText = 'Confirm',
29
+ confirmPrimary = true,
30
+ } = Astro.props;
4
31
  ---
5
32
 
6
- <style>
33
+ <style define:vars={{ maxWidth }}>
7
34
  dialog {
8
- @apply fixed top-0 left-0 right-0 bottom-0;
35
+ @apply fixed top-0 left-0 right-0 bottom-0 rounded-lg shadow-xl;
36
+ max-width: var(--maxWidth);
37
+ width: calc(100% - 2rem);
9
38
 
10
39
  &::backdrop {
11
40
  @apply bg-slate-medium/50 fixed;
@@ -15,20 +44,111 @@ import Button from '../components/Button.vue';
15
44
  left: 0px;
16
45
  }
17
46
  }
47
+
48
+ .modal-header {
49
+ @apply relative pb-4 mb-4 border-b border-gray-300;
50
+ }
51
+
52
+ .modal-close-x {
53
+ @apply absolute top-0 right-0 w-8 h-8 flex items-center justify-center rounded-full hover:bg-gray-200 transition-colors cursor-pointer border-0 bg-transparent;
54
+
55
+ &::before,
56
+ &::after {
57
+ content: '';
58
+ position: absolute;
59
+ width: 16px;
60
+ height: 2px;
61
+ background-color: #666;
62
+ }
63
+
64
+ &::before {
65
+ transform: rotate(45deg);
66
+ }
67
+
68
+ &::after {
69
+ transform: rotate(-45deg);
70
+ }
71
+
72
+ &:hover::before,
73
+ &:hover::after {
74
+ background-color: #000;
75
+ }
76
+ }
77
+
78
+ .modal-content {
79
+ @apply mb-4;
80
+ }
81
+
82
+ .modal-actions {
83
+ @apply flex gap-3 justify-end pt-4 border-t border-gray-300;
84
+ }
18
85
  </style>
19
86
 
20
- <Button
21
- primary
22
- onclick={`window.${id}.showModal()`}
23
- >{open}</Button
24
- >
87
+ {showTrigger && (
88
+ <Button
89
+ primary
90
+ onclick={`window.${id}.showModal()`}
91
+ >{open}</Button
92
+ >
93
+ )}
25
94
 
26
95
  <dialog
27
96
  id={id}
28
97
  class="p-6"
29
98
  >
30
- <slot name="main" />
31
- <form method="dialog">
32
- <slot name="close" />
33
- </form>
99
+ <div class="modal-header">
100
+ {title && <h2 class="text-2xl font-bold pr-8">{title}</h2>}
101
+ <slot name="header" />
102
+
103
+ {showXButton && (
104
+ <form method="dialog" class="inline">
105
+ <button
106
+ class="modal-close-x"
107
+ aria-label="Close"
108
+ type="submit"
109
+ />
110
+ </form>
111
+ )}
112
+ </div>
113
+
114
+ <div class="modal-content">
115
+ <slot name="main" />
116
+ <slot />
117
+ </div>
118
+
119
+ {showActions && (
120
+ <div class="modal-actions">
121
+ <slot name="actions">
122
+ <form method="dialog" class="contents">
123
+ <Button>{cancelText}</Button>
124
+ </form>
125
+ <Button primary={confirmPrimary} onclick={`document.getElementById('${id}').dispatchEvent(new CustomEvent('confirm', { detail: { id: '${id}' } }))`}>
126
+ {confirmText}
127
+ </Button>
128
+ </slot>
129
+ </div>
130
+ )}
131
+
132
+ {!showActions && (
133
+ <form method="dialog">
134
+ <slot name="close" />
135
+ </form>
136
+ )}
34
137
  </dialog>
138
+
139
+ <script define:vars={{ id }}>
140
+ // Close on backdrop click
141
+ const dialog = document.getElementById(id);
142
+ dialog?.addEventListener('click', (e) => {
143
+ if (e.target === dialog) {
144
+ dialog.close();
145
+ }
146
+ });
147
+
148
+ // Close on Escape key
149
+ dialog?.addEventListener('keydown', (e) => {
150
+ if (e.key === 'Escape') {
151
+ dialog.close();
152
+ }
153
+ });
154
+ </script>