@expressivecss/expressive 0.1.0 → 0.3.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.
Files changed (40) hide show
  1. package/README.md +8 -8
  2. package/dist/css/expressive.css +1088 -356
  3. package/dist/css/expressive.css.map +1 -1
  4. package/dist/css/expressive.min.css +1 -1
  5. package/dist/css/expressive.min.css.map +1 -1
  6. package/dist/js/expressive.cjs +778 -746
  7. package/dist/js/expressive.cjs.map +4 -4
  8. package/dist/js/expressive.js +778 -746
  9. package/dist/js/expressive.js.map +4 -4
  10. package/dist/js/expressive.min.js +10 -10
  11. package/dist/js/expressive.min.js.map +4 -4
  12. package/dist/js/expressive.mjs +778 -746
  13. package/dist/js/expressive.mjs.map +4 -4
  14. package/dist/types/behaviors/dialogs.d.ts +25 -0
  15. package/dist/types/components/buttons.d.ts +17 -41
  16. package/dist/types/components/carousel.d.ts +40 -46
  17. package/dist/types/components/chips.d.ts +2 -2
  18. package/dist/types/components/collapsible.d.ts +35 -13
  19. package/dist/types/components/parallax.d.ts +7 -31
  20. package/dist/types/components/tapTarget.d.ts +23 -24
  21. package/dist/types/index.d.ts +2 -1
  22. package/package.json +1 -1
  23. package/src/sass/README.md +4 -3
  24. package/src/sass/components/_badges.scss +5 -4
  25. package/src/sass/components/_breadcrumb.scss +93 -16
  26. package/src/sass/components/_buttons.scss +195 -96
  27. package/src/sass/components/_carousel.scss +130 -63
  28. package/src/sass/components/_chips.scss +1 -1
  29. package/src/sass/components/_collapsible.scss +160 -60
  30. package/src/sass/components/_collection.scss +161 -77
  31. package/src/sass/components/_icons-material-design.scss +77 -4
  32. package/src/sass/components/_lightbox.scss +4 -0
  33. package/src/sass/components/_modal.scss +67 -3
  34. package/src/sass/components/_navbar.scss +15 -0
  35. package/src/sass/components/_pagination.scss +131 -25
  36. package/src/sass/components/_parallax.scss +116 -20
  37. package/src/sass/components/_sidenav.scss +10 -5
  38. package/src/sass/components/_slider.scss +8 -0
  39. package/src/sass/components/_tapTarget.scss +134 -47
  40. package/src/sass/utilities/_helpers.scss +10 -0
@@ -13,7 +13,7 @@
13
13
  // body body-medium / on-surface-variant
14
14
  // icon 24dp, on-secondary
15
15
  // actions end-aligned, 8dp gap
16
- // scrim 32%
16
+ // scrim 32% + 12px blur
17
17
  // padding 24dp
18
18
 
19
19
  dialog,
@@ -22,6 +22,8 @@ dialog,
22
22
  --md-comp-basic-dialog-container-shape: 28px;
23
23
  --md-comp-basic-dialog-container-min-width: 280px;
24
24
  --md-comp-basic-dialog-container-max-width: 560px;
25
+ --md-comp-basic-dialog-sheet-width: 400px;
26
+ --md-comp-basic-dialog-scrim-blur: 12px;
25
27
 
26
28
  box-sizing: border-box;
27
29
  width: min(var(--md-comp-basic-dialog-container-max-width), calc(100% - 48px));
@@ -38,14 +40,23 @@ dialog,
38
40
 
39
41
  &::backdrop {
40
42
  background-color: color-mix(in oklab, var(--md-sys-color-scrim) 32%, transparent);
43
+ backdrop-filter: blur(var(--md-comp-basic-dialog-scrim-blur));
41
44
  }
42
45
  }
43
46
 
47
+ // showModal() puts the dialog in the top layer but does not lock the
48
+ // document scroller. :modal is the modal-only match — a modeless
49
+ // dialog.show() must not freeze the page.
50
+ html:has(dialog:modal) {
51
+ overflow: hidden;
52
+ }
53
+
44
54
  dialog[open],
45
55
  .modal[open] {
46
56
  display: flex;
47
57
  flex-direction: column;
48
58
  overflow: hidden;
59
+ overscroll-behavior: contain;
49
60
  }
50
61
 
51
62
  // Optional leading icon (M3 basic dialog).
@@ -84,6 +95,7 @@ dialog > div,
84
95
  flex: 1 1 auto;
