@kubex/zinc 1.1.98 → 1.1.100
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/AGENTS.md +5 -0
- package/CLAUDE.md +5 -0
- package/dist/custom-elements.json +876 -18
- package/dist/vscode.html-custom-data.json +148 -9
- package/dist/web-types.json +306 -12
- package/dist/zn.d.ts +203 -9
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +343 -297
- package/docs/pages/components/background.md +143 -0
- package/docs/pages/components/checkbox-group.md +13 -1
- package/docs/pages/components/checkbox.md +59 -1
- package/docs/pages/components/radio-group.md +12 -0
- package/docs/pages/components/radio.md +188 -0
- package/docs/pages/components/remarkd-editor.md +22 -0
- package/docs/pages/components/scroll-container.md +14 -15
- package/package.json +1 -1
- package/scss/_root.scss +16 -0
- package/scss/mixins/_typography-mixins.scss +1 -0
- package/scss/shared/_button.scss +4 -2
- package/scss/themes/_typography.scss +2 -0
- package/src/components/background/background.component.ts +111 -0
- package/src/components/background/background.scss +236 -0
- package/src/components/background/background.test.ts +120 -0
- package/src/components/background/index.ts +12 -0
- package/src/components/button/button.scss +14 -4
- package/src/components/checkbox/checkbox.component.ts +62 -6
- package/src/components/checkbox/checkbox.scss +1 -0
- package/src/components/checkbox/checkbox.test.ts +56 -0
- package/src/components/checkbox-group/checkbox-group.component.ts +15 -0
- package/src/components/checkbox-group/checkbox-group.scss +23 -5
- package/src/components/checkbox-group/checkbox-group.test.ts +15 -0
- package/src/components/confirm/confirm.component.ts +3 -3
- package/src/components/confirm/confirm.test.ts +15 -0
- package/src/components/dialog/dialog.component.ts +3 -2
- package/src/components/editor/editor.component.ts +7 -6
- package/src/components/header/header.scss +3 -1
- package/src/components/menu-item/menu-item.scss +8 -5
- package/src/components/navbar/navbar.scss +11 -0
- package/src/components/page/page.scss +16 -2
- package/src/components/radio/radio.component.ts +63 -7
- package/src/components/radio/radio.scss +1 -0
- package/src/components/radio/radio.test.ts +56 -0
- package/src/components/radio-group/radio-group.component.ts +16 -3
- package/src/components/radio-group/radio-group.scss +23 -4
- package/src/components/radio-group/radio-group.test.ts +15 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +118 -5
- package/src/components/remarkd-editor/remarkd-editor.scss +8 -1
- package/src/components/remarkd-editor/remarkd-editor.test.ts +96 -0
- package/src/components/scroll-container/scroll-container.component.ts +15 -1
- package/src/components/scroll-container/scroll-container.scss +4 -2
- package/src/components/scroll-container/scroll-container.test.ts +13 -0
- package/src/components/slideout/slideout.component.ts +3 -2
- package/src/components/thumbnail-group/thumbnail-group.scss +0 -1
- package/src/components/tile/tile.scss +2 -2
- package/src/components/toggle/toggle.scss +8 -1
- package/src/components/tooltip/tooltip.scss +4 -1
- package/src/components/translation-group/translation-group.test.ts +22 -0
- package/src/components/translations/translations.component.ts +18 -3
- package/src/form-control.scss +3 -1
- package/src/internal/conditional.ts +2 -2
- package/src/internal/form.ts +2 -1
- package/src/internal/selection-card.ts +19 -0
- package/src/selection-card.scss +187 -0
- package/src/utilities/query.test.ts +18 -1
- package/src/utilities/query.ts +8 -0
- package/src/zinc.ts +1 -0
package/scss/shared/_button.scss
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
@use '../variables';
|
|
3
3
|
|
|
4
4
|
@mixin button {
|
|
5
|
-
|
|
5
|
+
// Buttons take their own radius token so a theme can make controls pills
|
|
6
|
+
// without reshaping panels, inputs and dialogs at the same time.
|
|
7
|
+
border-radius: var(--zn-button-border-radius, var(--zn-border-radius));
|
|
6
8
|
display: inline-flex;
|
|
7
9
|
align-items: center;
|
|
8
10
|
justify-content: center;
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
margin: auto;
|
|
11
13
|
width: fit-content;
|
|
12
14
|
background-color: rgb(var(--zn-primary));
|
|
13
|
-
color: rgba(255, 255, 255, 0.9);
|
|
15
|
+
color: rgba(var(--zn-color-on-primary, 255, 255, 255), 0.9);
|
|
14
16
|
font-size: var(--zn-text-button-label-font-size);
|
|
15
17
|
font-weight: var(--zn-text-button-label-font-weight);
|
|
16
18
|
line-height: var(--zn-text-button-label-line-height);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* --zn-text-<role>-font-weight
|
|
13
13
|
* --zn-text-<role>-color
|
|
14
14
|
* --zn-text-<role>-transform
|
|
15
|
+
* --zn-text-<role>-letter-spacing
|
|
15
16
|
*
|
|
16
17
|
* All roles use the sans family (--zn-font-sans). Apply a role
|
|
17
18
|
* in a component with the text-style() mixin from scss/mixins.
|
|
@@ -59,5 +60,6 @@ $text-styles: (
|
|
|
59
60
|
--zn-text-#{$role}-color: var(--zn-text-color);
|
|
60
61
|
}
|
|
61
62
|
--zn-text-#{$role}-transform: #{map.get($style, "transform")};
|
|
63
|
+
--zn-text-#{$role}-letter-spacing: var(--zn-letter-spacing-normal, normal);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
|
+
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
4
|
+
import {styleMap} from 'lit/directives/style-map.js';
|
|
5
|
+
import ZincElement from '../../internal/zinc-element';
|
|
6
|
+
import ZnIcon from '../icon';
|
|
7
|
+
|
|
8
|
+
import styles from './background.scss';
|
|
9
|
+
|
|
10
|
+
export type BackgroundImageStrength = 'soft' | 'medium' | 'full';
|
|
11
|
+
export type BackgroundMotion = 'none' | 'drift' | 'breathe';
|
|
12
|
+
export type BackgroundOverlay = 'none' | 'soft' | 'strong';
|
|
13
|
+
export type BackgroundOverlayTone = 'light' | 'dark';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @summary Composes a colour, decorative image, image strength, optional motion and contrast overlay behind slotted content.
|
|
17
|
+
*
|
|
18
|
+
* @documentation https://zinc.style/components/background
|
|
19
|
+
* @status experimental
|
|
20
|
+
* @since 1.0
|
|
21
|
+
*
|
|
22
|
+
* @dependency zn-icon
|
|
23
|
+
*
|
|
24
|
+
* @slot - Content displayed above the background layers.
|
|
25
|
+
*
|
|
26
|
+
* @csspart base - The component's full-size background canvas.
|
|
27
|
+
* @csspart image - The decorative background image.
|
|
28
|
+
* @csspart overlay - The contrast overlay between the image and content.
|
|
29
|
+
* @csspart floating-icons - The non-interactive layer containing the floating icons.
|
|
30
|
+
* @csspart floating-icon - Each floating `zn-icon`.
|
|
31
|
+
* @csspart content - The wrapper around the default slot.
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty --zn-background-color - Fallback colour when the `color` attribute is not set.
|
|
34
|
+
* @cssproperty --zn-background-image-position - Position of the background image. Defaults to `center`.
|
|
35
|
+
* @cssproperty --zn-background-overlay-angle - Direction of the overlay gradient. Defaults to `110deg`.
|
|
36
|
+
* @cssproperty --zn-background-floating-icon-color - Colour of the floating icons. Defaults to `currentColor`.
|
|
37
|
+
*/
|
|
38
|
+
export default class ZnBackground extends ZincElement {
|
|
39
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
40
|
+
static dependencies = {'zn-icon': ZnIcon};
|
|
41
|
+
|
|
42
|
+
private readonly resizeObserver = new ResizeController<number>(this, {
|
|
43
|
+
callback: entries => {
|
|
44
|
+
const width = entries[0]?.contentRect.width ?? this.offsetWidth;
|
|
45
|
+
return Math.max(1, Math.min(width * 0.05, 150));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** URL of the decorative background image. */
|
|
50
|
+
@property() image = '';
|
|
51
|
+
|
|
52
|
+
/** CSS colour painted beneath the image. */
|
|
53
|
+
@property() color = '';
|
|
54
|
+
|
|
55
|
+
/** Visibility of the image: `soft` (30%), `medium` (62%) or `full` (100%). */
|
|
56
|
+
@property({attribute: 'image-strength', reflect: true}) imageStrength: BackgroundImageStrength = 'full';
|
|
57
|
+
|
|
58
|
+
/** Subtle animation applied to the image. Motion stops when reduced motion is requested. */
|
|
59
|
+
@property({reflect: true}) motion: BackgroundMotion = 'none';
|
|
60
|
+
|
|
61
|
+
/** Strength of the contrast gradient above the image. */
|
|
62
|
+
@property({reflect: true}) overlay: BackgroundOverlay = 'soft';
|
|
63
|
+
|
|
64
|
+
/** Whether the overlay uses a light or dark contrast treatment. */
|
|
65
|
+
@property({attribute: 'overlay-tone', reflect: true}) overlayTone: BackgroundOverlayTone = 'light';
|
|
66
|
+
|
|
67
|
+
/** Comma-separated Zinc icon names placed as ambient decoration. At most eight are rendered. */
|
|
68
|
+
@property({attribute: 'floating-icons'}) floatingIcons = '';
|
|
69
|
+
|
|
70
|
+
/** Pauses background motion without changing the selected motion treatment. */
|
|
71
|
+
@property({type: Boolean, reflect: true}) paused = false;
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
const floatingIcons = this.floatingIcons
|
|
75
|
+
.split(',')
|
|
76
|
+
.map(icon => icon.trim())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.slice(0, 8);
|
|
79
|
+
const floatingIconSize = this.resizeObserver.value ?? 1;
|
|
80
|
+
|
|
81
|
+
return html`
|
|
82
|
+
<div
|
|
83
|
+
part="base"
|
|
84
|
+
class="background"
|
|
85
|
+
style=${styleMap({'--_zn-background-color': this.color || undefined})}>
|
|
86
|
+
${this.image ? html`
|
|
87
|
+
<img
|
|
88
|
+
part="image"
|
|
89
|
+
class="background__image"
|
|
90
|
+
src=${this.image}
|
|
91
|
+
alt=""
|
|
92
|
+
decoding="async">
|
|
93
|
+
` : ''}
|
|
94
|
+
<div part="overlay" class="background__overlay" aria-hidden="true"></div>
|
|
95
|
+
${floatingIcons.length ? html`
|
|
96
|
+
<div part="floating-icons" class="background__floating-icons" aria-hidden="true">
|
|
97
|
+
${floatingIcons.map(icon => html`
|
|
98
|
+
<zn-icon
|
|
99
|
+
part="floating-icon"
|
|
100
|
+
class="background__floating-icon"
|
|
101
|
+
src=${icon}
|
|
102
|
+
size=${floatingIconSize}
|
|
103
|
+
aria-hidden="true"></zn-icon>
|
|
104
|
+
`)}
|
|
105
|
+
</div>
|
|
106
|
+
` : ''}
|
|
107
|
+
<div part="content" class="background__content"><slot></slot></div>
|
|
108
|
+
</div>
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
@use "../../wc";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
position: relative;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
isolation: isolate;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.background {
|
|
13
|
+
position: relative;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
min-height: inherit;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background-color: var(--_zn-background-color, var(--zn-background-color, transparent));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.background__image,
|
|
23
|
+
.background__overlay,
|
|
24
|
+
.background__floating-icons {
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.background__image {
|
|
33
|
+
width: 108%;
|
|
34
|
+
height: 108%;
|
|
35
|
+
max-width: none;
|
|
36
|
+
inset: -4%;
|
|
37
|
+
object-fit: cover;
|
|
38
|
+
object-position: var(--zn-background-image-position, center);
|
|
39
|
+
opacity: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:host([image-strength="soft"]) .background__image {
|
|
43
|
+
opacity: 0.3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:host([image-strength="medium"]) .background__image {
|
|
47
|
+
opacity: 0.62;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.background__overlay {
|
|
51
|
+
background: linear-gradient(
|
|
52
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
53
|
+
rgb(255 255 255 / 18%),
|
|
54
|
+
rgb(255 255 255 / 4%)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:host([overlay="none"]) .background__overlay {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:host([overlay="strong"]) .background__overlay {
|
|
63
|
+
background: linear-gradient(
|
|
64
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
65
|
+
rgb(255 255 255 / 48%),
|
|
66
|
+
rgb(255 255 255 / 18%)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([overlay-tone="dark"]) .background__overlay {
|
|
71
|
+
background: linear-gradient(
|
|
72
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
73
|
+
rgb(4 12 32 / 28%),
|
|
74
|
+
rgb(4 12 32 / 6%)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:host([overlay-tone="dark"][overlay="strong"]) .background__overlay {
|
|
79
|
+
background: linear-gradient(
|
|
80
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
81
|
+
rgb(4 12 32 / 62%),
|
|
82
|
+
rgb(4 12 32 / 28%)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:host([motion="drift"]) .background__image {
|
|
87
|
+
animation: zn-background-drift 28s ease-in-out infinite alternate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:host([motion="breathe"]) .background__image {
|
|
91
|
+
animation: zn-background-breathe 18s ease-in-out infinite;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:host([paused]) .background__image {
|
|
95
|
+
animation-play-state: paused;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.background__floating-icons {
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
color: var(--zn-background-floating-icon-color, currentColor);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.background__floating-icon {
|
|
104
|
+
--_zn-background-icon-opacity-min: 0.01;
|
|
105
|
+
--_zn-background-icon-opacity-max: 0.18;
|
|
106
|
+
position: absolute;
|
|
107
|
+
left: 8%;
|
|
108
|
+
top: 13%;
|
|
109
|
+
opacity: var(--_zn-background-icon-opacity-min);
|
|
110
|
+
animation: zn-background-icon-float 11s ease-in-out infinite alternate both;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.background__floating-icon:nth-child(2) {
|
|
114
|
+
--_zn-background-icon-opacity-min: 0.08;
|
|
115
|
+
--_zn-background-icon-opacity-max: 0.34;
|
|
116
|
+
left: auto;
|
|
117
|
+
right: 9%;
|
|
118
|
+
top: 9%;
|
|
119
|
+
animation-delay: -4s;
|
|
120
|
+
animation-duration: 14s;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.background__floating-icon:nth-child(3) {
|
|
124
|
+
--_zn-background-icon-opacity-min: 0.03;
|
|
125
|
+
--_zn-background-icon-opacity-max: 0.5;
|
|
126
|
+
left: 2%;
|
|
127
|
+
top: 47%;
|
|
128
|
+
animation-delay: -7s;
|
|
129
|
+
animation-duration: 13s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.background__floating-icon:nth-child(4) {
|
|
133
|
+
--_zn-background-icon-opacity-min: 0.12;
|
|
134
|
+
--_zn-background-icon-opacity-max: 0.38;
|
|
135
|
+
left: auto;
|
|
136
|
+
right: 12%;
|
|
137
|
+
top: 45%;
|
|
138
|
+
animation-delay: -2s;
|
|
139
|
+
animation-duration: 10s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.background__floating-icon:nth-child(5) {
|
|
143
|
+
--_zn-background-icon-opacity-min: 0.05;
|
|
144
|
+
--_zn-background-icon-opacity-max: 0.29;
|
|
145
|
+
left: 21%;
|
|
146
|
+
top: auto;
|
|
147
|
+
bottom: 4%;
|
|
148
|
+
animation-delay: -5s;
|
|
149
|
+
animation-duration: 15s;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.background__floating-icon:nth-child(6) {
|
|
153
|
+
--_zn-background-icon-opacity-min: 0.01;
|
|
154
|
+
--_zn-background-icon-opacity-max: 0.42;
|
|
155
|
+
left: auto;
|
|
156
|
+
right: 4%;
|
|
157
|
+
top: auto;
|
|
158
|
+
bottom: 3%;
|
|
159
|
+
animation-delay: -9s;
|
|
160
|
+
animation-duration: 12s;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.background__floating-icon:nth-child(7) {
|
|
164
|
+
--_zn-background-icon-opacity-min: 0.09;
|
|
165
|
+
--_zn-background-icon-opacity-max: 0.31;
|
|
166
|
+
left: 42%;
|
|
167
|
+
top: 7%;
|
|
168
|
+
animation-delay: -6s;
|
|
169
|
+
animation-duration: 16s;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.background__floating-icon:nth-child(8) {
|
|
173
|
+
--_zn-background-icon-opacity-min: 0.04;
|
|
174
|
+
--_zn-background-icon-opacity-max: 0.46;
|
|
175
|
+
left: 54%;
|
|
176
|
+
top: auto;
|
|
177
|
+
bottom: 8%;
|
|
178
|
+
animation-delay: -11s;
|
|
179
|
+
animation-duration: 17s;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
:host([paused]) .background__floating-icon {
|
|
183
|
+
animation-play-state: paused;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.background__content {
|
|
187
|
+
position: relative;
|
|
188
|
+
z-index: 1;
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
width: 100%;
|
|
191
|
+
height: 100%;
|
|
192
|
+
min-height: inherit;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@keyframes zn-background-drift {
|
|
196
|
+
from {
|
|
197
|
+
transform: scale(1.04) translate3d(-1.2%, -0.6%, 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
to {
|
|
201
|
+
transform: scale(1.04) translate3d(1.2%, 0.6%, 0);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@keyframes zn-background-breathe {
|
|
206
|
+
0%, 100% {
|
|
207
|
+
transform: scale(1.02);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
50% {
|
|
211
|
+
transform: scale(1.07);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@keyframes zn-background-icon-float {
|
|
216
|
+
from {
|
|
217
|
+
opacity: var(--_zn-background-icon-opacity-min);
|
|
218
|
+
transform: translate3d(-8%, -12%, 0) scale(0.88) rotate(-8deg);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
to {
|
|
222
|
+
opacity: var(--_zn-background-icon-opacity-max);
|
|
223
|
+
transform: translate3d(8%, 12%, 0) scale(1.06) rotate(8deg);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@media (prefers-reduced-motion: reduce) {
|
|
228
|
+
.background__image,
|
|
229
|
+
.background__floating-icon {
|
|
230
|
+
animation: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.background__floating-icon {
|
|
234
|
+
opacity: var(--_zn-background-icon-opacity-max);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import {expect, fixture, html, waitUntil} from '@open-wc/testing';
|
|
3
|
+
import type ZnBackground from './background.component';
|
|
4
|
+
|
|
5
|
+
const transparentImage = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
|
6
|
+
|
|
7
|
+
describe('<zn-background>', () => {
|
|
8
|
+
it('renders slotted content above the background layers', async () => {
|
|
9
|
+
const el = await fixture<ZnBackground>(html`
|
|
10
|
+
<zn-background><p>Lesson content</p></zn-background>`);
|
|
11
|
+
|
|
12
|
+
const content = el.shadowRoot!.querySelector<HTMLElement>('.background__content')!;
|
|
13
|
+
const slot = content.querySelector<HTMLSlotElement>('slot')!;
|
|
14
|
+
|
|
15
|
+
expect(slot.assignedElements()[0].textContent).to.equal('Lesson content');
|
|
16
|
+
expect(getComputedStyle(content).zIndex).to.equal('1');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('composes the configured colour and decorative image', async () => {
|
|
20
|
+
const el = await fixture<ZnBackground>(html`
|
|
21
|
+
<zn-background color="#123456" image=${transparentImage}></zn-background>`);
|
|
22
|
+
|
|
23
|
+
const base = el.shadowRoot!.querySelector<HTMLElement>('.background')!;
|
|
24
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
25
|
+
|
|
26
|
+
expect(getComputedStyle(base).backgroundColor).to.equal('rgb(18, 52, 86)');
|
|
27
|
+
expect(image.src).to.equal(transparentImage);
|
|
28
|
+
expect(image.alt).to.equal('');
|
|
29
|
+
expect(image.getAttribute('part')).to.equal('image');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not render an image element without an image URL', async () => {
|
|
33
|
+
const el = await fixture<ZnBackground>(html`<zn-background></zn-background>`);
|
|
34
|
+
|
|
35
|
+
expect(el.shadowRoot!.querySelector('.background__image')).to.not.exist;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('applies soft, medium and full image strengths', async () => {
|
|
39
|
+
const el = await fixture<ZnBackground>(html`
|
|
40
|
+
<zn-background image=${transparentImage} image-strength="soft"></zn-background>`);
|
|
41
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
42
|
+
|
|
43
|
+
expect(getComputedStyle(image).opacity).to.equal('0.3');
|
|
44
|
+
el.imageStrength = 'medium';
|
|
45
|
+
await el.updateComplete;
|
|
46
|
+
expect(getComputedStyle(image).opacity).to.equal('0.62');
|
|
47
|
+
el.imageStrength = 'full';
|
|
48
|
+
await el.updateComplete;
|
|
49
|
+
expect(getComputedStyle(image).opacity).to.equal('1');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('supports drift and breathe motion and can pause it', async () => {
|
|
53
|
+
const el = await fixture<ZnBackground>(html`
|
|
54
|
+
<zn-background image=${transparentImage} motion="drift"></zn-background>`);
|
|
55
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
56
|
+
|
|
57
|
+
expect(getComputedStyle(image).animationName).to.equal('zn-background-drift');
|
|
58
|
+
el.motion = 'breathe';
|
|
59
|
+
el.paused = true;
|
|
60
|
+
await el.updateComplete;
|
|
61
|
+
expect(getComputedStyle(image).animationName).to.equal('zn-background-breathe');
|
|
62
|
+
expect(getComputedStyle(image).animationPlayState).to.equal('paused');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('supports none, soft and strong light or dark overlays', async () => {
|
|
66
|
+
const el = await fixture<ZnBackground>(html`
|
|
67
|
+
<zn-background overlay="none"></zn-background>`);
|
|
68
|
+
const overlay = el.shadowRoot!.querySelector<HTMLElement>('.background__overlay')!;
|
|
69
|
+
|
|
70
|
+
expect(getComputedStyle(overlay).display).to.equal('none');
|
|
71
|
+
el.overlay = 'soft';
|
|
72
|
+
await el.updateComplete;
|
|
73
|
+
const lightGradient = getComputedStyle(overlay).backgroundImage;
|
|
74
|
+
expect(lightGradient).to.contain('255, 255, 255');
|
|
75
|
+
|
|
76
|
+
el.overlay = 'strong';
|
|
77
|
+
el.overlayTone = 'dark';
|
|
78
|
+
await el.updateComplete;
|
|
79
|
+
const darkGradient = getComputedStyle(overlay).backgroundImage;
|
|
80
|
+
expect(darkGradient).to.contain('4, 12, 32');
|
|
81
|
+
expect(darkGradient).to.not.equal(lightGradient);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders up to eight comma-separated floating Zinc icons', async () => {
|
|
85
|
+
const el = await fixture<ZnBackground>(html`
|
|
86
|
+
<zn-background
|
|
87
|
+
floating-icons="school, lightbulb, quiz, psychology, star, rocket, science, public, hidden">
|
|
88
|
+
</zn-background>`);
|
|
89
|
+
const icons = el.shadowRoot!.querySelectorAll<HTMLElement>('.background__floating-icon');
|
|
90
|
+
|
|
91
|
+
expect(icons).to.have.lengthOf(8);
|
|
92
|
+
expect(icons[0].getAttribute('src')).to.equal('school');
|
|
93
|
+
expect(icons[7].getAttribute('src')).to.equal('public');
|
|
94
|
+
expect(el.shadowRoot!.querySelector('.background__floating-icons')!.getAttribute('aria-hidden')).to.equal('true');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('sizes floating icons to five percent of the background width', async () => {
|
|
98
|
+
const el = await fixture<ZnBackground>(html`
|
|
99
|
+
<zn-background floating-icons="school" style="width: 1000px;"></zn-background>`);
|
|
100
|
+
const icon = el.shadowRoot!.querySelector<HTMLElement & {size: number}>('.background__floating-icon')!;
|
|
101
|
+
|
|
102
|
+
await waitUntil(() => icon.size === 50);
|
|
103
|
+
expect(icon.size).to.equal(50);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('caps floating icons at 150 pixels and keeps opacity within one to fifty percent', async () => {
|
|
107
|
+
const el = await fixture<ZnBackground>(html`
|
|
108
|
+
<zn-background floating-icons="school,lightbulb" style="width: 4000px;"></zn-background>`);
|
|
109
|
+
const icons = [...el.shadowRoot!.querySelectorAll<HTMLElement & {size: number}>('.background__floating-icon')];
|
|
110
|
+
|
|
111
|
+
await waitUntil(() => icons[0].size === 150);
|
|
112
|
+
expect(icons[0].size).to.equal(150);
|
|
113
|
+
icons.forEach(icon => {
|
|
114
|
+
const styles = getComputedStyle(icon);
|
|
115
|
+
expect(Number(styles.getPropertyValue('--_zn-background-icon-opacity-min'))).to.be.at.least(0.01);
|
|
116
|
+
expect(Number(styles.getPropertyValue('--_zn-background-icon-opacity-max'))).to.be.at.most(0.5);
|
|
117
|
+
expect(styles.animationName).to.equal('zn-background-icon-float');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnBackground from './background.component';
|
|
2
|
+
|
|
3
|
+
export * from './background.component';
|
|
4
|
+
export default ZnBackground;
|
|
5
|
+
|
|
6
|
+
ZnBackground.define('zn-background');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-background': ZnBackground;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
.button {
|
|
18
18
|
@include shared.button;
|
|
19
19
|
|
|
20
|
+
// --btn-color paints the fill; --btn-ink is the text/icon colour that sits
|
|
21
|
+
// on top of it. They are declared as a pair so a theme with a light accent
|
|
22
|
+
// (e.g. neon lime) can re-point the ink via --zn-color-on-* without any
|
|
23
|
+
// component-level override.
|
|
20
24
|
--btn-color: var(--zn-primary);
|
|
25
|
+
--btn-ink: var(--zn-color-on-primary, 255, 255, 255);
|
|
21
26
|
--notification-color: rgb(var(--zn-color-error));
|
|
22
27
|
|
|
23
28
|
border-width: var(--zn-button-border-width, 1px);
|
|
@@ -31,18 +36,22 @@
|
|
|
31
36
|
|
|
32
37
|
&.button--info {
|
|
33
38
|
--btn-color: var(--zn-color-info);
|
|
39
|
+
--btn-ink: var(--zn-color-on-info, 255, 255, 255);
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
&.button--error {
|
|
37
43
|
--btn-color: var(--zn-color-error);
|
|
44
|
+
--btn-ink: var(--zn-color-on-error, 255, 255, 255);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
&.button--warning {
|
|
41
48
|
--btn-color: var(--zn-color-warning);
|
|
49
|
+
--btn-ink: var(--zn-color-on-warning, 255, 255, 255);
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
&.button--success {
|
|
45
53
|
--btn-color: var(--zn-color-success);
|
|
54
|
+
--btn-ink: var(--zn-color-on-success, 255, 255, 255);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
&__label--hidden {
|
|
@@ -131,7 +140,7 @@
|
|
|
131
140
|
|
|
132
141
|
background-color: rgba(var(--btn-color));
|
|
133
142
|
border-color: var(--zn-button-border-color, rgb(var(--btn-color)));
|
|
134
|
-
color: rgba(
|
|
143
|
+
color: rgba(var(--btn-ink), 0.9);
|
|
135
144
|
|
|
136
145
|
&:hover:not([disabled]) {
|
|
137
146
|
background-color: rgba(var(--btn-color), 0.8);
|
|
@@ -154,7 +163,7 @@
|
|
|
154
163
|
|
|
155
164
|
&:hover:not([disabled]), &:active:not([disabled]) {
|
|
156
165
|
background-color: rgb(var(--btn-color));
|
|
157
|
-
color:
|
|
166
|
+
color: rgb(var(--btn-ink));
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
&.button--secondary {
|
|
@@ -219,9 +228,10 @@
|
|
|
219
228
|
|
|
220
229
|
.button--star {
|
|
221
230
|
--btn-color: var(--zn-orange);
|
|
231
|
+
--btn-ink: var(--zn-color-on-warning, 255, 255, 255);
|
|
222
232
|
background-color: rgb(var(--btn-color));
|
|
223
233
|
border-color: var(--zn-button-border-color, rgb(var(--btn-color)));
|
|
224
|
-
color:
|
|
234
|
+
color: rgb(var(--btn-ink));
|
|
225
235
|
opacity: 1 !important;
|
|
226
236
|
|
|
227
237
|
&:hover:not([disabled]) {
|
|
@@ -239,7 +249,7 @@
|
|
|
239
249
|
|
|
240
250
|
&:hover:not([disabled]), &:active:not([disabled]) {
|
|
241
251
|
background-color: rgba(var(--btn-color));
|
|
242
|
-
color:
|
|
252
|
+
color: rgb(var(--btn-ink));
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
255
|
}
|