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.
@@ -6,29 +6,254 @@ import SlimBanner from '../../components/SlimBanner.vue'
6
6
 
7
7
  # SlimBanner
8
8
 
9
- SlimBanner - solidarity with Ukraine.
9
+ A flexible banner component for announcements, alerts, or calls to action. Supports custom messages, links, icons, and toggle states.
10
+
11
+ ## Basic Example (Ukraine Solidarity)
10
12
 
11
13
  <div class="component-preview">
12
14
  <SlimBanner
13
15
  class="w-full drop-shadow"
14
16
  client:load
17
+ message="We stand with our friends and colleagues in Ukraine. To support Ukraine in their time of need visit"
18
+ linkText="this page"
19
+ linkUrl="https://polo.blue/support-ukraine/"
20
+ linkTitle="Support Ukraine"
21
+ showIcon
15
22
  showCloseButton
16
- transition:name="slim-banner"
17
- transition:persist
18
-
23
+ toggleMessage="RUSSIA IS A TERRORIST STATE"
19
24
  />
20
25
  </div>
21
26
 
22
- ```js
27
+ ```astro
23
28
  <SlimBanner
24
29
  class="w-full drop-shadow"
25
30
  client:load
31
+ message="We stand with our friends and colleagues in Ukraine. To support Ukraine in their time of need visit"
32
+ linkText="this page"
33
+ linkUrl="https://polo.blue/support-ukraine/"
34
+ linkTitle="Support Ukraine"
35
+ showIcon
36
+ showCloseButton
37
+ toggleMessage="RUSSIA IS A TERRORIST STATE"
38
+ />
39
+ ```
40
+
41
+ ## Simple Announcement
42
+
43
+ <div class="component-preview">
44
+ <SlimBanner
45
+ class="w-full drop-shadow"
46
+ client:load
47
+ message="🎉 New features are now available!"
48
+ linkText="Learn more"
49
+ linkUrl="/updates"
50
+ showCloseButton={false}
51
+ />
52
+ </div>
53
+
54
+ ```astro
55
+ <SlimBanner
56
+ message="🎉 New features are now available!"
57
+ linkText="Learn more"
58
+ linkUrl="/updates"
59
+ showCloseButton={false}
60
+ />
61
+ ```
62
+
63
+ ## Custom Message with Link
64
+
65
+ <div class="component-preview">
66
+ <SlimBanner
67
+ class="w-full drop-shadow"
68
+ client:load
69
+ message="Limited time offer! Get 50% off all products. "
70
+ linkText="Shop now"
71
+ linkUrl="/shop"
72
+ linkTitle="Visit our shop"
73
+ showCloseButton={false}
74
+ />
75
+ </div>
76
+
77
+ ```astro
78
+ <SlimBanner
79
+ message="Limited time offer! Get 50% off all products. "
80
+ linkText="Shop now"
81
+ linkUrl="/shop"
82
+ linkTitle="Visit our shop"
83
+ showCloseButton={false}
84
+ />
85
+ ```
86
+
87
+ ## With Toggle State
88
+
89
+ <div class="component-preview">
90
+ <SlimBanner
91
+ class="w-full drop-shadow"
92
+ client:load
93
+ message="Join us at the annual car meet this weekend!"
94
+ linkText="See the lineup"
95
+ linkUrl="/events"
96
+ showCloseButton
97
+ toggleMessage="🏁 TORQUE IS EVERYTHING"
98
+ toggleBgClass="bg-red-700"
99
+ toggleTextClass="text-white"
100
+ />
101
+ </div>
102
+
103
+ ```astro
104
+ <SlimBanner
105
+ message="Join us at the annual car meet this weekend!"
106
+ linkText="See the lineup"
107
+ linkUrl="/events"
26
108
  showCloseButton
27
- transition:name="slim-banner"
28
- transition:persist
109
+ toggleMessage="🏁 TORQUE IS EVERYTHING"
110
+ toggleBgClass="bg-red-700"
111
+ toggleTextClass="text-white"
29
112
  />
