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.
- package/.github/workflows/code-quality.yml +35 -2
- package/CHANGELOG.md +8 -0
- package/package.json +32 -32
- package/src/components/MainColors.vue +91 -9
- package/src/components/Modal.astro +132 -12
- package/src/components/SlimBanner.vue +161 -52
- package/src/pages/components/modal.mdx +178 -30
- package/src/pages/components/slimbanner.mdx +232 -7
- package/src/pages/core/colors.mdx +13 -2
- package/src/pages/core/grid.mdx +104 -0
- package/src/styles/base/grid.css +22 -0
- package/src/utils/text.ts +5 -3
- package/.github/workflows/sonarcloud.yml +0 -39
|
@@ -1,73 +1,182 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { ref } from 'vue';
|
|
2
|
+
import { ref, onMounted } from 'vue';
|
|
3
3
|
|
|
4
4
|
const props = defineProps({
|
|
5
|
+
// Primary state (visible by default)
|
|
6
|
+
message: {
|
|
7
|
+
type: String,
|
|
8
|
+
default: 'We stand with our friends and colleagues in Ukraine. To support Ukraine in their time of need visit',
|
|
9
|
+
},
|
|
10
|
+
linkText: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: 'this page',
|
|
13
|
+
},
|
|
14
|
+
linkUrl: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'https://polo.blue/support-ukraine/',
|
|
17
|
+
},
|
|
18
|
+
linkTitle: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: '',
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Toggle state (shows when close button is clicked)
|
|
24
|
+
toggleMessage: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: '',
|
|
27
|
+
},
|
|
28
|
+
toggleBgClass: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: 'bg-black',
|
|
31
|
+
},
|
|
32
|
+
toggleTextClass: {
|
|
33
|
+
type: String,
|
|
34
|
+
default: 'text-white',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Close button
|
|
5
38
|
showCloseButton: {
|
|
6
39
|
type: Boolean,
|
|
7
40
|
default: true,
|
|
8
|
-
|
|
41
|
+
},
|
|
42
|
+
closeButtonAriaLabel: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: 'Toggle',
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Show flag icon (Ukraine flag by default)
|
|
48
|
+
showIcon: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
52
|
+
iconClass: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: 'inline-block text-4xl w-6 h-3.5 min-w-[1.25rem] mr-3 bg-gradient-to-b stops-[#0057b7_50%,50%,#ffd700_100%]',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// LocalStorage persistence
|
|
58
|
+
persistClose: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
storageKey: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: 'slimbanner-closed',
|
|
65
|
+
},
|
|
66
|
+
// Expiration in days (0 = never expires)
|
|
67
|
+
expirationDays: {
|
|
68
|
+
type: Number,
|
|
69
|
+
default: 30,
|
|
9
70
|
},
|
|
10
71
|
});
|
|
72
|
+
|
|
11
73
|
const isShow = ref(true);
|
|
74
|
+
const isHidden = ref(false);
|
|
75
|
+
|
|
76
|
+
// Check localStorage on mount
|
|
77
|
+
onMounted(() => {
|
|
78
|
+
if (props.persistClose && typeof window !== 'undefined') {
|
|
79
|
+
const stored = localStorage.getItem(props.storageKey);
|
|
80
|
+
if (stored) {
|
|
81
|
+
try {
|
|
82
|
+
const data = JSON.parse(stored);
|
|
83
|
+
const now = new Date().getTime();
|
|
84
|
+
|
|
85
|
+
// Check if expired
|
|
86
|
+
if (props.expirationDays > 0 && data.expires && now > data.expires) {
|
|
87
|
+
// Expired, remove from storage
|
|
88
|
+
localStorage.removeItem(props.storageKey);
|
|
89
|
+
} else if (data.closed) {
|
|
90
|
+
// Still valid, keep hidden
|
|
91
|
+
isHidden.value = true;
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// Invalid data, remove it
|
|
95
|
+
localStorage.removeItem(props.storageKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
12
101
|
const toggleVisibility = () => {
|
|
13
102
|
isShow.value = !isShow.value;
|
|
14
103
|
};
|
|
104
|
+
|
|
105
|
+
const closePermanently = () => {
|
|
106
|
+
isHidden.value = true;
|
|
107
|
+
|
|
108
|
+
if (props.persistClose && typeof window !== 'undefined') {
|
|
109
|
+
const data = {
|
|
110
|
+
closed: true,
|
|
111
|
+
timestamp: new Date().getTime(),
|
|
112
|
+
expires: props.expirationDays > 0
|
|
113
|
+
? new Date().getTime() + (props.expirationDays * 24 * 60 * 60 * 1000)
|
|
114
|
+
: null,
|
|
115
|
+
};
|
|
116
|
+
localStorage.setItem(props.storageKey, JSON.stringify(data));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
15
119
|
</script>
|
|
16
120
|
|
|
17
121
|
<template>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<span
|
|
24
|
-
class="inline-block text-4xl w-6 h-3.5 min-w-[1.25rem] mr-3 bg-gradient-to-b stops-[#0057b7_50%,50%,#ffd700_100%]"
|
|
25
|
-
/>
|
|
26
|
-
<span class="leading-none"
|
|
27
|
-
><span
|
|
28
|
-
data-text="We stand with our friends and colleagues in Ukraine. To support Ukraine in their time of need visit "
|
|
29
|
-
/>
|
|
30
|
-
<a
|
|
31
|
-
href="https://polo.blue/support-ukraine/"
|
|
32
|
-
target="_blank"
|
|
33
|
-
rel="noopener"
|
|
34
|
-
title="Support Ukraine"
|
|
35
|
-
class="underline underline-offset-2 hover:text-blue-wrc"
|
|
36
|
-
>this page</a
|
|
37
|
-
>.
|
|
38
|
-
</span>
|
|
39
|
-
|
|
40
|
-
<button
|
|
41
|
-
v-if="props.showCloseButton"
|
|
42
|
-
class="btn-close text-white"
|
|
43
|
-
aria-label="Toggle"
|
|
44
|
-
@click="toggleVisibility()"
|
|
122
|
+
<template v-if="!isHidden">
|
|
123
|
+
<div
|
|
124
|
+
v-if="isShow"
|
|
125
|
+
data-pagefind-ignore
|
|
126
|
+
class="slimbanner"
|
|
45
127
|
>
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
128
|
+
<slot name="icon">
|
|
129
|
+
<span
|
|
130
|
+
v-if="props.showIcon"
|
|
131
|
+
:class="props.iconClass"
|
|
132
|
+
/>
|
|
133
|
+
</slot>
|
|
134
|
+
|
|
135
|
+
<span class="leading-none inline-flex">
|
|
136
|
+
<slot>
|
|
137
|
+
<span :data-text="props.message"></span>
|
|
138
|
+
<a
|
|
139
|
+
v-if="props.linkUrl"
|
|
140
|
+
:href="props.linkUrl"
|
|
141
|
+
target="_blank"
|
|
142
|
+
rel="noopener"
|
|
143
|
+
:title="props.linkTitle || props.linkText"
|
|
144
|
+
class="underline underline-offset-2 hover:text-blue-wrc ml-1.5"
|
|
145
|
+
:data-text="props.linkText"
|
|
146
|
+
></a>
|
|
147
|
+
<span v-if="props.linkUrl" data-text="."></span>
|
|
148
|
+
</slot>
|
|
149
|
+
</span>
|
|
150
|
+
|
|
151
|
+
<button
|
|
152
|
+
v-if="props.showCloseButton && (props.toggleMessage || props.persistClose)"
|
|
153
|
+
class="btn-close text-white"
|
|
154
|
+
:aria-label="props.closeButtonAriaLabel"
|
|
155
|
+
@click="props.persistClose ? closePermanently() : toggleVisibility()"
|
|
156
|
+
>
|
|
157
|
+
<span class="close close-dark" />
|
|
158
|
+
</button>
|
|
61
159
|
</div>
|
|
62
|
-
<
|
|
63
|
-
v-if="props.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@click="toggleVisibility()"
|
|
160
|
+
<div
|
|
161
|
+
v-else-if="props.toggleMessage"
|
|
162
|
+
data-pagefind-ignore
|
|
163
|
+
:class="`px-4 sm:px-8 py-3 flex items-center justify-center text-xs sm:text-base leading-none relative drop-shadow-md z-2 ${props.toggleBgClass} ${props.toggleTextClass}`"
|
|
67
164
|
>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
165
|
+
<div class="tracking-widest leading-none">
|
|
166
|
+
<slot name="toggle">
|
|
167
|
+
{{ props.toggleMessage }}
|
|
168
|
+
</slot>
|
|
169
|
+
</div>
|
|
170
|
+
<button
|
|
171
|
+
v-if="props.showCloseButton"
|
|
172
|
+
class="btn-close"
|
|
173
|
+
:aria-label="props.closeButtonAriaLabel"
|
|
174
|
+
@click="props.persistClose ? closePermanently() : toggleVisibility()"
|
|
175
|
+
>
|
|
176
|
+
<span :class="props.toggleTextClass === 'text-white' ? 'close close-light' : 'close close-dark'" />
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</template>
|
|
71
180
|
</template>
|
|
72
181
|
|
|
73
182
|
<style>
|
|
@@ -9,56 +9,204 @@ import { Icon } from 'astro-icon/components';
|
|
|
9
9
|
|
|
10
10
|
# Modal
|
|
11
11
|
|
|
12
|
-
Native modal window based on `<dialog>` tag with `::backdrop` CSS pseudo-element
|
|
13
|
-
#
|
|
14
|
-
**Documentation:**
|
|
12
|
+
Native modal window based on `<dialog>` tag with `::backdrop` CSS pseudo-element. Features built-in X close button, action buttons, and flexible configuration.
|
|
15
13
|
|
|
14
|
+
**Documentation:**
|
|
16
15
|
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
|
|
17
16
|
- https://developer.mozilla.org/en-US/docs/Web/CSS/::backdrop
|
|
18
17
|
|
|
19
|
-
## Modal
|
|
18
|
+
## Basic Modal with Title and X Button
|
|
19
|
+
|
|
20
|
+
<div class="component-preview">
|
|
21
|
+
<Modal id="dialog1" open="Open modal" title="Modal Title">
|
|
22
|
+
<p>This modal has a title and built-in X close button in the top-right corner.</p>
|
|
23
|
+
<p>You can also close by clicking the backdrop or pressing Escape.</p>
|
|
24
|
+
</Modal>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
```astro
|
|
28
|
+
<Modal id="dialog1" open="Open modal" title="Modal Title">
|
|
29
|
+
<p>This modal has a title and built-in X close button in the top-right corner.</p>
|
|
30
|
+
<p>You can also close by clicking the backdrop or pressing Escape.</p>
|
|
31
|
+
</Modal>
|
|
32
|
+
```
|
|
20
33
|
|
|
34
|
+
## Modal with Action Buttons
|
|
21
35
|
|
|
22
36
|
<div class="component-preview">
|
|
23
|
-
<Modal
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
<Modal
|
|
38
|
+
id="dialog2"
|
|
39
|
+
open="Delete Item"
|
|
40
|
+
title="Confirm Delete"
|
|
41
|
+
showActions
|
|
42
|
+
cancelText="Cancel"
|
|
43
|
+
confirmText="Delete"
|
|
44
|
+
>
|
|
45
|
+
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
29
46
|
</Modal>
|
|
30
47
|
</div>
|
|
31
48
|
|
|
32
|
-
```
|
|
33
|
-
<Modal
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
```astro
|
|
50
|
+
<Modal
|
|
51
|
+
id="dialog2"
|
|
52
|
+
open="Delete Item"
|
|
53
|
+
title="Confirm Delete"
|
|
54
|
+
showActions
|
|
55
|
+
cancelText="Cancel"
|
|
56
|
+
confirmText="Delete"
|
|
57
|
+
>
|
|
58
|
+
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
40
59
|
</Modal>
|
|
41
60
|
```
|
|
42
61
|
|
|
43
|
-
## Modal with
|
|
62
|
+
## Modal with Custom Actions
|
|
44
63
|
|
|
45
64
|
<div class="component-preview">
|
|
46
|
-
<Modal
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
<Modal
|
|
66
|
+
id="dialog3"
|
|
67
|
+
open="Save Changes"
|
|
68
|
+
title="Save Document"
|
|
69
|
+
showActions
|
|
70
|
+
>
|
|
71
|
+
<p>Would you like to save your changes before closing?</p>
|
|
72
|
+
|
|
73
|
+
<div slot="actions" class="flex gap-3 justify-end">
|
|
74
|
+
<form method="dialog" class="contents">
|
|
75
|
+
<Button>Don't Save</Button>
|
|
76
|
+
</form>
|
|
77
|
+
<form method="dialog" class="contents">
|
|
78
|
+
<Button primary>Save</Button>
|
|
79
|
+
</form>
|
|
80
|
+
</div>
|
|
81
|
+
</Modal>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
```astro
|
|
85
|
+
<Modal
|
|
86
|
+
id="dialog3"
|
|
87
|
+
open="Save Changes"
|
|
88
|
+
title="Save Document"
|
|
89
|
+
showActions
|
|
90
|
+
>
|
|
91
|
+
<p>Would you like to save your changes before closing?</p>
|
|
92
|
+
|
|
93
|
+
<div slot="actions" class="flex gap-3 justify-end">
|
|
94
|
+
<form method="dialog" class="contents">
|
|
95
|
+
<Button>Don't Save</Button>
|
|
96
|
+
</form>
|
|
97
|
+
<form method="dialog" class="contents">
|
|
98
|
+
<Button primary>Save</Button>
|
|
99
|
+
</form>
|
|
100
|
+
</div>
|
|
101
|
+
</Modal>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Modal with Custom Width
|
|
105
|
+
|
|
106
|
+
<div class="component-preview">
|
|
107
|
+
<Modal
|
|
108
|
+
id="dialog4"
|
|
109
|
+
open="Wide Modal"
|
|
110
|
+
title="Large Content"
|
|
111
|
+
maxWidth="900px"
|
|
112
|
+
>
|
|
113
|
+
<p>This modal is wider than the default. Useful for forms or detailed content.</p>
|
|
114
|
+
<div class="grid grid-cols-2 gap-4 mt-4">
|
|
115
|
+
<div class="p-4 bg-gray-100 rounded">Column 1</div>
|
|
116
|
+
<div class="p-4 bg-gray-100 rounded">Column 2</div>
|
|
117
|
+
</div>
|
|
118
|
+
</Modal>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
```astro
|
|
122
|
+
<Modal
|
|
123
|
+
id="dialog4"
|
|
124
|
+
open="Wide Modal"
|
|
125
|
+
title="Large Content"
|
|
126
|
+
maxWidth="900px"
|
|
127
|
+
>
|
|
128
|
+
<p>This modal is wider than the default. Useful for forms or detailed content.</p>
|
|
129
|
+
</Modal>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Modal without X Button (Legacy Style)
|
|
133
|
+
|
|
134
|
+
<div class="component-preview">
|
|
135
|
+
<Modal id="dialog5" open="Open modal" title="Custom Close" showXButton={false}>
|
|
136
|
+
<p>This modal doesn't have the X button.</p>
|
|
137
|
+
<p>You need to provide your own close button.</p>
|
|
138
|
+
<Button primary slot="close" class="mt-4">Close Modal</Button>
|
|
51
139
|
</Modal>
|
|
52
140
|
</div>
|
|
53
141
|
|
|
54
|
-
```
|
|
55
|
-
<Modal id="
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
142
|
+
```astro
|
|
143
|
+
<Modal id="dialog5" open="Open modal" title="Custom Close" showXButton={false}>
|
|
144
|
+
<p>This modal doesn't have the X button.</p>
|
|
145
|
+
<Button primary slot="close" class="mt-4">Close Modal</Button>
|
|
146
|
+
</Modal>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Modal with Custom Close Button (Icon)
|
|
150
|
+
|
|
151
|
+
<div class="component-preview">
|
|
152
|
+
<Modal id="dialog6" open="Open modal" title="Icon Close" showXButton={false}>
|
|
153
|
+
<p>Some extra content you would like here</p>
|
|
154
|
+
<Button primary slot="close" class="mt-4"><Icon name="octicon:x-24" /></Button>
|
|
155
|
+
</Modal>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
```astro
|
|
159
|
+
<Modal id="dialog6" open="Open modal" title="Icon Close" showXButton={false}>
|
|
160
|
+
<p>Some extra content you would like here</p>
|
|
60
161
|
<Button primary slot="close" class="mt-4"><Icon name="octicon:x-24" /></Button>
|
|
61
162
|
</Modal>
|
|
62
163
|
```
|
|
63
164
|
|
|
165
|
+
## Handling Confirm Events
|
|
166
|
+
|
|
167
|
+
You can listen for the `confirm` event when using action buttons:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
document.getElementById('dialog2').addEventListener('confirm', (e) => {
|
|
171
|
+
console.log('Confirmed!', e.detail);
|
|
172
|
+
// Perform your action here
|
|
173
|
+
document.getElementById('dialog2').close();
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Props
|
|
178
|
+
|
|
179
|
+
| Prop | Type | Default | Description |
|
|
180
|
+
|------|------|---------|-------------|
|
|
181
|
+
| `id` | string | required | Unique identifier for the modal |
|
|
182
|
+
| `open` | string | `'Open modal'` | Text for the trigger button |
|
|
183
|
+
| `title` | string | `undefined` | Modal title (shown in header) |
|
|
184
|
+
| `maxWidth` | string | `'600px'` | Maximum width of the modal |
|
|
185
|
+
| `showXButton` | boolean | `true` | Show X close button in top-right |
|
|
186
|
+
| `showTrigger` | boolean | `true` | Show the trigger button |
|
|
187
|
+
| `showActions` | boolean | `false` | Show action buttons footer |
|
|
188
|
+
| `cancelText` | string | `'Cancel'` | Text for cancel button |
|
|
189
|
+
| `confirmText` | string | `'Confirm'` | Text for confirm button |
|
|
190
|
+
| `confirmPrimary` | boolean | `true` | Make confirm button primary style |
|
|
191
|
+
|
|
192
|
+
## Slots
|
|
193
|
+
|
|
194
|
+
| Slot | Description |
|
|
195
|
+
|------|-------------|
|
|
196
|
+
| `default` | Main modal content (alias for `main` slot) |
|
|
197
|
+
| `main` | Main modal content |
|
|
198
|
+
| `header` | Custom header content (replaces title) |
|
|
199
|
+
| `actions` | Custom action buttons (when `showActions` is true) |
|
|
200
|
+
| `close` | Custom close button (when `showActions` is false) |
|
|
201
|
+
|
|
202
|
+
## Features
|
|
203
|
+
|
|
204
|
+
- ✅ Built-in X close button in top-right corner
|
|
205
|
+
- ✅ Click backdrop to close
|
|
206
|
+
- ✅ Press Escape to close
|
|
207
|
+
- ✅ Built-in action buttons (Cancel/Confirm)
|
|
208
|
+
- ✅ Customizable width
|
|
209
|
+
- ✅ TypeScript support
|
|
210
|
+
- ✅ Fully accessible with ARIA labels
|
|
211
|
+
|
|
64
212
|
|