maverick-wave 4.27.0 → 5.0.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.
- package/.claude/settings.local.json +29 -1
- package/.claude/skills/mw-maverick-wave/SKILL.md +42 -25
- package/.claude/skills/mw-maverick-wave/examples/angular-services.md +16 -4
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +13 -4
- package/.claude/skills/mw-maverick-wave/references/components.md +90 -5
- package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
- package/.claude/skills/mw-maverick-wave/references/javascript.md +18 -4
- package/.claude/skills/mw-maverick-wave/references/layout.md +53 -19
- package/.claude/skills/mw-maverick-wave/references/theming.md +38 -1
- package/.impeccable/config.local.json +5 -0
- package/.impeccable/hook.cache.json +1 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +5 -5
- package/README.md +48 -4
- package/index.html +19 -38
- package/maverick-wave.min.css +14 -12
- package/maverick-wave.min.js +1 -1
- package/package.json +2 -2
- package/scripts/verify.js +27 -2
- package/src/js/main.js +266 -75
- package/src/partials/accordions-container.html +6 -10
- package/src/partials/alerts-container.html +6 -6
- package/src/partials/announcement-container.html +5 -5
- package/src/partials/avatars-container.html +85 -17
- package/src/partials/badges-container.html +6 -6
- package/src/partials/blog-posts-container.html +11 -12
- package/src/partials/buttons-container.html +3 -3
- package/src/partials/cards-container.html +26 -9
- package/src/partials/code-container.html +5 -5
- package/src/partials/content-slider-container.html +5 -4
- package/src/partials/divider-container.html +7 -7
- package/src/partials/dropdown-container.html +2 -2
- package/src/partials/empty-state-container.html +5 -5
- package/src/partials/footer-container.html +21 -18
- package/src/partials/form-elements-container.html +14 -14
- package/src/partials/gallery-container.html +16 -8
- package/src/partials/get-started-container.html +4 -8
- package/src/partials/header-container.html +18 -7
- package/src/partials/header-utilities-container.html +192 -5
- package/src/partials/home-container.html +10 -20
- package/src/partials/input-group-container.html +11 -2
- package/src/partials/kbd-container.html +1 -1
- package/src/partials/login-container.html +5 -1
- package/src/partials/media-container.html +2 -2
- package/src/partials/modals-container.html +22 -32
- package/src/partials/pricing-container.html +9 -9
- package/src/partials/skeleton-container.html +6 -6
- package/src/partials/stepper-container.html +2 -2
- package/src/partials/tables-container.html +12 -12
- package/src/partials/tabs-container.html +17 -27
- package/src/partials/tags-container.html +23 -17
- package/src/partials/techstack-bucket-container.html +13 -0
- package/src/partials/tiles-container.html +26 -9
- package/src/partials/typography-container.html +7 -6
- package/src/partials/utilities-container.html +35 -15
- package/src/scss/_layers.scss +10 -0
- package/src/scss/abstracts/_index.scss +6 -0
- package/src/scss/abstracts/_mixins.scss +61 -153
- package/src/scss/abstracts/_variables.scss +123 -319
- package/src/scss/base/_base.scss +170 -190
- package/src/scss/base/_reset.scss +106 -104
- package/src/scss/base/_typography.scss +153 -151
- package/src/scss/components/_accordions.scss +97 -97
- package/src/scss/components/_alerts.scss +71 -70
- package/src/scss/components/_announcement.scss +71 -70
- package/src/scss/components/_avatars.scss +130 -142
- package/src/scss/components/_badge.scss +131 -132
- package/src/scss/components/_blog-post.scss +223 -220
- package/src/scss/components/_breadcrumbs.scss +93 -92
- package/src/scss/components/_button-bar.scss +102 -110
- package/src/scss/components/_buttons.scss +319 -324
- package/src/scss/components/_calendar.scss +251 -252
- package/src/scss/components/_cards.scss +359 -386
- package/src/scss/components/_code.scss +190 -194
- package/src/scss/components/_coming-soon.scss +77 -75
- package/src/scss/components/_content-slider.scss +90 -89
- package/src/scss/components/_divider.scss +55 -53
- package/src/scss/components/_dropdown.scss +179 -185
- package/src/scss/components/_empty-state.scss +57 -57
- package/src/scss/components/_flag.scss +325 -0
- package/src/scss/components/_gallery.scss +131 -133
- package/src/scss/components/_index.scss +2 -0
- package/src/scss/components/_info.scss +151 -167
- package/src/scss/components/_kanban.scss +332 -341
- package/src/scss/components/_kbd.scss +40 -38
- package/src/scss/components/_lang-switch.scss +108 -0
- package/src/scss/components/_links.scss +21 -28
- package/src/scss/components/_lists.scss +510 -514
- package/src/scss/components/_localhost-indicator.scss +26 -24
- package/src/scss/components/_media.scss +22 -20
- package/src/scss/components/_meta-info.scss +35 -33
- package/src/scss/components/_modals.scss +277 -186
- package/src/scss/components/_pagination.scss +81 -87
- package/src/scss/components/_panels.scss +99 -97
- package/src/scss/components/_pricing.scss +183 -241
- package/src/scss/components/_progress.scss +112 -89
- package/src/scss/components/_prose.scss +35 -33
- package/src/scss/components/_ratings.scss +40 -38
- package/src/scss/components/_segmented.scss +82 -84
- package/src/scss/components/_skeleton.scss +66 -72
- package/src/scss/components/_spinners.scss +155 -159
- package/src/scss/components/_stepper.scss +116 -116
- package/src/scss/components/_tables.scss +155 -148
- package/src/scss/components/_tabs.scss +232 -141
- package/src/scss/components/_tags.scss +109 -107
- package/src/scss/components/_techstack-bucket.scss +123 -122
- package/src/scss/components/_testimonial.scss +72 -70
- package/src/scss/components/_theme-toggle.scss +53 -51
- package/src/scss/components/_tiles.scss +174 -174
- package/src/scss/components/_timelines.scss +282 -280
- package/src/scss/components/_toasts.scss +66 -72
- package/src/scss/form-elements/_checkbox.scss +117 -132
- package/src/scss/form-elements/_form.scss +149 -115
- package/src/scss/form-elements/_input-group.scss +99 -103
- package/src/scss/form-elements/_input.scss +74 -74
- package/src/scss/form-elements/_login.scss +48 -48
- package/src/scss/form-elements/_prefilled.scss +53 -51
- package/src/scss/form-elements/_radio.scss +95 -107
- package/src/scss/form-elements/_select.scss +42 -42
- package/src/scss/form-elements/_slider.scss +117 -132
- package/src/scss/form-elements/_textarea.scss +68 -54
- package/src/scss/form-elements/_toggle.scss +127 -140
- package/src/scss/layout/_footer.scss +172 -171
- package/src/scss/layout/_grid.scss +268 -302
- package/src/scss/layout/_header.scss +426 -427
- package/src/scss/layout/_home.scss +28 -27
- package/src/scss/layout/_main.scss +275 -238
- package/src/scss/layout/_page-header.scss +38 -36
- package/src/scss/layout/_section.scss +150 -124
- package/src/scss/main.scss +1 -1
- package/src/scss/utilities/_accessibility.scss +46 -45
- package/src/scss/utilities/_aspect.scss +22 -20
- package/src/scss/utilities/_corner.scss +23 -5
- package/src/scss/utilities/_display.scss +76 -70
- package/src/scss/utilities/_elevation.scss +13 -11
- package/src/scss/utilities/_flex.scss +83 -85
- package/src/scss/utilities/_reveal.scss +40 -63
- package/src/scss/utilities/_spacing.scss +63 -64
- package/src/scss/utilities/_text.scss +139 -141
- package/src/scss/utilities/_touch-targets.scss +130 -147
|
@@ -11,5 +11,33 @@
|
|
|
11
11
|
"Bash(npm audit:*)",
|
|
12
12
|
"Bash(npx prettier *)"
|
|
13
13
|
]
|
|
14
|
-
}
|
|
14
|
+
},
|
|
15
|
+
"hooks": {
|
|
16
|
+
"PostToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Edit|Write",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "[ ! -f '/Users/m1well/.claude/skills/impeccable/scripts/impeccable' ] || '/Users/m1well/.claude/skills/impeccable/scripts/impeccable' hook",
|
|
23
|
+
"timeout": 5,
|
|
24
|
+
"statusMessage": "Checking UI changes"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"Stop": [
|
|
30
|
+
{
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "[ ! -f '/Users/m1well/.claude/skills/impeccable/scripts/impeccable' ] || '/Users/m1well/.claude/skills/impeccable/scripts/impeccable' hook",
|
|
35
|
+
"timeout": 30,
|
|
36
|
+
"statusMessage": "Design deep pass"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"description": "Impeccable design detector: immediate-tier checks after Edit/Write on UI files, full-rule deep pass on Stop."
|
|
15
43
|
}
|
|
@@ -44,14 +44,14 @@ Load the one you need - do not read them all up front.
|
|
|
44
44
|
```html
|
|
45
45
|
<link
|
|
46
46
|
rel="stylesheet"
|
|
47
|
-
href="https://cdn.jsdelivr.net/npm/maverick-wave@
|
|
47
|
+
href="https://cdn.jsdelivr.net/npm/maverick-wave@5.0.0/maverick-wave.min.css"
|
|
48
48
|
/>
|
|
49
49
|
<link
|
|
50
50
|
rel="stylesheet"
|
|
51
51
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
|
52
52
|
/>
|
|
53
53
|
...
|
|
54
|
-
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@
|
|
54
|
+
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.0.0/maverick-wave.min.js"></script>
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Pin the version. The JS file is optional and only for server-rendered/static pages -
|
|
@@ -116,18 +116,18 @@ leave a second tab lit.
|
|
|
116
116
|
|
|
117
117
|
The classes that mean something _other_ than "on" keep their own names:
|
|
118
118
|
|
|
119
|
-
| Component | State class
|
|
120
|
-
| -------------------------------------------- |
|
|
121
|
-
| Anything switchable, "on" | `mw-active` (`active` deprecated)
|
|
122
|
-
| Burger button + navbar drawer | `open` (no prefix)
|
|
123
|
-
| Stepper indicator / label / connector / step | `mw-active`, `mw-done`
|
|
124
|
-
| Checkbox list item (`li`) | `mw-selected`
|
|
125
|
-
| Calendar day, picked | `mw-selected`
|
|
126
|
-
| Kanban ticket being edited | `mw-kanban-editing`
|
|
127
|
-
| Modal overlay | `mw-modal-open`
|
|
128
|
-
| Alert,
|
|
129
|
-
| Field wrapper in error | `mw-field-has-error`
|
|
130
|
-
| Single form control in error | `mw-form-element-error`
|
|
119
|
+
| Component | State class |
|
|
120
|
+
| -------------------------------------------- | -------------------------------------------------------- |
|
|
121
|
+
| Anything switchable, "on" | `mw-active` (`active` deprecated) |
|
|
122
|
+
| Burger button + navbar drawer | `open` (no prefix) |
|
|
123
|
+
| Stepper indicator / label / connector / step | `mw-active`, `mw-done` |
|
|
124
|
+
| Checkbox list item (`li`) | `mw-selected` |
|
|
125
|
+
| Calendar day, picked | `mw-selected` |
|
|
126
|
+
| Kanban ticket being edited | `mw-kanban-editing` |
|
|
127
|
+
| Modal overlay | `mw-modal-open` |
|
|
128
|
+
| Alert, dismissing / dismissed | `mw-alert-closing` → `mw-alert-closed` (`display: none`) |
|
|
129
|
+
| Field wrapper in error | `mw-field-has-error` |
|
|
130
|
+
| Single form control in error | `mw-form-element-error` |
|
|
131
131
|
|
|
132
132
|
`mw-active` and `mw-selected` are not the same thing and not interchangeable:
|
|
133
133
|
active is the one of several that is currently showing, selected is a choice the
|
|
@@ -150,8 +150,22 @@ keys at all (negative gap is invalid CSS and is not generated).
|
|
|
150
150
|
`sm` 0.9, `base` 1, `md` 1.1, `lg` 1.3, `xl` 1.5, `2xl` 1.8, `3xl` 2.2, `4xl` 2.5,
|
|
151
151
|
`5xl` 3, `6xl` 4.3 rem.
|
|
152
152
|
|
|
153
|
-
**Breakpoints
|
|
154
|
-
|
|
153
|
+
**Breakpoints**: `xs` 375, `sm` 576, `md` 768, `lg` 992, `xl` 1200, `2xl` 1400 px.
|
|
154
|
+
Column grids and most components are mobile-first (`min-width`); the ranges do
|
|
155
|
+
not overlap, `media-down` stops 0.02px short of its breakpoint. Blocks that only
|
|
156
|
+
adjust something on a phone stay `max-width`.
|
|
157
|
+
|
|
158
|
+
**Overriding.** Everything the framework emits sits in
|
|
159
|
+
`@layer mw.reset, mw.base, mw.forms, mw.components, mw.layout, mw.utilities`.
|
|
160
|
+
Any rule written outside a layer beats all of it, so a single `.mw-card {}` in
|
|
161
|
+
your own stylesheet wins - no `!important`, no doubled selectors. `!important`
|
|
162
|
+
is not a stronger version of this but a weaker one: important declarations
|
|
163
|
+
reverse the layer order and unlayered comes last, so an important rule in
|
|
164
|
+
`mw.base` beats an important one of yours even behind an ID selector. The flip
|
|
165
|
+
side: third-party CSS loaded unlayered also wins, so pull it into a layer of
|
|
166
|
+
its own:
|
|
167
|
+
`@import url('font-awesome.css') layer(vendor);` with `@layer vendor, mw;`
|
|
168
|
+
declared before it.
|
|
155
169
|
|
|
156
170
|
**Radius** (`mw-radius-none|xs|sm|md|lg|xl|2xl|full`): 0, 2, 5, 10, 15, 20, 30 px, 50%.
|
|
157
171
|
|
|
@@ -221,7 +235,9 @@ feature frame) ·
|
|
|
221
235
|
|
|
222
236
|
**Navigation** `mw-header` + `mw-navbar` · `mw-breadcrumbs` · `mw-pagination` ·
|
|
223
237
|
`mw-dropdown` (+ `-menu`, `-item`, `-item-danger`, `-divider`, `-label`,
|
|
224
|
-
`-caret`, `-end`, `-up`) · `mw-
|
|
238
|
+
`-caret`, `-end`, `-up`) · `mw-lang-switch` (+ `-code`, `-name`, `-check`,
|
|
239
|
+
`inverted`) · `mw-flag` (+ 23 country codes) ·
|
|
240
|
+
`mw-section-nav` (`references/layout.md`)
|
|
225
241
|
|
|
226
242
|
**Media & content** `mw-avatar` (+ `initials`, `group`) · `mw-gallery` ·
|
|
227
243
|
`mw-image-slider` · `mw-blog-post` · `mw-testimonial` (+ `-source`, `-detail`,
|
|
@@ -368,14 +384,15 @@ feature frame) ·
|
|
|
368
384
|
comment. If your component really does need to animate a size, name that
|
|
369
385
|
property - do not reach for `all`.
|
|
370
386
|
27. **A control is a `<button>`, never a styled `<div>`.** `mw-tabs-nav-item`,
|
|
371
|
-
`mw-theme-toggle`, `mw-gallery-dot
|
|
372
|
-
written for one, and all
|
|
373
|
-
until 4.11, the accordion header until 4.12
|
|
374
|
-
Each class clears what a `<button>` brings
|
|
375
|
-
`<button type="button" class="mw-tabs-nav-item" data-tab="...">`
|
|
376
|
-
whole markup.
|
|
377
|
-
|
|
378
|
-
|
|
387
|
+
`mw-theme-toggle`, `mw-gallery-dot`, `mw-accordion-header` and `mw-menu-btn`
|
|
388
|
+
are all written for one, and all five shipped as divs and spans - the first
|
|
389
|
+
three until 4.11, the accordion header until 4.12, the burger after that -
|
|
390
|
+
that no keyboard could reach. Each class clears what a `<button>` brings
|
|
391
|
+
with it, so `<button type="button" class="mw-tabs-nav-item" data-tab="...">`
|
|
392
|
+
is the whole markup. The burger is the worst of the five to get wrong:
|
|
393
|
+
below the collapse breakpoint it is the only route to the navigation.
|
|
394
|
+
If you build your own clickable thing: the element decides whether anyone
|
|
395
|
+
without a mouse can use it, the class only decides how it looks.
|
|
379
396
|
28. **A link in running text is `mw-link`, not `mw-btn mw-btn-link`.** Since
|
|
380
397
|
4.13.0 there is a class for exactly that. `mw-btn` is `inline-flex` with
|
|
381
398
|
`min-height: var(--mw-control-height)`, so a link written that way pulls its
|
|
@@ -7,7 +7,9 @@ service or a component per behaviour, none of them longer than a screen.
|
|
|
7
7
|
## Theme service
|
|
8
8
|
|
|
9
9
|
The framework only needs one class on `<body>`. Everything else - persistence,
|
|
10
|
-
the toggle UI, the initial value - belongs to the application.
|
|
10
|
+
the toggle UI, the initial value - belongs to the application. The second class,
|
|
11
|
+
`mw-theme-switching` on `<html>`, is not optional on a page of any size: without
|
|
12
|
+
it the flip starts a transition on every element that changes colour.
|
|
11
13
|
|
|
12
14
|
```ts
|
|
13
15
|
import { Injectable, effect, signal } from '@angular/core';
|
|
@@ -21,7 +23,13 @@ export class ThemeService {
|
|
|
21
23
|
constructor() {
|
|
22
24
|
effect(() => {
|
|
23
25
|
const light = this.light();
|
|
26
|
+
const root = document.documentElement;
|
|
27
|
+
|
|
28
|
+
root.classList.add('mw-theme-switching');
|
|
24
29
|
document.body.classList.toggle('mw-theme-light', light);
|
|
30
|
+
void root.offsetHeight; // commit the colours with transitions off
|
|
31
|
+
root.classList.remove('mw-theme-switching');
|
|
32
|
+
|
|
25
33
|
localStorage.setItem(STORAGE_KEY, light ? 'light' : 'dark');
|
|
26
34
|
});
|
|
27
35
|
}
|
|
@@ -308,13 +316,17 @@ on every navigation.
|
|
|
308
316
|
<span class="mw-profile-btn-name">{{ user().name }}</span>
|
|
309
317
|
</button>
|
|
310
318
|
|
|
311
|
-
<
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
312
321
|
class="mw-menu-btn"
|
|
322
|
+
aria-label="Menu"
|
|
323
|
+
aria-controls="main-nav"
|
|
324
|
+
[attr.aria-expanded]="menuOpen()"
|
|
313
325
|
[class.open]="menuOpen()"
|
|
314
326
|
(click)="menuOpen.set(!menuOpen())"
|
|
315
327
|
>
|
|
316
|
-
<
|
|
317
|
-
</
|
|
328
|
+
<span class="mw-menu-btn-burger"></span>
|
|
329
|
+
</button>
|
|
318
330
|
</div>
|
|
319
331
|
</div>
|
|
320
332
|
</header>
|
|
@@ -20,7 +20,7 @@ accordion FAQ.
|
|
|
20
20
|
|
|
21
21
|
<link
|
|
22
22
|
rel="stylesheet"
|
|
23
|
-
href="https://cdn.jsdelivr.net/npm/maverick-wave@
|
|
23
|
+
href="https://cdn.jsdelivr.net/npm/maverick-wave@5.0.0/maverick-wave.min.css"
|
|
24
24
|
/>
|
|
25
25
|
<link
|
|
26
26
|
rel="stylesheet"
|
|
@@ -78,7 +78,15 @@ accordion FAQ.
|
|
|
78
78
|
</div>
|
|
79
79
|
</button>
|
|
80
80
|
|
|
81
|
-
<
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class="mw-menu-btn"
|
|
84
|
+
aria-label="Menu"
|
|
85
|
+
aria-expanded="false"
|
|
86
|
+
aria-controls="main-nav"
|
|
87
|
+
>
|
|
88
|
+
<span class="mw-menu-btn-burger"></span>
|
|
89
|
+
</button>
|
|
82
90
|
</div>
|
|
83
91
|
</div>
|
|
84
92
|
</header>
|
|
@@ -479,7 +487,7 @@ accordion FAQ.
|
|
|
479
487
|
<div class="mw-modal-backdrop" onclick="closeModal('demo')"></div>
|
|
480
488
|
</div>
|
|
481
489
|
|
|
482
|
-
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@
|
|
490
|
+
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.0.0/maverick-wave.min.js"></script>
|
|
483
491
|
<script>
|
|
484
492
|
// The only thing the shipped script does not cover: opening a modal.
|
|
485
493
|
// Closing works through .mw-modal-close, the backdrop is wired above.
|
|
@@ -497,7 +505,8 @@ accordion FAQ.
|
|
|
497
505
|
## Notes
|
|
498
506
|
|
|
499
507
|
- The theme toggle needs no code - the script persists the choice under
|
|
500
|
-
`localStorage['mw-theme']
|
|
508
|
+
`localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and wraps the
|
|
509
|
+
flip in `mw-theme-switching` so it starts no transitions.
|
|
501
510
|
- The scroll spy sets `mw-active` on the `mw-navbar-link` whose `href` matches
|
|
502
511
|
the `section[id]` currently in view - the `<section id="…">` elements are
|
|
503
512
|
the contract.
|
|
@@ -544,8 +544,12 @@ Toggling is JS - see `references/javascript.md`; the shipped script keeps
|
|
|
544
544
|
```html
|
|
545
545
|
<div class="mw-tabs">
|
|
546
546
|
<div class="mw-tabs-nav">
|
|
547
|
-
<
|
|
548
|
-
|
|
547
|
+
<button type="button" class="mw-tabs-nav-item mw-active" data-tab="tab1">
|
|
548
|
+
Details
|
|
549
|
+
</button>
|
|
550
|
+
<button type="button" class="mw-tabs-nav-item" data-tab="tab2">
|
|
551
|
+
History
|
|
552
|
+
</button>
|
|
549
553
|
</div>
|
|
550
554
|
<div class="mw-tabs-content">
|
|
551
555
|
<div class="mw-tabs-panel mw-active" id="tab1">...</div>
|
|
@@ -558,6 +562,9 @@ Toggling is JS - see `references/javascript.md`; the shipped script keeps
|
|
|
558
562
|
`mw-tabs-pills`.
|
|
559
563
|
- `data-tab` matches the panel `id` - that pairing is only needed for the
|
|
560
564
|
shipped JS. In a SPA, bind `mw-active` yourself and drop the attribute.
|
|
565
|
+
- The shipped JS turns the strip into a real tablist: `role`, `aria-selected`,
|
|
566
|
+
`aria-controls`, `aria-labelledby`, a roving `tabindex` and arrow/Home/End
|
|
567
|
+
keys. Without it, that is yours to add - see `references/javascript.md`.
|
|
561
568
|
- The nav scrolls horizontally instead of wrapping, and says so: a shadow shows
|
|
562
569
|
on whichever side still has tabs behind it and disappears once that end is
|
|
563
570
|
reached. Pure CSS, no scroll listener. The fade colour comes from
|
|
@@ -602,6 +609,35 @@ Toggling is JS - see `references/javascript.md`; the shipped script keeps
|
|
|
602
609
|
|
|
603
610
|
Angular: `<div class="mw-modal-overlay" [class.mw-modal-open]="isOpen()">`.
|
|
604
611
|
|
|
612
|
+
### The same modal as a `<dialog>`
|
|
613
|
+
|
|
614
|
+
Same classes, no overlay wrapper and no backdrop element:
|
|
615
|
+
|
|
616
|
+
```html
|
|
617
|
+
<dialog id="delete-modal" class="mw-modal mw-modal-sm" closedby="any">
|
|
618
|
+
<div class="mw-modal-header">
|
|
619
|
+
<h4 class="mw-modal-title">Delete invoice</h4>
|
|
620
|
+
<button class="mw-modal-close" type="button" aria-label="Close">
|
|
621
|
+
𝗫
|
|
622
|
+
</button>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="mw-modal-body">…</div>
|
|
625
|
+
<div class="mw-modal-footer">…</div>
|
|
626
|
+
</dialog>
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
- Escape, the focus trap, `inert` on the page behind it and the scroll lock all
|
|
630
|
+
come from the element. Prefer this shape for anything that asks a question.
|
|
631
|
+
- Opened with `showModal()`, closed with `close()`. `main.js` wires the close
|
|
632
|
+
buttons and exposes `mwOpenModal(id)` / `mwCloseModal(id)`, both of which take
|
|
633
|
+
either shape. A trigger can also carry `data-mw-modal="delete-modal"`.
|
|
634
|
+
- `closedby="any"` dismisses it on a backdrop click; where that attribute is not
|
|
635
|
+
understood the script handles the click instead.
|
|
636
|
+
- Sizes, the bottom sheet below 576px and every `mw-modal-*` part behave exactly
|
|
637
|
+
as above - those rules are class-based.
|
|
638
|
+
|
|
639
|
+
Angular: bind nothing, call `showModal()` on a `viewChild` ref.
|
|
640
|
+
|
|
605
641
|
## Alerts & toasts
|
|
606
642
|
|
|
607
643
|
```html
|
|
@@ -618,9 +654,10 @@ Angular: `<div class="mw-modal-overlay" [class.mw-modal-open]="isOpen()">`.
|
|
|
618
654
|
```
|
|
619
655
|
|
|
620
656
|
Variants: `mw-alert-primary`, `-secondary`, `-success`, `-warning`, `-danger`,
|
|
621
|
-
`-info`. `mw-alert-title` and the close button are optional.
|
|
622
|
-
|
|
623
|
-
|
|
657
|
+
`-info`. `mw-alert-title` and the close button are optional. Dismissal runs in
|
|
658
|
+
two steps: `mw-alert-closing` fades the alert out over `--mw-duration-base`, then
|
|
659
|
+
`mw-alert-closed` hides it (`display: none`) - in a SPA prefer removing it from
|
|
660
|
+
the list instead.
|
|
624
661
|
|
|
625
662
|
**Toasts** are alerts inside a fixed stack:
|
|
626
663
|
|
|
@@ -1170,6 +1207,54 @@ your own it is `:has(.mw-dropdown[open]) { overflow: visible }`.
|
|
|
1170
1207
|
On a coarse pointer the rows grow to 2.75rem and the menu takes at least the
|
|
1171
1208
|
trigger's full width.
|
|
1172
1209
|
|
|
1210
|
+
## Language switcher
|
|
1211
|
+
|
|
1212
|
+
A `mw-dropdown` with a quieter trigger: no fill and no border until it is
|
|
1213
|
+
hovered, so a header bar does not turn into a row of competing buttons.
|
|
1214
|
+
|
|
1215
|
+
```html
|
|
1216
|
+
<details class="mw-dropdown mw-dropdown-end mw-lang-switch">
|
|
1217
|
+
<summary aria-label="Language: Deutsch">
|
|
1218
|
+
<span class="mw-flag mw-flag-de"></span>
|
|
1219
|
+
<span class="mw-lang-switch-code">de</span>
|
|
1220
|
+
<i class="fas fa-chevron-down mw-dropdown-caret"></i>
|
|
1221
|
+
</summary>
|
|
1222
|
+
<div class="mw-dropdown-menu">
|
|
1223
|
+
<button
|
|
1224
|
+
type="button"
|
|
1225
|
+
class="mw-dropdown-item mw-active"
|
|
1226
|
+
aria-current="true"
|
|
1227
|
+
data-mw-lang="de"
|
|
1228
|
+
>
|
|
1229
|
+
<span class="mw-flag mw-flag-de"></span>
|
|
1230
|
+
<span class="mw-lang-switch-name" lang="de">Deutsch</span>
|
|
1231
|
+
<i class="fas fa-check mw-lang-switch-check"></i>
|
|
1232
|
+
</button>
|
|
1233
|
+
<button type="button" class="mw-dropdown-item" data-mw-lang="en">
|
|
1234
|
+
<span class="mw-flag mw-flag-gb"></span>
|
|
1235
|
+
<span class="mw-lang-switch-name" lang="en">English</span>
|
|
1236
|
+
<i class="fas fa-check mw-lang-switch-check"></i>
|
|
1237
|
+
</button>
|
|
1238
|
+
</div>
|
|
1239
|
+
</details>
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
The two-letter code carries the state, not the flag: a flag is a country and
|
|
1243
|
+
never a language - no country stands for English. Write each language name in
|
|
1244
|
+
its own language and put `lang="fr"` on it, so a screen reader pronounces it
|
|
1245
|
+
instead of spelling it out.
|
|
1246
|
+
|
|
1247
|
+
Parts: `mw-lang-switch-code`, `-name`, `-check` (the tick, shown by `mw-active`
|
|
1248
|
+
on the item and kept in the layout while hidden). `mw-lang-switch-inverted` is
|
|
1249
|
+
for a dark bar built without `mw-header`; inside `mw-header` those colours apply
|
|
1250
|
+
on their own.
|
|
1251
|
+
|
|
1252
|
+
Flags: `mw-flag` plus `mw-flag-<iso>` for de, at, ch, gb, us, ie, fr, it, es,
|
|
1253
|
+
pt, nl, be, pl, hu, ro, bg, ua, se, dk, no, fi, tr and jp - CSS gradients, all
|
|
1254
|
+
in the same 4:3 box whatever the real ratio. Never an emoji flag: Windows ships
|
|
1255
|
+
no glyphs for them. Anything outside the set goes as an `<img>` or an inline
|
|
1256
|
+
`<svg>` inside `mw-flag`.
|
|
1257
|
+
|
|
1173
1258
|
## Keyboard keys
|
|
1174
1259
|
|
|
1175
1260
|
`<kbd>` is styled directly, so a shortcut in prose needs no class:
|
|
@@ -45,14 +45,26 @@ reactive form control.
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
- `mw-required` on the label appends a red asterisk (`data-required="true"`
|
|
48
|
-
works too).
|
|
48
|
+
works too). A label sitting directly in front of a control with the `required`
|
|
49
|
+
attribute gets the asterisk without either class - useful for template-driven
|
|
50
|
+
and plain HTML forms, where the marker then cannot drift out of step with the
|
|
51
|
+
validation.
|
|
49
52
|
- `mw-field-hint` is the small muted helper line, `mw-field-error` the small red
|
|
50
53
|
one. Render only one of them at a time.
|
|
51
54
|
- `mw-field-has-error` on the **wrapper** turns the border of the contained
|
|
52
55
|
`mw-input` / `mw-select` / `mw-textarea` red and adds a soft red halo.
|
|
53
56
|
- `mw-form-element-error` does the same for a single control that has no field
|
|
54
57
|
wrapper - it also works on `mw-checkbox-group`, `mw-radio-group` and
|
|
55
|
-
`mw-slider-container`.
|
|
58
|
+
`mw-slider-container`. On the two groups it adds the inset a field brings with
|
|
59
|
+
it, so the rows do not sit flush against the border.
|
|
60
|
+
- Native validation is styled too: a control that fails `required`, `type` or
|
|
61
|
+
`pattern` gets the same red border and halo through `:user-invalid`, and the
|
|
62
|
+
`mw-field-hint` beside it turns red. `:user-invalid` and not `:invalid`, so an
|
|
63
|
+
untouched empty field is not red on page load. Nothing has to be bound for
|
|
64
|
+
this - which is exactly why the classes still exist for the case below, where
|
|
65
|
+
the validator lives in the component rather than on the element.
|
|
66
|
+
- `mw-textarea` grows with its content (`field-sizing`) between 3 lines and
|
|
67
|
+
60dvh, where the browser supports it.
|
|
56
68
|
|
|
57
69
|
> **The framework does not style Angular's `ng-invalid` / `ng-touched` classes.**
|
|
58
70
|
> Bind the framework classes to the control state yourself:
|
|
@@ -28,14 +28,14 @@ classes are the entire contract.
|
|
|
28
28
|
| Behaviour | What the shipped JS does | What to do instead |
|
|
29
29
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
30
30
|
| Accordion | Toggles `mw-active` on `mw-accordion-header` and the following `mw-accordion-content`, and writes `aria-expanded` when the header is a `<button>` | `[class.mw-active]="isOpen()"` and `[attr.aria-expanded]="isOpen()"` on the header, `mw-active` on the panel |
|
|
31
|
-
| Tabs | `data-tab` → panel `id`; sets `mw-active` on nav item and panel
|
|
31
|
+
| Tabs | `data-tab` → panel `id`; sets `mw-active` on nav item and panel, and wires the whole tablist: `role`, `aria-selected`, `aria-controls`, `aria-labelledby`, a roving `tabindex` and arrow/Home/End keys | Track the selected index/key, bind `mw-active` on both; drop `data-tab` |
|
|
32
32
|
| Modal | Click on `mw-modal-close` removes `mw-modal-open` from the overlay | `[class.mw-modal-open]="isOpen()"`; backdrop click closes. Opening is not in the script at all (the showcase has its own `openModal`) |
|
|
33
|
-
| Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, closes on anchor click
|
|
33
|
+
| Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, writes `aria-expanded` when the button is a `<button>`, closes on anchor click and on Escape (focus returns to the button) | One signal, bound to both; reset it on navigation end |
|
|
34
34
|
| Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
|
|
35
|
-
| Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `mw-active` on the toggle
|
|
35
|
+
| Theme toggle | `localStorage['mw-theme']`, toggles `mw-theme-light` on `<body>` and `mw-active` on the toggle, wrapped in `mw-theme-switching` on `<html>` so the flip starts no transitions | A theme service - see `examples/angular-services.md` |
|
|
36
36
|
| Progress bar | `IntersectionObserver` sets `width` from `data-value` | Bind `[style.width.%]="value()"` on `mw-progress-fill` |
|
|
37
37
|
| Slider | On `input`, sets `--value` (track fill) and `data-value` (badge text) | Bind `[style.--value.%]` and `[attr.data-value]` |
|
|
38
|
-
| Alerts | Close button adds `mw-alert-closed` (`display: none`)
|
|
38
|
+
| Alerts | Close button adds `mw-alert-closing` (fade out), then `mw-alert-closed` (`display: none`) after `--mw-duration-base` | Remove the alert from the list/signal |
|
|
39
39
|
| Checkbox lists | Adds `mw-selected` to the `li`, emits a `checkboxToggle` event, exposes `window.toggleCheckbox` | `[class.mw-selected]="item.checked"` |
|
|
40
40
|
| Gallery | Generates the dots, moves the track, swipe handling, writes `mw-gallery-desc` | Render dots in the template, bind the track transform and `mw-active` on the current dot |
|
|
41
41
|
| Image slider | Toggles `mw-active` on the overlay image and the control button with the matching `data-index` | Bind `mw-active` from the selected index |
|
|
@@ -45,6 +45,20 @@ classes are the entire contract.
|
|
|
45
45
|
| Header login button | Swaps the FontAwesome lock icon | Bind the icon class |
|
|
46
46
|
| Color swatches | Showcase-only (prints computed hex values) | Not needed |
|
|
47
47
|
| Dropdown | Delegated to the document: closes the open `mw-dropdown` on Escape, on a click elsewhere and on a click on a `mw-dropdown-item`, and returns focus to the `summary` | The `<details>` does the opening, the keyboard and the state on its own. Rebuild only the two behaviours markup cannot express - or bind `[attr.open]` and keep them in the component |
|
|
48
|
+
| Language switcher | Keeps the trigger's flag and code in step with the chosen item, moves `mw-active` and `aria-current`, and fires `mw-language-change` (`detail: { lang, name }`) on the switcher | Bind the trigger from your locale signal and switch the language in your own i18n service; the menu itself is a `<details>` and needs nothing |
|
|
49
|
+
|
|
50
|
+
## Modals and progress bars
|
|
51
|
+
|
|
52
|
+
`mwOpenModal(id)` / `mwCloseModal(id)` are on `window` and handle both modal
|
|
53
|
+
shapes - the `mw-modal-overlay` div and a `<dialog class="mw-modal">`. Close
|
|
54
|
+
buttons and `data-mw-modal="<id>"` triggers are delegated from the document, so
|
|
55
|
+
markup rendered later still works. A `<dialog>` needs none of it in a SPA: call
|
|
56
|
+
`showModal()` and `close()` on the element.
|
|
57
|
+
|
|
58
|
+
`mw-progress-fill` takes its target width from `data-value="75"` or an inline
|
|
59
|
+
`style="width: 75%"`. Where scroll-driven animations are supported the bar fills
|
|
60
|
+
with the scroll position and the script only hands the value over; elsewhere it
|
|
61
|
+
falls back to setting the width once the bar comes into view.
|
|
48
62
|
|
|
49
63
|
## A note on the state class
|
|
50
64
|
|
|
@@ -59,11 +59,11 @@ its children are styled through descendant selectors:
|
|
|
59
59
|
<header class="mw-header">
|
|
60
60
|
<div class="mw-container">
|
|
61
61
|
<div class="mw-logo">
|
|
62
|
-
<
|
|
62
|
+
<a href="#start"><img src="logo.svg" alt="Logo" /></a>
|
|
63
63
|
</div>
|
|
64
64
|
|
|
65
65
|
<div class="mw-header-actions">
|
|
66
|
-
<nav class="mw-navbar mw-navbar-medium">
|
|
66
|
+
<nav class="mw-navbar mw-navbar-medium" id="main-nav">
|
|
67
67
|
<ul class="mw-navbar-list">
|
|
68
68
|
<li class="mw-navbar-item">
|
|
69
69
|
<a href="#start" class="mw-navbar-link mw-active">Start</a>
|
|
@@ -74,17 +74,29 @@ its children are styled through descendant selectors:
|
|
|
74
74
|
</ul>
|
|
75
75
|
</nav>
|
|
76
76
|
|
|
77
|
-
<
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
class="mw-theme-toggle mw-ml-5"
|
|
80
|
+
aria-label="Toggle light and dark theme"
|
|
81
|
+
>
|
|
78
82
|
<div class="mw-theme-toggle-slider">
|
|
79
83
|
<div class="mw-theme-toggle-icon"><i class="fas fa-moon"></i></div>
|
|
80
84
|
</div>
|
|
81
|
-
</
|
|
85
|
+
</button>
|
|
82
86
|
|
|
83
|
-
<button class="mw-login-btn" type="button">
|
|
87
|
+
<button class="mw-login-btn" type="button" aria-label="Log in">
|
|
84
88
|
<i class="fas fa-lock"></i>
|
|
85
89
|
</button>
|
|
86
90
|
|
|
87
|
-
<
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
class="mw-menu-btn"
|
|
94
|
+
aria-label="Menu"
|
|
95
|
+
aria-expanded="false"
|
|
96
|
+
aria-controls="main-nav"
|
|
97
|
+
>
|
|
98
|
+
<span class="mw-menu-btn-burger"></span>
|
|
99
|
+
</button>
|
|
88
100
|
</div>
|
|
89
101
|
</div>
|
|
90
102
|
</header>
|
|
@@ -94,7 +106,9 @@ its children are styled through descendant selectors:
|
|
|
94
106
|
at `md`, `mw-navbar-medium` (4-5) at `lg`, `mw-navbar-large` (6+) at `xl`.
|
|
95
107
|
Pick the class by how many links you have.
|
|
96
108
|
- Below the breakpoint the list is hidden and `mw-menu-btn` appears. Opening the
|
|
97
|
-
drawer means adding `open` to **both** `mw-menu-btn` and `mw-navbar
|
|
109
|
+
drawer means adding `open` to **both** `mw-menu-btn` and `mw-navbar`, and
|
|
110
|
+
setting `aria-expanded` on the button - below the breakpoint it is the only
|
|
111
|
+
route to the navigation, so it has to be a real `<button>`, not a `<div>`.
|
|
98
112
|
- The active link carries `mw-active` (bare `active` still works but is
|
|
99
113
|
deprecated).
|
|
100
114
|
- `mw-profile-btn` is the signed-in pill, next to or instead of the login button:
|
|
@@ -162,14 +176,14 @@ viewport, so keep long tooltips off the outermost elements.
|
|
|
162
176
|
|
|
163
177
|
## Sections
|
|
164
178
|
|
|
165
|
-
| Class | Use
|
|
166
|
-
| ----------------------------------- |
|
|
167
|
-
| `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 3.3rem (2.5rem below `md`, 1.75rem below `sm`)
|
|
168
|
-
| `mw-section-alternate` | Diagonal pattern background; combine with `mw-section`
|
|
169
|
-
| `mw-section-title` | Centered `3xl` heading with a decorative primary underline - landing pages
|
|
170
|
-
| `mw-section-intro` | The lead paragraph under a section title - centred, muted, 46rem measure
|
|
171
|
-
| `mw-section-subtitle` | Centered `2xl` heading with a thin secondary underline
|
|
172
|
-
| `mw-section-nav` + `mw-section-btn` |
|
|
179
|
+
| Class | Use |
|
|
180
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
181
|
+
| `mw-section` | Vertical rhythm for a page band - `--mw-section-padding-block`, 3.3rem (2.5rem below `md`, 1.75rem below `sm`) |
|
|
182
|
+
| `mw-section-alternate` | Diagonal pattern background; combine with `mw-section` |
|
|
183
|
+
| `mw-section-title` | Centered `3xl` heading with a decorative primary underline - landing pages |
|
|
184
|
+
| `mw-section-intro` | The lead paragraph under a section title - centred, muted, 46rem measure |
|
|
185
|
+
| `mw-section-subtitle` | Centered `2xl` heading with a thin secondary underline |
|
|
186
|
+
| `mw-section-nav` + `mw-section-btn` | Sticky single-row strip of outline-style jump links; parks under the header and scrolls sideways when it overflows |
|
|
173
187
|
|
|
174
188
|
```html
|
|
175
189
|
<section class="mw-section mw-section-alternate">
|
|
@@ -187,6 +201,10 @@ viewport, so keep long tooltips off the outermost elements.
|
|
|
187
201
|
</section>
|
|
188
202
|
```
|
|
189
203
|
|
|
204
|
+
The strip sticks at `--mw-header-height` and is `--mw-section-nav-height` tall
|
|
205
|
+
(3.7rem). Anything it can cover on a jump needs the sum as `scroll-margin-top`;
|
|
206
|
+
a block wrapping a `mw-section-subtitle` gets that from the framework already.
|
|
207
|
+
|
|
190
208
|
## Page header
|
|
191
209
|
|
|
192
210
|
The application counterpart to `mw-section-title`: title and subtitle left,
|
|
@@ -264,6 +282,12 @@ Components that paint their own surface (`mw-card`, `mw-panel`, `mw-modal`,
|
|
|
264
282
|
`mw-tile`, `mw-calendar`) are exempt and keep the theme's ink - a card in the
|
|
265
283
|
hero is still a card.
|
|
266
284
|
|
|
285
|
+
`mw-btn-outline` is exempt the other way round: inside the hero it takes the
|
|
286
|
+
hero's ink plus a dark scrim and a blur, because its usual border and label are
|
|
287
|
+
tuned for the page background and go quiet over a photograph. A second call to
|
|
288
|
+
action next to `mw-btn-primary` therefore looks different here than it does
|
|
289
|
+
further down the page - that is deliberate, not a stray override.
|
|
290
|
+
|
|
267
291
|
**Scroll cue** - `mw-scroll-hint` is the "scroll down" cue on the lower edge of
|
|
268
292
|
the hero. An `<a>` that is a sibling of `mw-hero`, directly in the container:
|
|
269
293
|
|
|
@@ -286,8 +310,10 @@ is taken on a phone. The bobbing stops under `prefers-reduced-motion`.
|
|
|
286
310
|
## Grid
|
|
287
311
|
|
|
288
312
|
All grid classes are `display: grid` with a preset gap (`mw-gap-*` overrides
|
|
289
|
-
it). They
|
|
290
|
-
|
|
313
|
+
it). They handle their own column count - no responsive suffixes to manage. One
|
|
314
|
+
column is the base and each breakpoint adds to it, so a phone renders them
|
|
315
|
+
without evaluating a single media query; the table below reads the same either
|
|
316
|
+
way.
|
|
291
317
|
|
|
292
318
|
| Class | Columns | Collapses |
|
|
293
319
|
| ----------------- | ----------------------------------------------- | ---------------------------- |
|
|
@@ -414,6 +440,11 @@ chip-sized `mw-card-simple`, a swatch, a marker in a layout demo. A card with
|
|
|
414
440
|
content in it never needs this, and it is not the way to switch the signature
|
|
415
441
|
off across a project - restyle `--mw-corner-accent` for that.
|
|
416
442
|
|
|
443
|
+
**Squircle corners** - `mw-squircle` draws the same silhouette with a
|
|
444
|
+
superellipse instead of a circular arc: same four radii, fuller curve, and the
|
|
445
|
+
corner accent follows. Opt-in, because `corner-shape` only lands in Chromium so
|
|
446
|
+
far - everywhere else the box is simply the normal one.
|
|
447
|
+
|
|
417
448
|
**Aspect ratio** - `mw-aspect-square|video|wide|portrait|photo`. Reserves the
|
|
418
449
|
box before the image inside it has loaded, so the page does not reflow when the
|
|
419
450
|
picture arrives. The child fills the box and crops rather than stretching.
|
|
@@ -513,11 +544,14 @@ do not.
|
|
|
513
544
|
**Flex** - `mw-flex-row`, `mw-flex-column`, `mw-flex-wrap`, `mw-flex-nowrap`,
|
|
514
545
|
`mw-flex-1`, `mw-flex-grow-1`, `mw-flex-shrink-0`,
|
|
515
546
|
`mw-justify-start|end|center|between|around|evenly`,
|
|
516
|
-
`mw-items-start|end|center|stretch
|
|
547
|
+
`mw-items-start|end|center|stretch`,
|
|
548
|
+
`mw-self-start|end|center|stretch`.
|
|
517
549
|
|
|
518
550
|
`mw-flex-1` sets `flex: 1` (basis 0, all items equal). `mw-flex-grow-1` only
|
|
519
551
|
grows and keeps the content width as the basis - that is the one you want next
|
|
520
|
-
to an avatar or an icon.
|
|
552
|
+
to an avatar or an icon. `mw-self-*` is the per-item counterpart to
|
|
553
|
+
`mw-items-*`: it goes on the child, for the one that sits differently from the
|
|
554
|
+
rest of the row.
|
|
521
555
|
|
|
522
556
|
**Text** - alignment `mw-text-left|center|right`; colour `mw-text-primary`,
|
|
523
557
|
`-secondary`, `-success`, `-warning`, `-danger`, `-info`, `-muted`,
|