85
96
  min-height: 0;
86
97
  overflow: auto;
98
+ overscroll-behavior: contain;
87
99
  margin: 0;
88
100
  padding: 0 24px 16px;
89
101
  font-family: var(--md-sys-typescale-body-medium-font-family-name);
@@ -119,9 +131,13 @@ dialog > :is(form, nav):last-child,
119
131
  }
120
132
 
121
133
  // Full-screen (M3). Takes the viewport; no corners.
134
+ // UA `dialog:modal` sets max-width: calc(100% - 2em - 6px). Without
135
+ // clearing it, width: 100% is capped and a strip of scrim shows on the
136
+ // right. Bottom sheet already overrides this.
122
137
  dialog.max,
123
138
  .modal.max {
124
139
  width: 100%;
140
+ max-width: none;
125
141
  height: 100%;
126
142
  min-width: 0;
127
143
  max-height: none;
@@ -129,8 +145,9 @@ dialog.max,
129
145
  border-radius: 0;
130
146
  }
131
147
 
132
- // Bottom sheet. M3 sheets are a sibling component; this keeps the existing
133
- // Expressive helper and BeerCSS's `bottom` name.
148
+ // Sheets. Docked to one edge; the inner corners keep the dialog shape,
149
+ // the edge against the viewport is square. `left` / `right` also float
150
+ // via the helper utilities (`!important`), so sheets reset that.
134
151
  dialog.bottom,
135
152
  dialog.bottom-sheet,
136
153
  .modal.bottom-sheet {
@@ -142,3 +159,50 @@ dialog.bottom-sheet,
142
159
  border-radius: var(--md-comp-basic-dialog-container-shape)
143
160
  var(--md-comp-basic-dialog-container-shape) 0 0;
144
161
  }
162
+
163
+ dialog.top,
164
+ dialog.top-sheet,
165
+ .modal.top-sheet {
166
+ width: 100%;
167
+ min-width: 0;
168
+ max-width: none;
169
+ max-height: 50%;
170
+ margin: 0 0 auto;
171
+ border-radius: 0 0 var(--md-comp-basic-dialog-container-shape)
172
+ var(--md-comp-basic-dialog-container-shape);
173
+ }
174
+
175
+ dialog.left,
176
+ dialog.left-sheet,
177
+ .modal.left-sheet,
178
+ dialog.right,
179
+ dialog.right-sheet,
180
+ .modal.right-sheet {
181
+ float: none !important;
182
+ width: min(var(--md-comp-basic-dialog-sheet-width), 85%);
183
+ max-width: none;
184
+ height: auto;
185
+ max-height: none;
186
+ min-width: 0;
187
+ min-height: 0;
188
+ margin: 0;
189
+ inset-block: 0;
190
+ }
191
+
192
+ dialog.left,
193
+ dialog.left-sheet,
194
+ .modal.left-sheet {
195
+ inset-inline-start: 0;
196
+ inset-inline-end: auto;
197
+ border-radius: 0 var(--md-comp-basic-dialog-container-shape)
198
+ var(--md-comp-basic-dialog-container-shape) 0;
199
+ }
200
+
201
+ dialog.right,
202
+ dialog.right-sheet,
203
+ .modal.right-sheet {
204
+ inset-inline-start: auto;
205
+ inset-inline-end: 0;
206
+ border-radius: var(--md-comp-basic-dialog-container-shape) 0 0
207
+ var(--md-comp-basic-dialog-container-shape);
208
+ }
@@ -122,6 +122,21 @@ header:has(> nav) {
122
122
  }
123
123
  }
124
124
 
