@kwtv/components 0.8.0 → 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.
- package/README.md +92 -15
- package/package.json +2 -2
- package/src/astro/ActiveFilterTags.astro +4 -34
- package/src/astro/Alert.astro +4 -57
- package/src/astro/Badge.astro +4 -84
- package/src/astro/Checkbox.astro +5 -64
- package/src/astro/Chip.astro +14 -85
- package/src/astro/CopyChip.astro +22 -3
- package/src/astro/EmptyState.astro +5 -32
- package/src/astro/Filters.astro +11 -2
- package/src/astro/IconButton.astro +33 -14
- package/src/astro/Pager.astro +15 -54
- package/src/astro/ProductCard.astro +30 -142
- package/src/astro/Radio.astro +5 -46
- package/src/astro/Skeleton.astro +5 -56
- package/src/astro/Swatch.astro +10 -57
- package/src/react/Drawer.css +105 -1
- package/src/react/Drawer.tsx +178 -19
- package/src/styles/badge.css +95 -0
- package/src/styles/controls.css +3 -0
- package/src/styles/feedback.css +148 -0
- package/src/styles/filters.css +255 -0
- package/src/styles/pager.css +63 -0
- package/src/styles/product-card.css +148 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/feedback.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Empty state. Says what is missing and offers the one action that fixes it.
|
|
4
6
|
* Never a shrug: an empty cart links back to the category page, an empty filter
|
|
5
7
|
* result offers to clear filters.
|
|
8
|
+
*
|
|
9
|
+
* React and Preact islands write the same markup and import
|
|
10
|
+
* `@kwtv/components/styles/feedback.css`.
|
|
6
11
|
*/
|
|
7
12
|
interface Props {
|
|
8
13
|
title: string;
|
|
@@ -24,35 +29,3 @@ const hasAction = Astro.slots.has("action");
|
|
|
24
29
|
</div>
|
|
25
30
|
)}
|
|
26
31
|
</div>
|
|
27
|
-
|
|
28
|
-
<style>
|
|
29
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
30
|
-
|
|
31
|
-
@layer components {
|
|
32
|
-
.ui-empty {
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
align-items: flex-start;
|
|
36
|
-
gap: var(--space-2);
|
|
37
|
-
padding: var(--space-8) var(--space-6);
|
|
38
|
-
border: 1px dashed var(--border-strong);
|
|
39
|
-
border-radius: var(--radius-lg);
|
|
40
|
-
background: var(--surface);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.ui-empty__title {
|
|
44
|
-
font-size: var(--text-lg);
|
|
45
|
-
font-weight: var(--weight-semibold);
|
|
46
|
-
color: var(--text);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.ui-empty__body {
|
|
50
|
-
font-size: var(--text-sm);
|
|
51
|
-
line-height: var(--leading-sm);
|
|
52
|
-
color: var(--text-muted);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* The flex `gap` above already spaces the action from the body; an added */
|
|
56
|
-
/* margin here doubled that gap and threw off the rhythm between the two. */
|
|
57
|
-
}
|
|
58
|
-
</style>
|
package/src/astro/Filters.astro
CHANGED
|
@@ -8,21 +8,30 @@
|
|
|
8
8
|
* with variant="sheet" and place the FilterGroups inside it. The result count
|
|
9
9
|
* should be announced in a polite live region by the consumer when filters
|
|
10
10
|
* change.
|
|
11
|
+
*
|
|
12
|
+
* `titleLabel` is the bar's eyebrow. It defaults to "Filters".
|
|
11
13
|
*/
|
|
12
14
|
interface Props {
|
|
13
15
|
/** Text for the clear-all action. Omit to hide it. */
|
|
14
16
|
clearLabel?: string;
|
|
17
|
+
/** The eyebrow in the bar. Defaults to "Filters". */
|
|
18
|
+
titleLabel?: string;
|
|
15
19
|
class?: string;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
clearLabel = "Wissen",
|
|
24
|
+
titleLabel = "Filters",
|
|
25
|
+
class: className,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props;
|
|
19
28
|
|
|
20
29
|
const classes = ["ui-filters", className].filter(Boolean).join(" ");
|
|
21
30
|
---
|
|
22
31
|
|
|
23
32
|
<aside class={classes} {...rest}>
|
|
24
33
|
<div class="ui-filterbar">
|
|
25
|
-
<span class="ui-eyebrow">
|
|
34
|
+
<span class="ui-eyebrow">{titleLabel}</span>
|
|
26
35
|
{clearLabel && (
|
|
27
36
|
<slot name="clear">
|
|
28
37
|
<button class="ui-filters__clear" type="button">
|
|
@@ -8,6 +8,9 @@ import "../styles/controls.css";
|
|
|
8
8
|
* The count pill is decorative (aria-hidden); the number must be repeated in
|
|
9
9
|
* the label by the consumer, so it is announced once, not twice.
|
|
10
10
|
*
|
|
11
|
+
* Pass `href` to render a link with the same look, for a control that goes to
|
|
12
|
+
* a page, like the cart. The link gets no `type` and no `aria-expanded`.
|
|
13
|
+
*
|
|
11
14
|
* Pass `icon` to use the shared icon set, or a custom SVG via the default slot.
|
|
12
15
|
*/
|
|
13
16
|
interface Props {
|
|
@@ -15,7 +18,11 @@ interface Props {
|
|
|
15
18
|
icon?: string;
|
|
16
19
|
/** Decorative count pill. Repeat the number in `label`. */
|
|
17
20
|
count?: number;
|
|
21
|
+
/** Renders an `<a>` to this URL instead of a `<button>`. */
|
|
22
|
+
href?: string;
|
|
23
|
+
/** Button only. Ignored when `href` is set. */
|
|
18
24
|
type?: "button" | "submit" | "reset";
|
|
25
|
+
/** Button only. Ignored when `href` is set. */
|
|
19
26
|
expanded?: boolean;
|
|
20
27
|
class?: string;
|
|
21
28
|
}
|
|
@@ -24,6 +31,7 @@ const {
|
|
|
24
31
|
label,
|
|
25
32
|
icon,
|
|
26
33
|
count,
|
|
34
|
+
href,
|
|
27
35
|
type = "button",
|
|
28
36
|
expanded,
|
|
29
37
|
class: className,
|
|
@@ -33,17 +41,28 @@ const {
|
|
|
33
41
|
const classes = ["ui-iconbtn", className].filter(Boolean).join(" ");
|
|
34
42
|
---
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
class={classes}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
{href ? (
|
|
45
|
+
<a class={classes} href={href} aria-label={label} {...rest}>
|
|
46
|
+
{icon ? <Icon name={icon as any} size={20} stroke={1.7} /> : <slot />}
|
|
47
|
+
{count !== undefined && (
|
|
48
|
+
<span class="ui-iconbtn__count" aria-hidden="true">
|
|
49
|
+
{count}
|
|
50
|
+
</span>
|
|
51
|
+
)}
|
|
52
|
+
</a>
|
|
53
|
+
) : (
|
|
54
|
+
<button
|
|
55
|
+
class={classes}
|
|
56
|
+
type={type}
|
|
57
|
+
aria-label={label}
|
|
58
|
+
aria-expanded={expanded === undefined ? undefined : String(expanded)}
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{icon ? <Icon name={icon as any} size={20} stroke={1.7} /> : <slot />}
|
|
62
|
+
{count !== undefined && (
|
|
63
|
+
<span class="ui-iconbtn__count" aria-hidden="true">
|
|
64
|
+
{count}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
</button>
|
|
68
|
+
)}
|
package/src/astro/Pager.astro
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Icon from "./Icon.astro";
|
|
3
|
+
import "../styles/pager.css";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Pager. Appears once a list passes its page threshold. The current page is a
|
|
6
7
|
* non-link with aria-current="page"; disabled arrows are spans, not links, so
|
|
7
8
|
* keyboard focus never lands on a dead end. On mobile it collapses to
|
|
8
9
|
* previous / current / next.
|
|
10
|
+
*
|
|
11
|
+
* `previousLabel` and `nextLabel` name the arrow links. They default to the
|
|
12
|
+
* Dutch "Vorige pagina" and "Volgende pagina".
|
|
13
|
+
*
|
|
14
|
+
* React and Preact islands write the same markup and import
|
|
15
|
+
* `@kwtv/components/styles/pager.css`.
|
|
9
16
|
*/
|
|
10
17
|
interface Props {
|
|
11
18
|
current: number;
|
|
@@ -23,6 +30,10 @@ interface Props {
|
|
|
23
30
|
/** The query key the preserveScroll behaviour reads and writes. */
|
|
24
31
|
param?: string;
|
|
25
32
|
label?: string;
|
|
33
|
+
/** Accessible name of the previous arrow. Defaults to "Vorige pagina". */
|
|
34
|
+
previousLabel?: string;
|
|
35
|
+
/** Accessible name of the next arrow. Defaults to "Volgende pagina". */
|
|
36
|
+
nextLabel?: string;
|
|
26
37
|
class?: string;
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -33,6 +44,8 @@ const {
|
|
|
33
44
|
preserveScroll = false,
|
|
34
45
|
param = "page",
|
|
35
46
|
label = "Paginering",
|
|
47
|
+
previousLabel = "Vorige pagina",
|
|
48
|
+
nextLabel = "Volgende pagina",
|
|
36
49
|
class: className,
|
|
37
50
|
...rest
|
|
38
51
|
} = Astro.props;
|
|
@@ -64,7 +77,7 @@ const hasNext = current < total;
|
|
|
64
77
|
{...rest}
|
|
65
78
|
>
|
|
66
79
|
{hasPrev ? (
|
|
67
|
-
<a href={href(current - 1)} aria-label=
|
|
80
|
+
<a href={href(current - 1)} aria-label={previousLabel}>
|
|
68
81
|
<Icon name="chevron-left" size={17} stroke={2.3} />
|
|
69
82
|
</a>
|
|
70
83
|
) : (
|
|
@@ -86,7 +99,7 @@ const hasNext = current < total;
|
|
|
86
99
|
)}
|
|
87
100
|
|
|
88
101
|
{hasNext ? (
|
|
89
|
-
<a href={href(current + 1)} aria-label=
|
|
102
|
+
<a href={href(current + 1)} aria-label={nextLabel}>
|
|
90
103
|
<Icon name="chevron-right" size={17} stroke={2.3} />
|
|
91
104
|
</a>
|
|
92
105
|
) : (
|
|
@@ -166,55 +179,3 @@ const hasNext = current < total;
|
|
|
166
179
|
bindUiPager();
|
|
167
180
|
document.addEventListener("astro:page-load", bindUiPager);
|
|
168
181
|
</script>
|
|
169
|
-
|
|
170
|
-
<style>
|
|
171
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
172
|
-
|
|
173
|
-
@layer components {
|
|
174
|
-
.ui-pager {
|
|
175
|
-
display: flex;
|
|
176
|
-
align-items: center;
|
|
177
|
-
gap: var(--space-1);
|
|
178
|
-
font-size: var(--text-sm);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.ui-pager a,
|
|
182
|
-
.ui-pager span:not(.ui-pager__gap) {
|
|
183
|
-
display: inline-flex;
|
|
184
|
-
align-items: center;
|
|
185
|
-
justify-content: center;
|
|
186
|
-
min-width: 36px;
|
|
187
|
-
height: 36px;
|
|
188
|
-
padding: 0 var(--space-1);
|
|
189
|
-
border-radius: var(--radius-md);
|
|
190
|
-
color: var(--link);
|
|
191
|
-
text-decoration: none;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.ui-pager a:hover {
|
|
195
|
-
background: var(--surface-hover);
|
|
196
|
-
color: var(--link-hover);
|
|
197
|
-
}
|
|
198
|
-
.ui-pager a:focus-visible {
|
|
199
|
-
outline: 2px solid var(--focus);
|
|
200
|
-
outline-offset: 2px;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.ui-pager a.is-current {
|
|
204
|
-
background: var(--primary);
|
|
205
|
-
color: var(--primary-fg);
|
|
206
|
-
font-weight: var(--weight-semibold);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.ui-pager .is-disabled {
|
|
210
|
-
color: var(--border);
|
|
211
|
-
cursor: not-allowed;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.ui-pager__gap {
|
|
215
|
-
min-width: 24px;
|
|
216
|
-
text-align: center;
|
|
217
|
-
color: var(--text-muted);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
</style>
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/product-card.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Product card. The whole card is one link, so the reader hears name, stock and
|
|
4
6
|
* price as a single stop. A corner ribbon marks new, promo, last items or
|
|
5
7
|
* member favourite or sold-out stock, one ribbon at a time. Ribbons are
|
|
6
8
|
* decorative; sold-out text is repeated for assistive technology and the card
|
|
7
9
|
* stays reachable.
|
|
10
|
+
*
|
|
11
|
+
* The ribbon words and the sold-out text default to Dutch. Pass `newLabel`,
|
|
12
|
+
* `promoLabel`, `lastLabel`, `favLabel` and `soldOutLabel` for another
|
|
13
|
+
* language. The screen-reader text is `soldOutLabel` followed by a full stop.
|
|
14
|
+
*
|
|
15
|
+
* React and Preact islands write the same markup and import
|
|
16
|
+
* `@kwtv/components/styles/product-card.css`.
|
|
8
17
|
*/
|
|
9
18
|
interface Props {
|
|
10
19
|
href: string;
|
|
@@ -24,6 +33,16 @@ interface Props {
|
|
|
24
33
|
sizes?: string;
|
|
25
34
|
/** AVIF srcset — when provided, a `<picture>` with an AVIF `<source>` is rendered. */
|
|
26
35
|
avifSrcset?: string;
|
|
36
|
+
/** Ribbon text for `ribbon="new"`. Defaults to "Nieuw". */
|
|
37
|
+
newLabel?: string;
|
|
38
|
+
/** Ribbon text for `ribbon="promo"`. Defaults to "Promo". */
|
|
39
|
+
promoLabel?: string;
|
|
40
|
+
/** Ribbon text for `ribbon="last"`. Defaults to "Laatste". */
|
|
41
|
+
lastLabel?: string;
|
|
42
|
+
/** Ribbon text for `ribbon="fav"`. Defaults to "Favoriet". */
|
|
43
|
+
favLabel?: string;
|
|
44
|
+
/** Sold-out ribbon and screen-reader text. Defaults to "Uitverkocht". */
|
|
45
|
+
soldOutLabel?: string;
|
|
27
46
|
}
|
|
28
47
|
|
|
29
48
|
const {
|
|
@@ -40,14 +59,19 @@ const {
|
|
|
40
59
|
srcset,
|
|
41
60
|
sizes,
|
|
42
61
|
avifSrcset,
|
|
62
|
+
newLabel = "Nieuw",
|
|
63
|
+
promoLabel = "Promo",
|
|
64
|
+
lastLabel = "Laatste",
|
|
65
|
+
favLabel = "Favoriet",
|
|
66
|
+
soldOutLabel = "Uitverkocht",
|
|
43
67
|
...rest
|
|
44
68
|
} = Astro.props;
|
|
45
69
|
|
|
46
70
|
const ribbonLabel: Record<NonNullable<Props["ribbon"]>, string> = {
|
|
47
|
-
new:
|
|
48
|
-
promo:
|
|
49
|
-
last:
|
|
50
|
-
fav:
|
|
71
|
+
new: newLabel,
|
|
72
|
+
promo: promoLabel,
|
|
73
|
+
last: lastLabel,
|
|
74
|
+
fav: favLabel,
|
|
51
75
|
};
|
|
52
76
|
|
|
53
77
|
const classes = ["ui-product", soldOut && "is-disabled", className]
|
|
@@ -61,7 +85,7 @@ const classes = ["ui-product", soldOut && "is-disabled", className]
|
|
|
61
85
|
class={`ui-ribbon ui-ribbon--${soldOut ? "sold-out" : ribbon}`}
|
|
62
86
|
aria-hidden="true"
|
|
63
87
|
>
|
|
64
|
-
<i>{soldOut ?
|
|
88
|
+
<i>{soldOut ? soldOutLabel : ribbonLabel[ribbon!]}</i>
|
|
65
89
|
</span>
|
|
66
90
|
)}
|
|
67
91
|
{avifSrcset ? (
|
|
@@ -91,146 +115,10 @@ const classes = ["ui-product", soldOut && "is-disabled", className]
|
|
|
91
115
|
<span class="ui-product__body">
|
|
92
116
|
<span class="ui-product__title">{title}</span>
|
|
93
117
|
{meta && <span class="ui-product__meta">{meta}</span>}
|
|
94
|
-
{soldOut && <span class="ui-sr-only">
|
|
118
|
+
{soldOut && <span class="ui-sr-only">{soldOutLabel}.</span>}
|
|
95
119
|
<span class="ui-product__price">
|
|
96
120
|
{oldPrice && <s>{oldPrice}</s>}
|
|
97
121
|
{price}
|
|
98
122
|
</span>
|
|
99
123
|
</span>
|
|
100
124
|
</a>
|
|
101
|
-
|
|
102
|
-
<style>
|
|
103
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
104
|
-
|
|
105
|
-
@layer components {
|
|
106
|
-
.ui-product {
|
|
107
|
-
position: relative;
|
|
108
|
-
display: flex;
|
|
109
|
-
flex-direction: column;
|
|
110
|
-
border: 1px solid var(--border);
|
|
111
|
-
border-radius: var(--radius-lg);
|
|
112
|
-
overflow: hidden;
|
|
113
|
-
background: var(--surface);
|
|
114
|
-
color: var(--text);
|
|
115
|
-
text-decoration: none;
|
|
116
|
-
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.ui-product:hover,
|
|
120
|
-
.ui-product.is-hover {
|
|
121
|
-
border-color: var(--border-strong);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.ui-product:focus-visible {
|
|
125
|
-
outline: 2px solid var(--focus);
|
|
126
|
-
outline-offset: 2px;
|
|
127
|
-
box-shadow: 0 0 0 4px var(--focus-outer);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.ui-product__img {
|
|
131
|
-
display: block;
|
|
132
|
-
width: 100%;
|
|
133
|
-
aspect-ratio: 4 / 3;
|
|
134
|
-
object-fit: cover;
|
|
135
|
-
background: var(--surface-hover);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.ui-product__body {
|
|
139
|
-
display: flex;
|
|
140
|
-
flex-direction: column;
|
|
141
|
-
gap: var(--space-1);
|
|
142
|
-
padding: var(--space-3) var(--space-4) var(--space-4);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.ui-product__title {
|
|
146
|
-
font-size: var(--text-base);
|
|
147
|
-
font-weight: var(--weight-semibold);
|
|
148
|
-
color: var(--text);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.ui-product__meta {
|
|
152
|
-
font-size: var(--text-xs);
|
|
153
|
-
color: var(--text-muted);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.ui-product__price {
|
|
157
|
-
display: flex;
|
|
158
|
-
align-items: baseline;
|
|
159
|
-
gap: var(--space-2);
|
|
160
|
-
margin-top: var(--space-1);
|
|
161
|
-
font-size: var(--text-lg);
|
|
162
|
-
font-weight: var(--weight-semibold);
|
|
163
|
-
font-variant-numeric: tabular-nums;
|
|
164
|
-
color: var(--text);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.ui-product__price s {
|
|
168
|
-
font-size: var(--text-sm);
|
|
169
|
-
font-weight: var(--weight-regular);
|
|
170
|
-
color: var(--text-muted);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/* Sold out: card stays readable and clickable. */
|
|
174
|
-
.ui-product.is-disabled .ui-product__img {
|
|
175
|
-
opacity: 0.7;
|
|
176
|
-
}
|
|
177
|
-
.ui-product.is-disabled .ui-product__price {
|
|
178
|
-
color: var(--text-muted);
|
|
179
|
-
font-weight: var(--weight-regular);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/* Ribbon. One per card, decorative. 112 px box. */
|
|
183
|
-
.ui-ribbon {
|
|
184
|
-
position: absolute;
|
|
185
|
-
top: 0;
|
|
186
|
-
left: 0;
|
|
187
|
-
width: 112px;
|
|
188
|
-
height: 112px;
|
|
189
|
-
overflow: hidden;
|
|
190
|
-
pointer-events: none;
|
|
191
|
-
z-index: 1;
|
|
192
|
-
}
|
|
193
|
-
.ui-ribbon i {
|
|
194
|
-
position: absolute;
|
|
195
|
-
top: 22px;
|
|
196
|
-
left: -34px;
|
|
197
|
-
width: 140px;
|
|
198
|
-
transform: rotate(-45deg);
|
|
199
|
-
text-align: center;
|
|
200
|
-
font-size: var(--text-2xs);
|
|
201
|
-
font-weight: var(--weight-bold);
|
|
202
|
-
letter-spacing: var(--tracking-wider);
|
|
203
|
-
text-transform: uppercase;
|
|
204
|
-
font-style: normal;
|
|
205
|
-
padding: var(--space-1) 0;
|
|
206
|
-
color: var(--primary-fg);
|
|
207
|
-
background: var(--primary);
|
|
208
|
-
}
|
|
209
|
-
.ui-ribbon--new i {
|
|
210
|
-
background: var(--primary);
|
|
211
|
-
color: var(--primary-fg);
|
|
212
|
-
}
|
|
213
|
-
.ui-ribbon--promo i {
|
|
214
|
-
background: var(--accent);
|
|
215
|
-
color: var(--accent-fg);
|
|
216
|
-
}
|
|
217
|
-
.ui-ribbon--last i {
|
|
218
|
-
background: var(--warning);
|
|
219
|
-
color: var(--primary-fg);
|
|
220
|
-
}
|
|
221
|
-
.ui-ribbon--fav i {
|
|
222
|
-
background: var(--surface-accent);
|
|
223
|
-
color: var(--surface-accent-fg);
|
|
224
|
-
}
|
|
225
|
-
.ui-ribbon--sold-out i {
|
|
226
|
-
background: var(--danger-bg);
|
|
227
|
-
color: var(--danger);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
@media (prefers-reduced-motion: reduce) {
|
|
231
|
-
.ui-product {
|
|
232
|
-
transition: none;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
</style>
|
package/src/astro/Radio.astro
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/filters.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Radio. Native control tinted with accent-color, wrapped in a label so the
|
|
4
6
|
* whole row is the hit area. One answer per group: give every radio in the
|
|
@@ -6,6 +8,9 @@
|
|
|
6
8
|
*
|
|
7
9
|
* Put the group in a <fieldset> with a <legend> in the consuming page so the
|
|
8
10
|
* question is announced before the options.
|
|
11
|
+
*
|
|
12
|
+
* React and Preact islands write the same markup and import
|
|
13
|
+
* `@kwtv/components/styles/filters.css`.
|
|
9
14
|
*/
|
|
10
15
|
interface Props {
|
|
11
16
|
label?: string;
|
|
@@ -45,49 +50,3 @@ const classes = ["ui-check", disabled && "is-disabled", className]
|
|
|
45
50
|
/>
|
|
46
51
|
<slot>{label}</slot>
|
|
47
52
|
</label>
|
|
48
|
-
|
|
49
|
-
<style>
|
|
50
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
51
|
-
|
|
52
|
-
@layer components {
|
|
53
|
-
.ui-check {
|
|
54
|
-
display: flex;
|
|
55
|
-
align-items: center;
|
|
56
|
-
gap: var(--space-2);
|
|
57
|
-
min-height: 32px;
|
|
58
|
-
font-family: var(--font-sans);
|
|
59
|
-
font-size: var(--text-sm);
|
|
60
|
-
color: var(--text);
|
|
61
|
-
cursor: pointer;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.ui-check input {
|
|
65
|
-
width: 17px;
|
|
66
|
-
height: 17px;
|
|
67
|
-
flex-shrink: 0;
|
|
68
|
-
accent-color: var(--primary);
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.ui-check input:focus-visible {
|
|
73
|
-
outline: 2px solid var(--focus);
|
|
74
|
-
outline-offset: 2px;
|
|
75
|
-
box-shadow: 0 0 0 4px var(--focus-outer);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.ui-check.is-disabled {
|
|
79
|
-
cursor: not-allowed;
|
|
80
|
-
color: var(--text-muted);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.ui-check.is-disabled input {
|
|
84
|
-
cursor: not-allowed;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@media (max-width: 700px), (pointer: coarse) {
|
|
88
|
-
.ui-check {
|
|
89
|
-
min-height: 40px;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
</style>
|
package/src/astro/Skeleton.astro
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/feedback.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Loading skeleton. Blocks match the real layout so nothing jumps when content
|
|
4
6
|
* arrives. The shimmer stops for readers who ask for reduced motion, leaving a
|
|
5
7
|
* flat block. Decorative: aria-hidden by default.
|
|
8
|
+
*
|
|
9
|
+
* React and Preact islands write the same markup and import
|
|
10
|
+
* `@kwtv/components/styles/feedback.css`.
|
|
6
11
|
*/
|
|
7
12
|
interface Props {
|
|
8
13
|
variant?: "img" | "title" | "line" | "block";
|
|
@@ -40,59 +45,3 @@ const style = [
|
|
|
40
45
|
aria-hidden="true"
|
|
41
46
|
{...rest}
|
|
42
47
|
></span>
|
|
43
|
-
|
|
44
|
-
<style>
|
|
45
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
46
|
-
|
|
47
|
-
@layer components {
|
|
48
|
-
.ui-skel {
|
|
49
|
-
display: block;
|
|
50
|
-
background: var(--surface-hover);
|
|
51
|
-
background-image: linear-gradient(
|
|
52
|
-
90deg,
|
|
53
|
-
var(--surface-hover) 0%,
|
|
54
|
-
var(--border) 50%,
|
|
55
|
-
var(--surface-hover) 100%
|
|
56
|
-
);
|
|
57
|
-
background-size: 200% 100%;
|
|
58
|
-
border-radius: var(--radius-md);
|
|
59
|
-
animation: ui-skel-shimmer 1400ms ease-in-out infinite;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.ui-skel--img {
|
|
63
|
-
width: 100%;
|
|
64
|
-
aspect-ratio: 4 / 3;
|
|
65
|
-
border-radius: 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.ui-skel--title {
|
|
69
|
-
height: 0.875rem;
|
|
70
|
-
border-radius: var(--radius-sm);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.ui-skel--line {
|
|
74
|
-
height: 0.625rem;
|
|
75
|
-
border-radius: var(--radius-sm);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.ui-skel--block {
|
|
79
|
-
height: 1rem;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@keyframes ui-skel-shimmer {
|
|
83
|
-
0% {
|
|
84
|
-
background-position: 200% 0;
|
|
85
|
-
}
|
|
86
|
-
100% {
|
|
87
|
-
background-position: -200% 0;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@media (prefers-reduced-motion: reduce) {
|
|
92
|
-
.ui-skel {
|
|
93
|
-
animation: none;
|
|
94
|
-
background-image: none;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
</style>
|