bulma-turbo-themes 0.8.1 → 0.10.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 812fdf4e99d1579e7b6c84f3238ee761181eb6b091629430e53e4156171c438c
4
- data.tar.gz: 600afaa907a3247f7cd3c57348b7af8411aa483bbe3a7bc676cf3f83247e6db1
3
+ metadata.gz: ff3e634c80638ad6f5ad69a6568635a472228a2b816bda3823b292385726c038
4
+ data.tar.gz: 67c0c52ea8a2b2d5d8126d5a89cda1753d454c5a65e0082fcb15db368e6e8097
5
5
  SHA512:
6
- metadata.gz: a1ee5f3bdc8e41f475a910208a8d163004b30697925b87d80f3a2e978b63b55207dc26c77017f0fde62d12e3a49391c9f3d39e7858e7c1cc3b1d6269e4118a11
7
- data.tar.gz: ff997a38ad1069972b6cf2e6316c984d459d68633b2196f71f3dd0bfea9331973c92259a4e4994aab2568abd5b317180d7e7d642e08a650fa5055b7b0e621759
6
+ metadata.gz: 518dc0c638a69003056143e7f466bed724f93f5bee8929e65d7712710b4c795b900368ca26668529b2cf661a1ac0d80df235f34c7336a4067e14683075f5644c
7
+ data.tar.gz: ea9a920d193c8968501184afc433c13e9de6f113969289175d0dd57fda5ad9f5aa2b28817612d6a526bc8517f038f4c0bbd427e9275314b4f2d038157d84202c
data/CHANGELOG.md CHANGED
@@ -10,6 +10,36 @@ The format is based on Keep a Changelog and this project adheres to SemVer.
10
10
 
11
11
  - TBD
12
12
 