125
+ // Breadcrumb trail. Layout only — colors stay on the breadcrumb tokens
126
+ // (which this header remaps to inherit). Do not treat the crumbs as
127
+ // destinations.
128
+ > nav:not(.tabs) > ol {
129
+ display: flex;
130
+ flex-wrap: wrap;
131
+ align-items: center;
132
+ align-self: center;
133
+ flex: 1 1 auto;
134
+ min-width: 0;
135
+ list-style: none;
136
+ margin: 0;
137
+ padding-inline: 12px;
138
+ }
139
+
125
140
  > nav:not(.tabs) > form {
126
141
  display: flex;
127
142
  flex: 1 1 auto;
@@ -1,46 +1,152 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
+ // Pagination. Anatomy is the HTML: a nav.pagination whose child is an
4
+ // <ol> (or <ul>) of page links. The current page is aria-current="page".
5
+ // A bare <ul class="pagination"> stays as an alias.
6
+ //
7
+ // .active / .disabled / .prev / .next / .pages still work.
8
+ // .nowrap (or the old li.pages nest) scrolls a long run instead of wrapping.
9
+ //
10
+ // Tokens (defaults match the original compact pager):
11
+ // --md-comp-pagination-size 2rem
12
+ // --md-comp-pagination-shape 2rem (stadium / disc)
13
+ // --md-comp-pagination-gap 0
14
+ // --md-comp-pagination-padding 0.66rem
15
+ // --md-comp-pagination-icon-size 2rem
16
+ // --md-comp-pagination-selected-color primary
17
+ // --md-comp-pagination-selected-on-color on-primary
18
+ //
19
+ // Nest descendants inside this block.
20
+
3
21
  .pagination {
22
+ --md-comp-pagination-size: 2rem;
23
+ --md-comp-pagination-shape: 2rem;
24
+ --md-comp-pagination-gap: 0px;
25
+ --md-comp-pagination-padding: 0.66rem;
26
+ --md-comp-pagination-icon-size: 2rem;
27
+ --md-comp-pagination-selected-color: var(--md-sys-color-primary);
28
+ --md-comp-pagination-selected-on-color: var(--md-sys-color-on-primary);
29
+
30
+ box-sizing: border-box;
31
+ margin: 0;
32
+ padding: 0;
33
+ color: var(--md-sys-color-on-surface);
34
+ font-size: 1.2rem;
35
+ line-height: 1;
36
+
37
+ &,
38
+ > :is(ul, ol) {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ align-items: center;
42
+ gap: var(--md-comp-pagination-gap);
43
+ list-style: none;
44
+ margin: 0;
45
+ padding: 0;
46
+ }
47
+
48
+ &.nowrap,
49
+ &.nowrap > :is(ul, ol) {
50
+ flex-wrap: nowrap;
51
+ min-width: 0;
52
+ overflow-x: auto;
53
+ scrollbar-width: none;
54
+
55
+ &::-webkit-scrollbar {
56
+ display: none;
57
+ }
58
+ }
59
+
4
60
  li {
5
- display: inline-block;
6
- vertical-align: top;
61
+ display: flex;
62
+ flex-shrink: 0;
63
+ margin: 0;
64
+ padding: 0;
65
+ }
7
66
 
8
- a {
9
- @include btn($height: 2rem, $padding-left: .66rem, $padding-right: .66rem, $font-size: 1.2rem);
67
+ :is(a, button) {
68
+ box-sizing: border-box;
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ gap: 4px;
73
+ min-width: var(--md-comp-pagination-size);
74
+ height: var(--md-comp-pagination-size);
75
+ padding: 0 var(--md-comp-pagination-padding);
76
+ border: 0;
77
+ border-radius: var(--md-comp-pagination-shape);
78
+ background: transparent;
79
+ color: inherit;
80
+ font: inherit;
81
+ line-height: inherit;
82
+ text-decoration: none;
83
+ cursor: pointer;
84
+ -webkit-tap-highlight-color: transparent;
85
+ transition: background-color 0.2s ease-out;
86
+ @include focus-visible;
87
+
88
+ &:hover {
89
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
10
90
  }
11
91
 
12
- &.active a {
13
- @include btn-filled;
92
+ &:has(> span) {
93
+ width: auto;
14
94
  }
15
95
 
16
- &.disabled a {
17
- @include btn-disabled;
96
+ > i {
97
+ font-size: var(--md-comp-pagination-icon-size);
98
+ width: var(--md-comp-pagination-icon-size);
99
+ height: var(--md-comp-pagination-icon-size);
100
+ line-height: 1;
18
101
  }
102
+ }
19
103
 
20
- i {
21
- font-size: 2rem;
104
+ :is(a, button)[aria-current="page"],
105
+ .active > :is(a, button),
106
+ li:has(> [aria-current="page"]) > :is(a, button) {
107
+ background-color: var(--md-comp-pagination-selected-color);
108
+ color: var(--md-comp-pagination-selected-on-color);
109
+
110
+ &:hover {
111
+ background-color: color-mix(
112
+ in oklab,
113
+ var(--md-comp-pagination-selected-on-color) 8%,
114
+ var(--md-comp-pagination-selected-color)
115
+ );
22
116
  }
23
117
  }
24
118
 
25
- li.pages ul li {
26
- display: inline-block;
27
- float: none;
119
+ :is(a, button)[aria-disabled="true"],
120
+ .disabled > :is(a, button) {
121
+ color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
122
+ background-color: transparent;
123
+ pointer-events: none;
124
+ cursor: default;
28
125
  }
29
- }
30
126
 
31
- @include bp-down("large") {
32
- .pagination {
33
- width: 100%;
127
+ li > span {
128
+ box-sizing: border-box;
129
+ display: inline-flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ min-width: var(--md-comp-pagination-size);
133
+ height: var(--md-comp-pagination-size);
134
+ color: var(--md-sys-color-on-surface-variant);
135
+ }
34
136
 
35
- li.prev,
36
- li.next {
37
- width: 10%;
38
- }
137
+ // Old nested-pages markup. Scroll, do not clip to 80%.
138
+ .pages {
139
+ min-width: 0;
39
140
 
40
- li.pages {
41
- width: 80%;
42
- overflow: hidden;
43
- white-space: nowrap;
141
+ > :is(ul, ol) {
142
+ display: flex;
143
+ flex-wrap: inherit;
144
+ align-items: center;
145
+ gap: var(--md-comp-pagination-gap);
146
+ list-style: none;
147
+ min-width: 0;
148
+ margin: 0;
149
+ padding: 0;
44
150
  }
45
151
  }
46
152
  }
@@ -1,29 +1,125 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
- // Paired with components/parallax.ts, which positions the image on scroll.
3
+ // Parallax. Anatomy is the HTML: a .parallax (or <figure class="parallax">)
4
+ // clips an <img>, <picture>, or <video>. A heading / <p> / <figcaption> is
5
+ // the overlay. .parallax-container is the old wrapper and still works.
6
+ //
7
+ // Motion is CSS. A named view timeline on the clip drives the image; without
8
+ // animation-timeline the image is just object-fit: cover. JS does not scroll.
9
+ //
10
+ // Tokens:
11
+ // --md-comp-parallax-height visible window (500px)
12
+ // --md-comp-parallax-travel how far the image moves (64px)
13
+ // --md-comp-parallax-shape 12px, same as media / lightbox / slider
4
14
 
15
+ .parallax,
5
16
  .parallax-container {
17
+ --md-comp-parallax-height: 500px;
18
+ --md-comp-parallax-travel: 120px;
19
+ --md-comp-parallax-shape: 12px;
20
+
6
21
  position: relative;
22
+ isolation: isolate;
7
23
  overflow: hidden;
8
- height: 500px;
9
-
10
- .parallax {
11
- position: absolute;
12
- top: 0;
13
- left: 0;
14
- right: 0;
15
- bottom: 0;
16
- z-index: -1;
17
-
18
- img {
19
- opacity: 0;
20
- position: absolute;
21
- left: 50%;
22
- bottom: 0;
23
- min-width: 100%;
24
- min-height: 100%;
25
- transform: translate3d(0, 0, 0);
26
- transform: translateX(-50%);
24
+ box-sizing: border-box;
25
+ height: var(--md-comp-parallax-height);
26
+ margin: 0;
27
+ border-radius: var(--md-comp-parallax-shape);
28
+ view-timeline-name: --parallax;
29
+ view-timeline-axis: block;
30
+ }
31
+
32
+ // Old markup: container > .parallax > img. The outer box is the clip;
33
+ // the inner .parallax is just a fill, not a second 500px window.
34
+ .parallax-container > .parallax {
35
+ position: absolute;
36
+ inset: 0;
37
+ height: auto;
38
+ margin: 0;
39
+ border-radius: 0;
40
+ overflow: visible;
41
+ view-timeline-name: none;
42
+ }
43
+
44
+ .parallax > :is(img, video),
45
+ .parallax > picture,
46
+ .parallax-container .parallax > :is(img, video),
47
+ .parallax-container .parallax > picture {
48
+ position: absolute;
49
+ inset: 0;
50
+ display: block;
51
+ width: 100%;
52
+ height: 100%;
53
+ border: 0;
54
+ }
55
+
56
+ .parallax > picture img,
57
+ .parallax-container .parallax > picture img {
58
+ display: block;
59
+ width: 100%;
60
+ height: 100%;
61
+ border: 0;
62
+ }
63
+
64
+ .parallax > :is(img, video),
65
+ .parallax > picture img,
66
+ .parallax-container .parallax > :is(img, video),
67
+ .parallax-container .parallax > picture img {
68
+ object-fit: cover;
69
+ object-position: center;
70
+ }
71
+
72
+ // Overlay. A heading or <p> is a centered hero; figcaption docks to the
73
+ // bottom on a translucent surface, same idea as a card figure.
74
+ .parallax > :is(h1, h2, h3, h4, h5, h6),
75
+ .parallax > p {
76
+ position: absolute;
77
+ inset: 0;
78
+ z-index: 1;
79
+ display: grid;
80
+ place-items: center;
81
+ margin: 0;
82
+ padding: 24px;
83
+ color: var(--md-sys-color-on-surface);
84
+ text-align: center;
85
+ }
86
+
87
+ .parallax > figcaption {
88
+ position: absolute;
89
+ inset: auto 0 0;
90
+ z-index: 1;
91
+ margin: 0;
92
+ padding: 16px 24px;
93
+ color: var(--md-sys-color-on-surface);
94
+ background: color-mix(in oklab, var(--md-sys-color-surface) 72%, transparent);
95
+ }
96
+
97
+ // Full-bleed hero. Meets the viewport edges; no corners.
98
+ .parallax.max,
99
+ .parallax-container.max {
100
+ height: 100vh;
101
+ height: 100dvh;
102
+ border-radius: 0;
103
+ }
104
+
105
+ // Progressive enhancement: without animation-timeline the image stays a
106
+ // static cover. Named timeline is the clip, not the (taller) image.
107
+ @supports (animation-timeline: view()) {
108
+ @media (prefers-reduced-motion: no-preference) {
109
+ .parallax > :is(img, video),
110
+ .parallax > picture img,
111
+ .parallax-container .parallax > :is(img, video),
112
+ .parallax-container .parallax > picture img {
113
+ height: calc(100% + 2 * var(--md-comp-parallax-travel));
114
+ inset: calc(-1 * var(--md-comp-parallax-travel)) 0 auto;
115
+ animation: parallax-shift linear both;
116
+ animation-timeline: --parallax;
117
+ animation-range: cover;
27
118
  }
28
119
  }
29
120
  }
121
+
122
+ @keyframes parallax-shift {
123
+ from { transform: translateY(var(--md-comp-parallax-travel)); }
124
+ to { transform: translateY(calc(-1 * var(--md-comp-parallax-travel))); }
125
+ }
@@ -97,8 +97,7 @@
97
97
  }
98
98
 
99
99
  /* TODO: Use special class in future like "mw-icon" */
100
- & > .material-icons, & > .material-symbols-outlined,
101
- & > .material-symbols-rounded, & > .material-symbols-sharp {
100
+ & > :is(.material-symbols, .material-symbols-outlined, .material-symbols-rounded, .material-symbols-sharp, .material-icons) {
102
101
  display: inline-flex;
103
102
  vertical-align: middle;
104
103
  margin-right: 12px;
@@ -214,7 +213,8 @@
214
213
  }
215
214
  }
216
215
 
217
- .sidenav .collapsible-body {
216
+ .sidenav .collapsible-body,
217
+ .sidenav details::details-content {
218
218
  padding: 0;
219
219
  }
220
220
 
@@ -234,13 +234,18 @@
234
234
  .sidenav,
235
235
  .sidenav.sidenav-fixed {
236
236
  .collapsible {
237
+ overflow: visible;
237
238
  border: none;
239
+ border-radius: 0;
240
+ background: transparent;
238
241
  box-shadow: none;
239
242
  }
240
- .collapsible-header {
243
+ .collapsible-header,
244
+ summary {
241
245
  border: none;
242
246
  }
243
- .collapsible-body {
247
+ .collapsible-body,
248
+ details::details-content {
244
249
  border: none;
245
250
  }
246
251
  }
@@ -4,6 +4,14 @@
4
4
  position: relative;
5
5
  height: 400px;
6
6
  width: 100%;
7
+ // Same 12px as .responsive-img / .lightboxed. Applied to the slide
8
+ // stack, not the slider: init() adds 40px under the slides for the
9
+ // indicators, so a radius on the outer box never meets the photo.
10
+ // Fullscreen stays square so it can meet the viewport edges.
11
+ &:not(.fullscreen) .slides,
12
+ &:not(.fullscreen) .slides li {
13
+ border-radius: 12px;
14
+ }
7
15
 
8
16
  // Fullscreen slider
9
17
  &.fullscreen {