30
113
  ```
31
114
 
115
+ ## Message Only (No Link)
116
+
117
+ <div class="component-preview">
118
+ <SlimBanner
119
+ class="w-full drop-shadow"
120
+ client:load
121
+ message="Scheduled maintenance: The site will be down on Sunday, 3-5 AM EST."
122
+ linkUrl=""
123
+ showCloseButton={false}
124
+ />
125
+ </div>
126
+
127
+ ```astro
128
+ <SlimBanner
129
+ message="Scheduled maintenance: The site will be down on Sunday, 3-5 AM EST."
130
+ linkUrl=""
131
+ showCloseButton={false}
132
+ />
133
+ ```
134
+
135
+ ## Persistent Banner (LocalStorage)
136
+
137
+ Close the banner permanently using localStorage. It will stay hidden even after page refresh.
138
+
139
+ <div class="component-preview">
140
+ <SlimBanner
141
+ class="w-full drop-shadow"
142
+ client:load
143
+ message="🍪 This website uses cookies to enhance your experience."
144
+ linkText="Learn more"
145
+ linkUrl="/privacy"
146
+ showCloseButton
147
+ persistClose
148
+ storageKey="cookie-banner-closed"
149
+ expirationDays={90}
150
+ />
151
+ </div>
152
+
153
+ ```astro
154
+ <SlimBanner
155
+ message="🍪 This website uses cookies to enhance your experience."
156
+ linkText="Learn more"
157
+ linkUrl="/privacy"
158
+ showCloseButton
159
+ persistClose
160
+ storageKey="cookie-banner-closed"
161
+ expirationDays={90}
162
+ />
163
+ ```
164
+
165
+ **Note:** Once you close this banner, it won't show again for 90 days (or until you clear localStorage).
166
+
167
+ ## Persistent with No Expiration
168
+
169
+ <div class="component-preview">
170
+ <SlimBanner
171
+ class="w-full drop-shadow"
172
+ client:load
173
+ message="📢 Important: This is a one-time announcement."
174
+ linkUrl=""
175
+ showCloseButton
176
+ persistClose
177
+ storageKey="announcement-dismissed"
178
+ expirationDays={0}
179
+ />
180
+ </div>
181
+
182
+ ```astro
183
+ <SlimBanner
184
+ message="📢 Important: This is a one-time announcement."
185
+ linkUrl=""
186
+ showCloseButton
187
+ persistClose
188
+ storageKey="announcement-dismissed"
189
+ expirationDays={0}
190
+ />
191
+ ```
192
+
193
+ **Tip:** Set `expirationDays={0}` to hide the banner forever once closed.
194
+
195
+ ## Props
196
+
197
+ | Prop | Type | Default | Description |
198
+ |------|------|---------|-------------|
199
+ | `message` | String | Ukraine message | Main banner message text |
200
+ | `linkText` | String | `'this page'` | Text for the link |
201
+ | `linkUrl` | String | Ukraine URL | URL for the link (set to empty string for no link) |
202
+ | `linkTitle` | String | `''` | Title attribute for the link |
203
+ | `toggleMessage` | String | `''` | Message to show when close button is clicked (enables toggle) |
204
+ | `toggleBgClass` | String | `'bg-black'` | Background class for toggle state |
205
+ | `toggleTextClass` | String | `'text-white'` | Text color class for toggle state |
206
+ | `showCloseButton` | Boolean | `true` | Show close/toggle button |
207
+ | `closeButtonAriaLabel` | String | `'Toggle'` | Aria label for close button |
208
+ | `showIcon` | Boolean | `false` | Show icon (Ukraine flag by default) |
209
+ | `iconClass` | String | Ukraine flag CSS | Custom CSS class for icon |
210
+ | `persistClose` | Boolean | `false` | Save close state to localStorage |
211
+ | `storageKey` | String | `'slimbanner-closed'` | localStorage key for persistence |
212
+ | `expirationDays` | Number | `30` | Days until localStorage expires (0 = never) |
213
+
214
+ ## Slots
215
+
216
+ | Slot | Description |
217
+ |------|-------------|
218
+ | `default` | Override the entire message content |
219
+ | `icon` | Override the icon/flag |
220
+ | `toggle` | Override the toggle state content |
221
+
222
+ ## Using Slots
223
+
224
+ ```astro
225
+ <SlimBanner client:load>
226
+ <strong slot="default">Custom content</strong> with <em>HTML markup</em>
227
+ </SlimBanner>
228
+ ```
229
+
230
+ ## LocalStorage Management
231
+
232
+ When using `persistClose={true}`, the close state is saved to localStorage. Here's how to manage it:
233
+
234
+ ### Clear specific banner:
235
+ ```javascript
236
+ localStorage.removeItem('cookie-banner-closed');
237
+ ```
238
+
239
+ ### Clear all banners:
240
+ ```javascript
241
+ // If using default key
242
+ localStorage.removeItem('slimbanner-closed');
243
+
244
+ // Or clear all by pattern
245
+ Object.keys(localStorage)
246
+ .filter(key => key.includes('banner'))
247
+ .forEach(key => localStorage.removeItem(key));
248
+ ```
249
+
250
+ ### Check if banner is closed:
251
+ ```javascript
252
+ const data = JSON.parse(localStorage.getItem('cookie-banner-closed'));
253
+ console.log('Banner closed:', data?.closed);
254
+ console.log('Expires at:', new Date(data?.expires));
255
+ ```
256
+
32
257
  ## uno.config.ts / shortcuts
33
258
  ```js