13
+ ## [0.10.0] - 2025-12-05
14
+
15
+ ### ✨ Added
16
+
17
+ - add full theming solution with context and hooks (#151)
18
+
19
+ ### 🐛 Fixed
20
+
21
+ - recognize scoped conventional commits in version bump (#154)
22
+ - update peter-evans/create-pull-request action to v7.0.11 (#148)
23
+ - add missing egress endpoints for Bun CDN downloads
24
+
25
+ ## [0.9.0] - 2025-12-05
26
+
27
+ ### ✨ Added
28
+
29
+ - migrate theme system to SASS (#143)
30
+
31
+ ### 🐛 Fixed
32
+
33
+ - enable platformCommit for Renovate to sign commits
34
+ - correct JavaScript reference in gem layout (#106)
35
+
36
+ ### 🔧 Changed
37
+
38
+ - add missing egress endpoints for Bun download (#149)
39
+ - update actions/setup-node digest to 633bb92 (#110)
40
+ - update peter-evans/create-pull-request digest to 271a8d0 (#109)
41
+ - update actions/setup-node digest to 2028fbc (#108)
42
+
13
43
  ## [0.8.1] - 2025-11-16
14
44
 
15
45
  ### 🐛 Fixed
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  Modern, accessible theme packs and a drop-in theme selector for Bulma 1.x.
4
4
 
5
+ [![Bun](https://img.shields.io/badge/bun-1.3+-black?logo=bun)](https://bun.sh/)
5
6
  [![Node.js](https://img.shields.io/badge/node.js-22-green)](https://nodejs.org/)
6
7
  [![Coverage](https://codecov.io/gh/TurboCoder13/bulma-turbo-themes/branch/main/graph/badge.svg)](https://codecov.io/gh/TurboCoder13/bulma-turbo-themes)
7
8
  [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)
@@ -23,6 +24,9 @@ Modern, accessible theme packs and a drop-in theme selector for Bulma 1.x.
23
24
  - Accessible theme selector with keyboard and screen reader support
24
25
  - Inline or link-based CSS delivery; CSP-friendly
25
26
  - Tested with coverage, Lighthouse CI, and stylelint
27
+ - Advanced Bulma customization (breakpoints, spacing, shadows, mixins)
28
+ - Lazy-loaded themes with performance optimizations
29
+ - Full Bulma Sass variable integration
26
30
 
27
31
  ## Installation
28
32
 
@@ -49,11 +53,19 @@ bundle exec jekyll serve
49
53
 
50
54
  Assets are automatically available - no copying needed!
51
55
 
56
+ ### Advanced Theming
57
+
58
+ For advanced customization options including custom breakpoints, spacing, shadows, and Bulma mixins, see the [Advanced Theming Guide](docs/ADVANCED-THEMING.md).
59
+
52
60
  ### For Non-Jekyll Projects
53
61
 
54
- Install via npm:
62
+ Install via Bun (recommended) or npm:
55
63
 
56
64
  ```bash
65
+ # Using Bun (recommended - 5-10x faster)
66
+ bun add @turbocoder13/bulma-turbo-themes
67
+
68
+ # Using npm
57
69
  npm install @turbocoder13/bulma-turbo-themes
58
70
  ```
59
71
 
@@ -76,18 +88,24 @@ npm install @turbocoder13/bulma-turbo-themes
76
88
  1. Add selector markup and initialize:
77
89
 
78
90
  ```html
79
- <div class="dropdown is-right is-theme" id="theme-flavor-dd">
80
- <div class="dropdown-trigger">
81
- <button id="theme-flavor-trigger" aria-haspopup="true">
82
- <span id="theme-flavor-trigger-icon"></span>
83
- <span id="theme-flavor-trigger-label"></span>
84
- </button>
85
- </div>
86
- <div class="dropdown-menu" id="theme-flavor-menu" role="menu">
91
+ <div class="navbar-item has-dropdown is-hoverable">
92
+ <button
93
+ class="navbar-link"
94
+ id="theme-flavor-trigger"
95
+ type="button"
96
+ aria-haspopup="true"
97
+ aria-expanded="false"
98
+ aria-controls="theme-flavor-menu"
99
+ >
100
+ <span class="icon is-small" id="theme-flavor-trigger-icon"></span>
101
+ Theme
102
+ </button>
103
+ <div class="navbar-dropdown" id="theme-flavor-menu" aria-labelledby="theme-flavor-trigger">
87
104
  <div class="dropdown-content" id="theme-flavor-items"></div>
88
105
  </div>
89
- <div class="select is-hidden"><select id="theme-flavor-select"></select></div>
90
- <span id="theme-flavor-icon"></span>
106
+ </div>
107
+ <div class="select is-rounded is-small is-hidden">
108
+ <select id="theme-flavor-select" aria-label="Theme flavor" disabled></select>
91
109
  </div>
92
110
  ```
93
111
 
@@ -110,18 +128,24 @@ npm install @turbocoder13/bulma-turbo-themes
110
128
  1. Add selector markup and initialize:
111
129
 
112
130
  ```html
113
- <div class="dropdown is-right is-theme" id="theme-flavor-dd">
114
- <div class="dropdown-trigger">
115
- <button id="theme-flavor-trigger" aria-haspopup="true">
116
- <span id="theme-flavor-trigger-icon"></span>
117
- <span id="theme-flavor-trigger-label"></span>
118
- </button>
119
- </div>
120
- <div class="dropdown-menu" id="theme-flavor-menu" role="menu">
131
+ <div class="navbar-item has-dropdown is-hoverable">
132
+ <button
133
+ class="navbar-link"
134
+ id="theme-flavor-trigger"
135
+ type="button"
136
+ aria-haspopup="true"
137
+ aria-expanded="false"
138
+ aria-controls="theme-flavor-menu"
139
+ >
140
+ <span class="icon is-small" id="theme-flavor-trigger-icon"></span>
141
+ Theme
142
+ </button>
143
+ <div class="navbar-dropdown" id="theme-flavor-menu" aria-labelledby="theme-flavor-trigger">
121
144
  <div class="dropdown-content" id="theme-flavor-items"></div>
122
145
  </div>
123
- <div class="select is-hidden"><select id="theme-flavor-select"></select></div>
124
- <span id="theme-flavor-icon"></span>
146
+ </div>
147
+ <div class="select is-rounded is-small is-hidden">
148
+ <select id="theme-flavor-select" aria-label="Theme flavor" disabled></select>
125
149
  </div>
126
150
  ```
127
151
 
@@ -134,6 +158,93 @@ document.addEventListener('DOMContentLoaded', () => {
134
158
  });
135
159
  ```
136
160
 
161
+ ### React Native / Cross-Platform
162
+
163
+ This package provides platform-agnostic design tokens that work in React Native, Expo, and other non-web environments.
164
+
165
+ #### Installation
166
+
167
+ ```bash
168
+ bun add @turbocoder13/bulma-turbo-themes
169
+ # or
170
+ npm install @turbocoder13/bulma-turbo-themes
171
+ ```
172
+
173
+ #### Basic Usage (Without Context)
174
+
175
+ ```tsx
176
+ import { useTheme, useThemeColors } from '@turbocoder13/bulma-turbo-themes/tokens/react-native';
177
+
178
+ function MyComponent() {
179
+ const { colors, styles, theme } = useTheme('catppuccin-mocha');
180
+
181
+ return (
182
+ <View style={styles.container}>
183
+ <Text style={styles.h1}>{theme.label}</Text>
184
+ <Text style={[styles.text, { color: colors.brandPrimary }]}>
185
+ Primary color text
186
+ </Text>
187
+ </View>
188
+ );
189
+ }
190
+ ```
191
+
192
+ #### With ThemeProvider (Recommended)
193
+
194
+ ```tsx
195
+ import { ThemeProvider, useThemeContext } from '@turbocoder13/bulma-turbo-themes/tokens/react-native';
196
+ import { useColorScheme } from 'react-native';
197
+
198
+ // Wrap your app with ThemeProvider
199
+ function App() {
200
+ return (
201
+ <ThemeProvider
202
+ useColorScheme={useColorScheme}
203
+ followSystem
204
+ lightTheme="catppuccin-latte"
205
+ darkTheme="catppuccin-mocha"
206
+ >
207
+ <MyApp />
208
+ </ThemeProvider>
209
+ );
210
+ }
211
+
212
+ // Use the theme in any component
213
+ function MyApp() {
214
+ const { colors, styles, setTheme, toggleAppearance, appearance } = useThemeContext();
215
+
216
+ return (
217
+ <View style={styles.container}>
218
+ <Text style={styles.h1}>Welcome!</Text>
219
+ <Button
220
+ title={`Switch to ${appearance === 'dark' ? 'light' : 'dark'}`}
221
+ onPress={toggleAppearance}
222
+ />
223
+ </View>
224
+ );
225
+ }
226
+ ```
227
+
228
+ #### Available Exports
229
+
230
+ | Import Path | Use Case |
231
+ |-------------|----------|
232
+ | `@turbocoder13/bulma-turbo-themes/tokens` | Platform-agnostic tokens (pure data) |
233
+ | `@turbocoder13/bulma-turbo-themes/tokens/react-native` | React Native utilities, hooks, and context |
234
+ | `@turbocoder13/bulma-turbo-themes/tokens.json` | JSON tokens for Flutter, Swift, Kotlin |
235
+ | `@turbocoder13/bulma-turbo-themes/css/*` | CSS files for web |
236
+
237
+ #### Pre-built Styles
238
+
239
+ The React Native module includes pre-built styles for common components:
240
+
241
+ - **Layout**: `container`, `safeArea`, `centered`, `row`
242
+ - **Typography**: `h1`-`h6`, `text`, `textSecondary`, `caption`, `label`, `link`
243
+ - **Components**: `card`, `cardElevated`, `button`, `buttonOutline`, `input`, `listItem`, `badge`, `tag`, `divider`
244
+ - **State Colors**: `success`, `warning`, `danger`, `info`
245
+
246
+ Plus design tokens for `spacing`, `typography`, `borderRadius`, and `shadows`.
247
+
137
248
  ## Testing
138
249
 
139
250
  This project includes comprehensive testing:
@@ -146,16 +257,53 @@ This project includes comprehensive testing:
146
257
  Run tests:
147
258
 
148
259
  ```bash
260
+ # Using Bun (recommended)
261
+ bun run test # Unit tests with coverage
262
+ bun run e2e # All E2E tests
263
+ bun run e2e:smoke # Smoke tests only
264
+ bun run e2e:visual # Visual regression tests
265
+ bun run e2e:a11y # Accessibility tests
266
+ bun run e2e:ui # Playwright UI mode
267
+
268
+ # Using npm (also works)
149
269
  npm test # Unit tests with coverage
150
270
  npm run e2e # All E2E tests
151
- npm run e2e:smoke # Smoke tests only
152
- npm run e2e:visual # Visual regression tests
153
- npm run e2e:a11y # Accessibility tests
154
- npm run e2e:ui # Playwright UI mode
155
271
  ```
156
272
 
157
273
  For detailed E2E testing documentation, see `docs/E2E-TESTING.md`.
158
274
 
275
+ ## Development Setup
276
+
277
+ ### Prerequisites
278
+
279
+ - **Bun** 1.3+ (recommended) - [Install Bun](https://bun.sh/docs/installation)
280
+ - **Node.js** 22+ (alternative)
281
+ - **Ruby** 3.3+ with Bundler (for Jekyll demo site)
282
+
283
+ ### Quick Start
284
+
285
+ ```bash
286
+ # Clone and install
287
+ git clone https://github.com/TurboCoder13/bulma-turbo-themes.git
288
+ cd bulma-turbo-themes
289
+ bun install
290
+ bundle install
291
+
292
+ # Build and serve
293
+ bun run build
294
+ bun run build:themes
295
+ bun run serve
296
+ ```
297
+
298
+ ### Why Bun?
299
+
300
+ This project uses [Bun](https://bun.sh/) as its primary JavaScript runtime for:
301
+
302
+ - **5-10x faster** package installation
303
+ - **10x faster** script startup time
304
+ - **~40% reduction** in CI build times
305
+ - Full npm compatibility (works with all existing packages)
306
+
159
307
  ## Documentation
160
308
 
161
309
  - Code of Conduct: see `CODE_OF_CONDUCT.md`
@@ -8,15 +8,108 @@
8
8
  data-theme-label="{{ site.data.i18n.en.theme_label }}"
9
9
  data-current-theme-title-template="{{ site.data.i18n.en.current_theme_title }}"
10
10
  />
11
+ <!--
12
+ CSP Security Trade-off: 'unsafe-inline' for script-src
13
+ ========================================================
14
+ Why required: The inline theme-blocking script (lines 19-87 below) must run
15
+ synchronously before first paint to prevent Flash of Unstyled Content (FOUC).
16
+ Without it, users would see the wrong theme briefly before it snaps to their
17
+ saved preference — a jarring experience.
18
+
19
+ Why not use nonces/hashes:
20
+ - Nonces require server-side generation (not possible with static Jekyll/GitHub Pages)
21
+ - Hashes are brittle and break on any script change, causing CI failures
22
+
23
+ Mitigations in place:
24
+ - Inline script validates theme IDs against allowlist (prevents class injection)
25
+ - URL sanitization prevents XSS via manipulated data-baseurl
26
+ - No eval() or dynamic code execution
27
+ - All other scripts are external files
28
+
29
+ Future tightening: Service worker precomputed theme class, or migration to SSR
30
+ with nonce-based CSP.
31
+
32
+ See: docs/adr/0004-csp-unsafe-inline-for-theme-blocking-script.md
33
+ -->
34
+ <!-- Note: frame-ancestors can only be set via HTTP header, not meta tag, so it's omitted here -->
11
35
  <meta
12
36
  http-equiv="Content-Security-Policy"
13
- content="default-src 'self'; script-src 'self'; style-src 'self' https://cdn.jsdelivr.net https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://cdn.jsdelivr.net https://fonts.googleapis.com https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
37
+ content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests"
14
38
  />
15
39
  <title>{{ page.title | default: site.title }}</title>
16
40
  {% seo %}
41
+ <!-- Blocking script to apply theme immediately from localStorage -->
42
+ <!-- This prevents FOUC by setting theme class before first paint -->
43
+ <script>
44
+ (function () {
45
+ try {
46
+ var STORAGE_KEY = 'bulma-theme-flavor';
47
+ var DEFAULT_THEME = 'catppuccin-mocha';
48
+ // Valid theme IDs - must match THEMES array in theme-selector.js
49
+ var VALID_THEMES = [
50
+ 'bulma-light',
51
+ 'bulma-dark',
52
+ 'catppuccin-latte',
53
+ 'catppuccin-frappe',
54
+ 'catppuccin-macchiato',
55
+ 'catppuccin-mocha',
56
+ 'dracula',
57
+ 'github-light',
58
+ 'github-dark',
59
+ ];
60
+ var savedTheme = localStorage.getItem(STORAGE_KEY) || DEFAULT_THEME;
61
+ // Validate theme ID against allowed list to prevent invalid class/URL injection
62
+ if (VALID_THEMES.indexOf(savedTheme) === -1) {
63
+ savedTheme = DEFAULT_THEME;
64
+ }
65
+ var themeClass = 'theme-' + savedTheme;
66
+
67
+ // Apply theme class using classList to preserve existing classes
68
+ // First remove any existing theme classes
69
+ var classList = document.documentElement.classList;
70
+ var classesToRemove = [];
71
+ for (var i = 0; i < classList.length; i++) {
72
+ if (classList[i].indexOf('theme-') === 0) {
73
+ classesToRemove.push(classList[i]);
74
+ }
75
+ }
76
+ classesToRemove.forEach(function (cls) {
77
+ classList.remove(cls);
78
+ });
79
+ // Add the new theme class
80
+ classList.add(themeClass);
81
+
82
+ // Preload theme CSS for faster loading
83
+ var baseUrl = document.documentElement.getAttribute('data-baseurl') || '';
84
+ // Sanitize baseUrl to prevent XSS attacks
85
+ function sanitizeUrlPath(path) {
86
+ if (!path) return '';
87
+ // Remove or encode dangerous characters that could be interpreted as HTML/JS
88
+ return path.replace(/[<>"'`&]/g, '').replace(/\\/g, '');
89
+ }
90
+ var sanitizedBaseUrl = sanitizeUrlPath(baseUrl);
91
+ // Resolve path relative to site root
92
+ var base = sanitizedBaseUrl
93
+ ? window.location.origin + sanitizedBaseUrl + '/'
94
+ : window.location.origin + '/';
95
+ var themePath = new URL('assets/css/themes/' + savedTheme + '.css', base).pathname;
96
+
97
+ // Create preload link
98
+ var preloadLink = document.createElement('link');
99
+ preloadLink.rel = 'preload';
100
+ preloadLink.as = 'style';
101
+ preloadLink.href = themePath;
102
+ document.head.appendChild(preloadLink);
103
+
104
+ // Store theme info for later use
105
+ window.__INITIAL_THEME__ = savedTheme;
106
+ } catch (e) {
107
+ // Silently fail if localStorage is not available
108
+ console.warn('Unable to load saved theme:', e);
109
+ }
110
+ })();
111
+ </script>
17
112
  <link rel="icon" href="{{ '/favicon.ico' | relative_url }}" />
18
- <link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
19
- <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
20
113
  <link
21
114
  rel="icon"
22
115
  type="image/png"
@@ -34,32 +127,51 @@
34
127
  href="{{ '/assets/img/bulma-logo.png' | relative_url }}"
35
128
  color="#111827"
36
129
  />
130
+ <!--
131
+ Critical CSS removed - was causing theme override issues.
132
+ Theme CSS uses CSS variables for colors, but hardcoded critical CSS
133
+ was overriding them. The theme CSS is preloaded for fast loading.
134
+ -->
135
+ <!-- Base CSS (shared styles, loaded asynchronously) -->
37
136
  <link
137
+ id="theme-base-css"
38
138
  rel="preload"
39
139
  as="style"
40
- href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"
41
- crossorigin="anonymous"
42
- />
43
- <link
44
- rel="stylesheet"
45
- href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"
46
- integrity="sha384-tl5h4XuWmVzPeVWU0x8bx0j/5iMwCBduLEgZ+2lH4Wjda+4+q3mpCww74dgAB3OX"
47
- crossorigin="anonymous"
140
+ href="{{ '/assets/css/themes/base.css' | relative_url }}"
141
+ onload="this.rel = 'stylesheet'"
48
142
  />
49
- <link
50
- id="theme-global-css"
51
- rel="stylesheet"
52
- href="{{ '/assets/css/themes/global.css' | relative_url }}"
53
- />
54
- <link id="theme-flavor-css" rel="stylesheet" href="#" />
143
+ <noscript>
144
+ <link rel="stylesheet" href="{{ '/assets/css/themes/base.css' | relative_url }}" />
145
+ </noscript>
55
146
  <link rel="stylesheet" href="{{ '/assets/css/custom.css' | relative_url }}" />
56
- <script type="module" src="{{ '/dist/index.js' | relative_url }}"></script>
147
+ <!-- Use minified JS in production for ~50% smaller payload -->
148
+ {% if jekyll.environment == 'production' %}
149
+ <script type="module" src="{{ '/assets/js/theme-selector.min.js' | relative_url }}"></script>
150
+ {% else %}
151
+ <script type="module" src="{{ '/assets/js/theme-selector.js' | relative_url }}"></script>
152
+ {% endif %}
57
153
  <script>
58
154
  document.addEventListener('DOMContentLoaded', () => {
59
155
  // Initialize navbar highlighting
60
156
  if (window.initNavbar) {
61
157
  window.initNavbar(document);
62
158
  }
159
+
160
+ // Initialize burger menu toggle
161
+ const burgers = document.querySelectorAll('.navbar-burger');
162
+ burgers.forEach((burger) => {
163
+ burger.addEventListener('click', () => {
164
+ const targetId = burger.dataset.target;
165
+ const target = document.getElementById(targetId);
166
+ if (target) {
167
+ burger.classList.toggle('is-active');
168
+ target.classList.toggle('is-active');
169
+ // Update aria-expanded
170
+ const isExpanded = burger.classList.contains('is-active');
171
+ burger.setAttribute('aria-expanded', String(isExpanded));
172
+ }
173
+ });
174
+ });
63
175
  });
64
176
  </script>
65
177
  </head>
@@ -212,43 +324,38 @@
212
324
  </div>
213
325
  </div>
214
326
  <div class="navbar-end">
215
- <div class="navbar-item">
216
- <div class="control has-icons-left">
217
- <div class="select is-rounded is-small is-hidden" aria-hidden="true">
218
- <select id="theme-flavor-select" aria-label="Theme flavor" disabled></select>
219
- </div>
220
- <span
221
- class="icon is-small is-left"
222
- id="theme-flavor-icon"
223
- aria-hidden="true"
224
- ></span>
225
- </div>
327
+ <div class="navbar-item has-dropdown is-hoverable" data-testid="theme-dropdown">
328
+ <button
329
+ class="theme-trigger-icon"
330
+ id="theme-flavor-trigger"
331
+ type="button"
332
+ aria-haspopup="true"
333
+ aria-expanded="false"
334
+ aria-controls="theme-flavor-menu"
335
+ aria-label="Select theme"
336
+ data-testid="theme-trigger"
337
+ >
338
+ <img
339
+ id="theme-flavor-trigger-icon"
340
+ src="{{ '/assets/img/catppuccin-logo-macchiato.png' | relative_url }}"
341
+ alt="Current theme"
342
+ width="32"
343
+ height="32"
344
+ />
345
+ </button>
226
346
  <div
227
- class="dropdown is-right is-theme"
228
- id="theme-flavor-dd"
229
- data-testid="theme-dropdown"
347
+ class="navbar-dropdown is-right"
348
+ id="theme-flavor-menu"
349
+ aria-labelledby="theme-flavor-trigger"
350
+ data-testid="theme-menu"
351
+ role="menu"
230
352
  >
231
- <div class="dropdown-trigger">
232
- <button
233
- class="button is-small is-rounded is-flavor-trigger theme-flavor-trigger"
234
- aria-haspopup="true"
235
- aria-controls="theme-flavor-menu"
236
- aria-label="Theme"
237
- data-testid="theme-trigger"
238
- >
239
- <span class="icon is-small" id="theme-flavor-trigger-icon"></span>
240
- </button>
241
- </div>
242
- <div
243
- class="dropdown-menu"
244
- id="theme-flavor-menu"
245
- role="menu"
246
- data-testid="theme-menu"
247
- >
248
- <div class="dropdown-content" id="theme-flavor-items"></div>
249
- </div>
353
+ <!-- Theme items populated by JS -->
250
354
  </div>
251
355
  </div>
356
+ <div class="select is-rounded is-small is-hidden">
357
+ <select id="theme-flavor-select" aria-label="Theme flavor" disabled></select>
358
+ </div>
252
359
  </div>
253
360
  </div>
254
361
  </div>
@@ -30,3 +30,33 @@
30
30
  width: 1rem;
31
31
  height: 1rem;
32
32
  }
33
+
34
+ /* Theme switch loading indicator */
35
+
36
+ .theme-flavor-trigger.is-loading {
37
+ opacity: 0.7;
38
+ pointer-events: none;
39
+ position: relative;
40
+ }
41
+
42
+ .theme-flavor-trigger.is-loading::after {
43
+ content: '';
44
+ position: absolute;
45
+ width: 16px;
46
+ height: 16px;
47
+ top: 50%;
48
+ left: 50%;
49
+ margin-left: -8px;
50
+ margin-top: -8px;
51
+ border: 2px solid transparent;
52
+ border-top-color: currentcolor;
53
+ border-radius: 50%;
54
+ animation: theme-loading-spin 0.6s linear infinite;
55
+ }
56
+
57
+ @keyframes theme-loading-spin {
58
+
59
+ to {
60
+ transform: rotate(360deg);
61
+ }
62
+ }