34
259
  ['slimbanner','px-4 sm:px-8 flex items-center justify-center text-xs sm:text-base leading-none relative bg-gray-100 z-2 px-4 py-3 sm:(text-base px-8) text-blue-darker print-hidden'],
@@ -5,6 +5,17 @@ layout: ../../layouts/MainLayout.astro
5
5
  import MainColors from '../../components/MainColors.vue';
6
6
 
7
7
  <h1>Colors</h1>
8
- <p>Base colors.</p>
8
+ <p>Base colors for the design system.</p>
9
9
 
10
- <MainColors class="mt-8" />
10
+ ## How to use
11
+
12
+ Click on any element to copy values to your clipboard:
13
+
14
+ - **Color swatch** - Click the large color box to copy the hex value
15
+ - **Hex button** - Click to copy the hex color code (e.g., `#0040c5`)
16
+ - **text- button** - Click to copy the text color class (e.g., `text-brand-primary`)
17
+ - **bg- button** - Click to copy the background color class (e.g., `bg-brand-primary`)
18
+
19
+ A green checkmark (✓) will appear when copied successfully.
20
+
21
+ <MainColors class="mt-8" client:load />
@@ -87,3 +87,107 @@ Template inspired by [Kevin Powell](https://www.youtube.com/kevinpowell)
87
87
  </section>
88
88
  </main>
89
89
  ```
90
+
91
+ ## Full-width variants
92
+
93
+ Additional utility classes for more flexible full-width layouts.
94
+
95
+ ### Full-width with Flexbox
96
+
97
+ Use `.full-width-flex` when you need flexbox layout across the full width instead of grid.
98
+
99
+ <div class="component-preview" style="display: block">
100
+ <main class="flow content-grid bg-white pt-8">
101
+ <h2>Full-width Flexbox Example</h2>
102
+ <p>The section below uses flexbox for horizontal layout across the full width.</p>
103
+
104
+ <section class="full-width-flex bg-primary text-white section-padding justify-center gap-8">
105
+ <div class="text-center">
106
+ <h3 class="text-2xl font-bold">Feature 1</h3>
107
+ <p>Flexbox layout</p>
108
+ </div>
109
+ <div class="text-center">
110
+ <h3 class="text-2xl font-bold">Feature 2</h3>
111
+ <p>Full width span</p>
112
+ </div>
113
+ <div class="text-center">
114
+ <h3 class="text-2xl font-bold">Feature 3</h3>
115
+ <p>Easy alignment</p>
116
+ </div>
117
+ </section>
118
+
119
+ <p>Back to regular content width.</p>
120
+ </main>
121
+ </div>
122
+
123
+ ```html
124
+ <section class="full-width-flex bg-primary text-white section-padding justify-center gap-8">
125
+ <div class="text-center">
126
+ <h3 class="text-2xl font-bold">Feature 1</h3>
127
+ <p>Flexbox layout</p>
128
+ </div>
129
+ <div class="text-center">
130
+ <h3 class="text-2xl font-bold">Feature 2</h3>
131
+ <p>Full width span</p>
132
+ </div>
133
+ <div class="text-center">
134
+ <h3 class="text-2xl font-bold">Feature 3</h3>
135
+ <p>Easy alignment</p>
136
+ </div>
137
+ </section>
138
+ ```
139
+
140
+ ### Full-width Uncontained
141
+
142
+ Use `.full-width-uncontained` when you want a full-width section where children also span the full width (not constrained to content area).
143
+
144
+ <div class="component-preview" style="display: block">
145
+ <main class="flow content-grid bg-white pt-8">
146
+ <h2>Full-width Uncontained Example</h2>
147
+ <p>The section below has a full-width background and its children span the full width too.</p>
148
+
149
+ <section class="full-width-uncontained bg-gray-100 section-padding">
150
+ <h3 class="bg-primary text-white py-4 text-center">This heading spans the entire width</h3>
151
+ <p class="bg-blue-100 py-4 text-center">This paragraph also spans the entire width</p>
152
+ </section>
153
+
154
+ <p>Back to regular content width.</p>
155
+ </main>
156
+ </div>
157
+
158
+ ```html
159
+ <section class="full-width-uncontained bg-gray-100 section-padding">
160
+ <h3 class="bg-primary text-white py-4 text-center">This heading spans the entire width</h3>
161
+ <p class="bg-blue-100 py-4 text-center">This paragraph also spans the entire width</p>
162
+ </section>
163
+ ```
164
+
165
+ ### Full-width Block
166
+
167
+ Use `.full-width-block` for simple block-level full-width containers without grid or flex layout.
168
+
169
+ <div class="component-preview" style="display: block">
170
+ <main class="flow content-grid bg-white pt-8">
171
+ <h2>Full-width Block Example</h2>
172
+ <p>The image below is wrapped in a full-width-block container.</p>
173
+
174
+ <div class="full-width-block bg-gray-200">
175
+ <img src="https://unsplash.it/1600/300" alt="Full width example" style="width: 100%; display: block;" />
176
+ </div>
177
+
178
+ <p>Back to regular content width.</p>
179
+ </main>
180
+ </div>
181
+
182
+ ```html
183
+ <div class="full-width-block bg-gray-200">
184
+ <img src="https://unsplash.it/1600/300" alt="Full width example" style="width: 100%; display: block;" />
185
+ </div>
186
+ ```
187
+
188
+ ### Comparison
189
+
190
+ - **`.full-width`** - Grid layout with children constrained to content area (default behavior)
191
+ - **`.full-width-flex`** - Flexbox layout across full width
192
+ - **`.full-width-uncontained`** - Grid layout with children spanning full width
193
+ - **`.full-width-block`** - Simple block container at full width
@@ -29,6 +29,28 @@
29
29
  display: grid;
30
30
  grid-template-columns: inherit;
31
31
  }
32
+
33
+ /* Variant: full-width with flexbox layout instead of grid */
34
+ > .full-width-flex {
35
+ grid-column: full-width;
36
+ display: flex;
37
+ }
38
+
39
+ /* Variant: full-width container where children span the full width (not constrained to content area) */
40
+ > .full-width-uncontained {
41
+ grid-column: full-width;
42
+ display: grid;
43
+ grid-template-columns: inherit;
44
+
45
+ > * {
46
+ grid-column: full-width;
47
+ }
48
+ }
49
+
50
+ /* Variant: full-width block (no grid/flex on container, children flow naturally at full width) */
51
+ > .full-width-block {
52
+ grid-column: full-width;
53
+ }
32
54
  }
33
55
 
34
56
  img.full-width {
package/src/utils/text.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export const text2paragraphs = (text: string, firstLineBottomMargin: boolean = false) => {
2
- // return '<p class="mb-2">' + text.split(/[\n\r]+/g).join('</p><p>') + '</p>'
2
+ // Normalize line endings: convert \r\n to \n, then remove any remaining \r
3
+ const normalizedText = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
4
+
3
5
  let out =
4
6
  '<p' +
5
- (firstLineBottomMargin ? 'class="mb-3"' : '') +
7
+ (firstLineBottomMargin ? ' class="mb-3"' : '') +
6
8
  '>' +
7
- text.split('\n').join('</p><p>') +
9
+ normalizedText.split('\n').join('</p><p>') +
8
10
  '</p>';
9
11
  return out.split('<p></p><p>').join('<p class="mt-3">');
10
12
  };
@@ -1,39 +0,0 @@
1
- name: SonarCloud Analysis
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- sonarcloud:
11
- name: SonarCloud
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v5
15
- with:
16
- fetch-depth: 0 # Shallow clones should be disabled for better analysis
17
-
18
- - name: Setup pnpm
19
- uses: pnpm/action-setup@v4
20
- with:
21
- version: 10.17.1
22
-
23
- - name: Setup Node.js
24
- uses: actions/setup-node@v5
25
- with:
26
- node-version: '22'
27
- cache: 'pnpm'
28
-
29
- - name: Install dependencies
30
- run: pnpm install --frozen-lockfile
31
-
32
- - name: Build project
33
- run: pnpm run build
34
-
35
- - name: SonarCloud Scan
36
- uses: SonarSource/sonarcloud-github-action@master
37
- env:
38
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
39
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}