@kwtv/components 0.9.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 -17
- package/package.json +2 -2
- package/src/astro/ActiveFilterTags.astro +4 -34
- 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/Filters.astro +11 -2
- package/src/astro/Pager.astro +15 -54
- package/src/astro/ProductCard.astro +30 -142
- package/src/astro/Radio.astro +5 -46
- package/src/astro/Swatch.astro +10 -57
- package/src/react/Drawer.css +34 -0
- package/src/react/Drawer.tsx +54 -23
- package/src/styles/badge.css +95 -0
- package/src/styles/filters.css +255 -0
- package/src/styles/pager.css +63 -0
- package/src/styles/product-card.css +148 -0
package/README.md
CHANGED
|
@@ -7,15 +7,19 @@ there is no separate library build or consumer alias configuration.
|
|
|
7
7
|
|
|
8
8
|
## What ships
|
|
9
9
|
|
|
10
|
-
| Path
|
|
11
|
-
|
|
|
12
|
-
| `src/astro/*.astro`
|
|
13
|
-
| `src/react/*.tsx`
|
|
14
|
-
| `src/styles/base.css`
|
|
15
|
-
| `src/styles/controls.css`
|
|
16
|
-
| `src/styles/navlink.css`
|
|
17
|
-
| `src/styles/feedback.css`
|
|
18
|
-
| `src/styles/
|
|
10
|
+
| Path | What it is |
|
|
11
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| `src/astro/*.astro` | Static components. Scoped styles, no client JS. |
|
|
13
|
+
| `src/react/*.tsx` | Interactive islands. Select, Modal, Toast, Drawer, QuantityStepper, NewsCarousel, OrderStatus, OrderTimeline. |
|
|
14
|
+
| `src/styles/base.css` | The shared primitives the React islands import: focus ring, touch targets, screen-reader helper. Most Astro components carry their own scoped styles. |
|
|
15
|
+
| `src/styles/controls.css` | The one copy of the button and icon-button styles. `Button` and `IconButton` import it. React islands import it for the `ui-btn` / `ui-iconbtn` classes. |
|
|
16
|
+
| `src/styles/navlink.css` | The one copy of the header nav link styles. `NavLink` and `NavDropdown` import it. |
|
|
17
|
+
| `src/styles/feedback.css` | The one copy of the alert, empty state and skeleton styles. `Alert`, `EmptyState` and `Skeleton` import it. React islands import it for the `ui-alert`, `ui-empty` and `ui-skel` classes. |
|
|
18
|
+
| `src/styles/filters.css` | The one copy of the chip, swatch, checkbox, radio and active filter tag styles. `Chip`, `Swatch`, `Checkbox`, `Radio` and `ActiveFilterTags` import it. React islands import it for the `ui-chip`, `ui-swatch`, `ui-check` and `ui-filtertags` classes. |
|
|
19
|
+
| `src/styles/badge.css` | The one copy of the badge styles. `Badge` imports it. React islands import it for the `ui-badge` classes, also for the active filter tags. |
|
|
20
|
+
| `src/styles/pager.css` | The one copy of the pager styles. `Pager` imports it. React islands import it for the `ui-pager` classes. |
|
|
21
|
+
| `src/styles/product-card.css` | The one copy of the product card and ribbon styles. `ProductCard` imports it. React islands import it for the `ui-product` and `ui-ribbon` classes. |
|
|
22
|
+
| `src/styles/motion.css` | The `pl-` motion layer: lift, underline, arrow and row nudges, rise, marquee, chip rail and swipe carousel. |
|
|
19
23
|
|
|
20
24
|
## Install
|
|
21
25
|
|
|
@@ -64,16 +68,86 @@ import Select from "@kwtv/components/react/Select.tsx";
|
|
|
64
68
|
/>
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
The React islands need their shared stylesheets once in the app.
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
The React islands need their shared stylesheets once in the app. Astro
|
|
72
|
+
cannot pass its scoped styles into a React island, so an island that writes
|
|
73
|
+
the markup of an Astro component imports that component's sheet:
|
|
70
74
|
|
|
71
75
|
```ts
|
|
72
76
|
import "@kwtv/components/styles/base.css";
|
|
73
|
-
import "@kwtv/components/styles/controls.css";
|
|
74
|
-
import "@kwtv/components/styles/feedback.css";
|
|
77
|
+
import "@kwtv/components/styles/controls.css"; // ui-btn, ui-iconbtn
|
|
78
|
+
import "@kwtv/components/styles/feedback.css"; // ui-alert, ui-empty, ui-skel
|
|
79
|
+
import "@kwtv/components/styles/filters.css"; // ui-chip, ui-swatch, ui-check, ui-filtertags
|
|
80
|
+
import "@kwtv/components/styles/badge.css"; // ui-badge
|
|
81
|
+
import "@kwtv/components/styles/pager.css"; // ui-pager
|
|
82
|
+
import "@kwtv/components/styles/product-card.css"; // ui-product, ui-ribbon
|
|
75
83
|
```
|
|
76
84
|
|
|
85
|
+
The island writes the same markup as the Astro component, class for class.
|
|
86
|
+
The rules in these sheets are not scoped, so a consumer rule of equal
|
|
87
|
+
specificity in the `components` layer wins on source order.
|
|
88
|
+
|
|
89
|
+
## Labels and other languages
|
|
90
|
+
|
|
91
|
+
The visible and screen-reader text inside the components defaults to Dutch.
|
|
92
|
+
Every such string has its own optional prop named `<thing>Label`. Leave it
|
|
93
|
+
out and the Dutch text renders exactly as before. Pass it to render another
|
|
94
|
+
language.
|
|
95
|
+
|
|
96
|
+
| Component | Prop | Default |
|
|
97
|
+
| ------------------ | ------------------------------------------------------------------------------- | ----------------------- |
|
|
98
|
+
| `ProductCard` | `newLabel` | `Nieuw` |
|
|
99
|
+
| | `promoLabel` | `Promo` |
|
|
100
|
+
| | `lastLabel` | `Laatste` |
|
|
101
|
+
| | `favLabel` | `Favoriet` |
|
|
102
|
+
| | `soldOutLabel` | `Uitverkocht` |
|
|
103
|
+
| `Pager` | `label` | `Paginering` |
|
|
104
|
+
| | `previousLabel` | `Vorige pagina` |
|
|
105
|
+
| | `nextLabel` | `Volgende pagina` |
|
|
106
|
+
| `Swatch` | `selectedLabel` | `geselecteerd` |
|
|
107
|
+
| `Filters` | `titleLabel` | `Filters` |
|
|
108
|
+
| | `clearLabel` | `Wissen` |
|
|
109
|
+
| `ActiveFilterTags` | `clearAllLabel` | `Alles wissen` |
|
|
110
|
+
| `Badge` | `removeLabel` | `Verwijderen` |
|
|
111
|
+
| `Breadcrumb` | `label` | `Kruimelpad` |
|
|
112
|
+
| `ChipRail` | `label` | `Categorieën` |
|
|
113
|
+
| `SwipeCarousel` | `label` | `Uitgelichte artikelen` |
|
|
114
|
+
| `CartLine` | `removeLabel` | `Verwijderen` |
|
|
115
|
+
| `CopyChip` | `label` | `Copy` |
|
|
116
|
+
| | `copiedLabel` | `Copied` |
|
|
117
|
+
| `Newsletter` | `label`, `placeholder`, `buttonText` | Dutch |
|
|
118
|
+
| `Social` | `label` | The platform name |
|
|
119
|
+
| `Drawer` | `label` | `Menu` |
|
|
120
|
+
| `Toast` | `closeLabel` | `Melding sluiten` |
|
|
121
|
+
| `Select` | `placeholder` | `Kies een optie` |
|
|
122
|
+
| `QuantityStepper` | `decrementLabel`, `incrementLabel`, `removeLabel`, `announce`, `announceRemove` | Dutch |
|
|
123
|
+
| `NewsCarousel` | `prevLabel`, `nextLabel`, `dotLabel` | Dutch |
|
|
124
|
+
| `OrderStatus` | `title`, `orderLabel` | Dutch |
|
|
125
|
+
| `OrderTimeline` | `stateWords` | Dutch |
|
|
126
|
+
|
|
127
|
+
`Swatch` adds `selectedLabel` to the colour name after a comma, so
|
|
128
|
+
`label="Sage" selectedLabel="selected"` is read as "Sage, selected". The
|
|
129
|
+
`ProductCard` screen-reader text is `soldOutLabel` followed by a full stop.
|
|
130
|
+
`AccountNav`, `AuthCard`, `StepIndicator`, `TextField`, `Totals` and `Modal`
|
|
131
|
+
take all their text from props or slots.
|
|
132
|
+
|
|
133
|
+
Older props that do not follow the `<thing>Label` name, such as `label`,
|
|
134
|
+
`buttonText` and `stateWords`, keep their names.
|
|
135
|
+
|
|
136
|
+
## Toggle chips and plain buttons
|
|
137
|
+
|
|
138
|
+
`Chip` is a toggle by default and always carries `aria-pressed`, `"true"` or
|
|
139
|
+
`"false"` from `selected`. Pass `toggle={false}` for a chip that only opens or
|
|
140
|
+
runs something, such as the button that opens the filter sheet. It then has
|
|
141
|
+
no `aria-pressed`.
|
|
142
|
+
|
|
143
|
+
## Drawer title and width
|
|
144
|
+
|
|
145
|
+
`title` shows an `h2` in the top row of the `Drawer`, left of the close button,
|
|
146
|
+
and names the drawer through `aria-labelledby` in place of `label`.
|
|
147
|
+
`size="full"` lets a `start` or `end` drawer fill the viewport width on
|
|
148
|
+
screens up to 700 px wide. Bottom drawers and the panel under the header are
|
|
149
|
+
full width already. Without these props the drawer renders as before.
|
|
150
|
+
|
|
77
151
|
## Cascade layers
|
|
78
152
|
|
|
79
153
|
Every stylesheet in this package and in `@kwtv/tokens` sits in a cascade
|
|
@@ -88,7 +162,7 @@ layer, in one fixed order:
|
|
|
88
162
|
| `reset` | Empty in the packages. The app's element defaults go here. |
|
|
89
163
|
| `tokens` | The token custom properties, in `palette`, `theme` and `scale`. |
|
|
90
164
|
| `base` | `base.css`: focus ring, touch targets, screen-reader helper. |
|
|
91
|
-
| `components` | Astro component styles, React island CSS and
|
|
165
|
+
| `components` | Astro component styles, React island CSS and the shared sheets. |
|
|
92
166
|
| `motion` | `motion.css`. Above components, because it adjusts their parts. |
|
|
93
167
|
| `utilities` | `@kwtv/tokens/utilities.css`. |
|
|
94
168
|
|
|
@@ -114,8 +188,9 @@ statement or has a rule outside its layer.
|
|
|
114
188
|
- Use the `.astro`, `.tsx` and `.css` extensions shown in these examples.
|
|
115
189
|
- Import `@kwtv/components/styles/motion.css` when using the motion utilities.
|
|
116
190
|
- Class names keep the `ui-` prefix from the component library so markup reads
|
|
117
|
-
the same across both sites. Astro scopes them; the
|
|
118
|
-
and for consumer
|
|
191
|
+
the same across both sites. Astro scopes most of them; the shared sheets in
|
|
192
|
+
`src/styles/` are not scoped. The prefix is for readability and for consumer
|
|
193
|
+
overrides.
|
|
119
194
|
- Colour, space, type, radius, shadow and motion all come from the token CSS
|
|
120
195
|
variables. Components never hardcode a hex value.
|
|
121
196
|
- Status is never colour alone. Every status pairs an icon with a word.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kwtv/components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "KWTV UI components for Astro, with React islands. Built on the KWTV design tokens.",
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@kwtv/tokens": "^0.
|
|
42
|
+
"@kwtv/tokens": "^0.10.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Badge from "./Badge.astro";
|
|
3
|
+
import "../styles/filters.css";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The active filters repeated as removable tags above the grid, so the reader
|
|
@@ -7,6 +8,9 @@ import Badge from "./Badge.astro";
|
|
|
7
8
|
* names the filter it clears. A trailing clear-all action removes them at once.
|
|
8
9
|
*
|
|
9
10
|
* The row wraps. The consumer wires each removeLabel to the matching filter.
|
|
11
|
+
*
|
|
12
|
+
* React and Preact islands write the same markup and import
|
|
13
|
+
* `@kwtv/components/styles/badge.css` and `@kwtv/components/styles/filters.css`.
|
|
10
14
|
*/
|
|
11
15
|
interface Tag {
|
|
12
16
|
label: string;
|
|
@@ -42,37 +46,3 @@ const classes = ["ui-filtertags", className].filter(Boolean).join(" ");
|
|
|
42
46
|
</button>
|
|
43
47
|
)}
|
|
44
48
|
</div>
|
|
45
|
-
|
|
46
|
-
<style>
|
|
47
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
48
|
-
|
|
49
|
-
@layer components {
|
|
50
|
-
.ui-filtertags {
|
|
51
|
-
display: flex;
|
|
52
|
-
flex-wrap: wrap;
|
|
53
|
-
align-items: center;
|
|
54
|
-
gap: var(--space-2);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.ui-filtertags__clear {
|
|
58
|
-
padding: var(--space-1-5) var(--space-2);
|
|
59
|
-
background: transparent;
|
|
60
|
-
border: 0;
|
|
61
|
-
border-radius: var(--radius-sm);
|
|
62
|
-
font-family: var(--font-sans);
|
|
63
|
-
font-size: var(--text-xs);
|
|
64
|
-
font-weight: var(--weight-semibold);
|
|
65
|
-
color: var(--link);
|
|
66
|
-
cursor: pointer;
|
|
67
|
-
}
|
|
68
|
-
.ui-filtertags__clear:hover {
|
|
69
|
-
background: var(--surface-hover);
|
|
70
|
-
color: var(--link-hover);
|
|
71
|
-
}
|
|
72
|
-
.ui-filtertags__clear: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
|
-
</style>
|
package/src/astro/Badge.astro
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Icon from "./Icon.astro";
|
|
3
|
+
import "../styles/badge.css";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Badge. Status and stock. Each status tone carries an icon so the meaning
|
|
@@ -9,6 +10,9 @@ import Icon from "./Icon.astro";
|
|
|
9
10
|
*
|
|
10
11
|
* Stock badges should repeat the exact number in text rather than only shading
|
|
11
12
|
* the pill.
|
|
13
|
+
*
|
|
14
|
+
* React and Preact islands write the same markup and import
|
|
15
|
+
* `@kwtv/components/styles/badge.css`.
|
|
12
16
|
*/
|
|
13
17
|
interface Props {
|
|
14
18
|
tone?: "neutral" | "success" | "warning" | "danger" | "outline";
|
|
@@ -60,87 +64,3 @@ const classes = [
|
|
|
60
64
|
</button>
|
|
61
65
|
)}
|
|
62
66
|
</span>
|
|
63
|
-
|
|
64
|
-
<style>
|
|
65
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
66
|
-
|
|
67
|
-
@layer components {
|
|
68
|
-
.ui-badge {
|
|
69
|
-
display: inline-flex;
|
|
70
|
-
align-items: center;
|
|
71
|
-
gap: var(--space-1-5);
|
|
72
|
-
min-height: 24px;
|
|
73
|
-
padding: 0 var(--space-2);
|
|
74
|
-
border: 1px solid transparent;
|
|
75
|
-
border-radius: var(--radius-full);
|
|
76
|
-
font-family: var(--font-sans);
|
|
77
|
-
font-size: var(--text-xs);
|
|
78
|
-
font-weight: var(--weight-medium);
|
|
79
|
-
line-height: 1;
|
|
80
|
-
white-space: nowrap;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.ui-badge__icon {
|
|
84
|
-
display: inline-flex;
|
|
85
|
-
flex-shrink: 0;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.ui-badge--neutral {
|
|
89
|
-
background: var(--surface-accent);
|
|
90
|
-
color: var(--surface-accent-fg, var(--text));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.ui-badge--success {
|
|
94
|
-
background: var(--success-bg);
|
|
95
|
-
color: var(--success);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.ui-badge--warning {
|
|
99
|
-
background: var(--warning-bg);
|
|
100
|
-
color: var(--warning);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.ui-badge--danger {
|
|
104
|
-
background: var(--danger-bg);
|
|
105
|
-
color: var(--danger);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* Outline keeps the strong border and full-strength text, so it reads */
|
|
109
|
-
/* without relying on a fill. */
|
|
110
|
-
.ui-badge--outline {
|
|
111
|
-
background: transparent;
|
|
112
|
-
border: 1px solid var(--border-strong);
|
|
113
|
-
color: var(--text);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.ui-badge--removable {
|
|
117
|
-
padding-right: var(--space-1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* Small × target grows with padding plus an equal negative margin, so the */
|
|
121
|
-
/* hit area grows without moving the layout. */
|
|
122
|
-
.ui-badge__x {
|
|
123
|
-
display: inline-flex;
|
|
124
|
-
align-items: center;
|
|
125
|
-
justify-content: center;
|
|
126
|
-
padding: var(--space-2);
|
|
127
|
-
margin: calc(-1 * var(--space-2));
|
|
128
|
-
margin-left: 0;
|
|
129
|
-
background: transparent;
|
|
130
|
-
border: 0;
|
|
131
|
-
border-radius: var(--radius-sm);
|
|
132
|
-
color: inherit;
|
|
133
|
-
cursor: pointer;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.ui-badge__x:hover {
|
|
137
|
-
color: var(--danger);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.ui-badge__x:focus-visible {
|
|
141
|
-
outline: 2px solid var(--focus);
|
|
142
|
-
outline-offset: 2px;
|
|
143
|
-
box-shadow: 0 0 0 4px var(--focus-outer);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
</style>
|
package/src/astro/Checkbox.astro
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/filters.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Checkbox. Custom-styled native control: appearance:none box with a CSS
|
|
4
6
|
* checkmark SVG, so the checked state is consistent across surfaces and themes.
|
|
@@ -7,6 +9,9 @@
|
|
|
7
9
|
*
|
|
8
10
|
* When several checkboxes form a question, put them in a <fieldset> with a
|
|
9
11
|
* <legend> in the consuming page so the question is announced first.
|
|
12
|
+
*
|
|
13
|
+
* React and Preact islands write the same markup and import
|
|
14
|
+
* `@kwtv/components/styles/filters.css`.
|
|
10
15
|
*/
|
|
11
16
|
interface Props {
|
|
12
17
|
label?: string;
|
|
@@ -46,67 +51,3 @@ const classes = ["ui-check", disabled && "is-disabled", className]
|
|
|
46
51
|
/>
|
|
47
52
|
<slot>{label}</slot>
|
|
48
53
|
</label>
|
|
49
|
-
|
|
50
|
-
<style>
|
|
51
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
52
|
-
|
|
53
|
-
@layer components {
|
|
54
|
-
.ui-check {
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: flex-start;
|
|
57
|
-
gap: var(--space-2);
|
|
58
|
-
min-height: 32px;
|
|
59
|
-
font-family: var(--font-sans);
|
|
60
|
-
font-size: var(--text-sm);
|
|
61
|
-
color: var(--text);
|
|
62
|
-
cursor: pointer;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.ui-check input {
|
|
66
|
-
appearance: none;
|
|
67
|
-
-webkit-appearance: none;
|
|
68
|
-
width: 17px;
|
|
69
|
-
height: 17px;
|
|
70
|
-
flex-shrink: 0;
|
|
71
|
-
margin-top: calc((var(--leading-sm, 1.375rem) - 17px) / 2);
|
|
72
|
-
border: 1.5px solid var(--border-strong);
|
|
73
|
-
border-radius: var(--radius-xs, 3px);
|
|
74
|
-
background: var(--surface);
|
|
75
|
-
cursor: pointer;
|
|
76
|
-
transition:
|
|
77
|
-
background-color var(--duration-fast, 100ms) var(--ease-standard, ease),
|
|
78
|
-
border-color var(--duration-fast, 100ms) var(--ease-standard, ease);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.ui-check input:checked {
|
|
82
|
-
background: var(--primary);
|
|
83
|
-
border-color: var(--primary);
|
|
84
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='1 5 4.5 8.5 11 1'/%3E%3C/svg%3E");
|
|
85
|
-
background-repeat: no-repeat;
|
|
86
|
-
background-position: center;
|
|
87
|
-
background-size: 10px 8px;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.ui-check input:focus-visible {
|
|
91
|
-
outline: 2px solid var(--focus);
|
|
92
|
-
outline-offset: 2px;
|
|
93
|
-
box-shadow: 0 0 0 4px var(--focus-outer);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.ui-check.is-disabled {
|
|
97
|
-
cursor: not-allowed;
|
|
98
|
-
color: var(--text-muted);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.ui-check.is-disabled input {
|
|
102
|
-
cursor: not-allowed;
|
|
103
|
-
opacity: 0.45;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@media (max-width: 700px), (pointer: coarse) {
|
|
107
|
-
.ui-check {
|
|
108
|
-
min-height: 40px;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
</style>
|
package/src/astro/Chip.astro
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import "../styles/filters.css";
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Chip. Size selection and filter shortcuts. A <button> with aria-pressed, not
|
|
4
6
|
* a link. Selection adds a 2 px border so it reads without colour. Sold-out
|
|
@@ -7,12 +9,22 @@
|
|
|
7
9
|
*
|
|
8
10
|
* The `--filter` variant is the 32 px shortcut that opens the filter sheet on
|
|
9
11
|
* mobile.
|
|
12
|
+
*
|
|
13
|
+
* A chip is a toggle by default and always carries aria-pressed. Pass
|
|
14
|
+
* `toggle={false}` for a chip that is a plain button, such as the one that
|
|
15
|
+
* opens the filter sheet. It then has no aria-pressed, and `selected` only
|
|
16
|
+
* sets the look.
|
|
17
|
+
*
|
|
18
|
+
* React and Preact islands write the same markup and import
|
|
19
|
+
* `@kwtv/components/styles/filters.css`.
|
|
10
20
|
*/
|
|
11
21
|
interface Props {
|
|
12
22
|
variant?: "default" | "filter";
|
|
13
23
|
selected?: boolean;
|
|
14
24
|
disabled?: boolean;
|
|
15
25
|
strikethrough?: boolean;
|
|
26
|
+
/** Set to false for a plain button. Drops aria-pressed. Defaults to true. */
|
|
27
|
+
toggle?: boolean;
|
|
16
28
|
type?: "button" | "submit";
|
|
17
29
|
class?: string;
|
|
18
30
|
}
|
|
@@ -22,6 +34,7 @@ const {
|
|
|
22
34
|
selected = false,
|
|
23
35
|
disabled = false,
|
|
24
36
|
strikethrough = false,
|
|
37
|
+
toggle = true,
|
|
25
38
|
type = "button",
|
|
26
39
|
class: className,
|
|
27
40
|
...rest
|
|
@@ -42,93 +55,9 @@ const classes = [
|
|
|
42
55
|
<button
|
|
43
56
|
class={classes}
|
|
44
57
|
type={type}
|
|
45
|
-
aria-pressed={String(selected)}
|
|
58
|
+
aria-pressed={toggle ? String(selected) : undefined}
|
|
46
59
|
disabled={disabled}
|
|
47
60
|
{...rest}
|
|
48
61
|
>
|
|
49
62
|
<slot />
|
|
50
63
|
</button>
|
|
51
|
-
|
|
52
|
-
<style>
|
|
53
|
-
@layer reset, tokens, base, components, motion, utilities;
|
|
54
|
-
|
|
55
|
-
@layer components {
|
|
56
|
-
.ui-chip {
|
|
57
|
-
display: inline-flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
justify-content: center;
|
|
60
|
-
min-height: 36px;
|
|
61
|
-
padding: 0 var(--space-4);
|
|
62
|
-
background: var(--surface);
|
|
63
|
-
border: 1px solid var(--border);
|
|
64
|
-
border-radius: var(--radius-md);
|
|
65
|
-
font-family: var(--font-sans);
|
|
66
|
-
font-size: var(--text-sm);
|
|
67
|
-
font-weight: var(--weight-medium);
|
|
68
|
-
color: var(--text);
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
transition:
|
|
71
|
-
border-color var(--duration-fast) var(--ease-standard),
|
|
72
|
-
background-color var(--duration-fast) var(--ease-standard);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.ui-chip:hover,
|
|
76
|
-
.ui-chip.is-hover {
|
|
77
|
-
border-color: var(--border-strong);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.ui-chip:focus-visible,
|
|
81
|
-
.ui-chip.is-focus {
|
|
82
|
-
outline: 2px solid var(--focus);
|
|
83
|
-
outline-offset: 2px;
|
|
84
|
-
box-shadow: 0 0 0 4px var(--focus-outer);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/* Selected: 2 px border plus the accent surface, readable without colour. */
|
|
88
|
-
.ui-chip.is-selected,
|
|
89
|
-
.ui-chip[aria-pressed="true"] {
|
|
90
|
-
border-width: 2px;
|
|
91
|
-
border-color: var(--border-strong);
|
|
92
|
-
background: var(--surface-accent);
|
|
93
|
-
color: var(--surface-accent-fg, var(--text));
|
|
94
|
-
font-weight: var(--weight-semibold);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.ui-chip.is-disabled,
|
|
98
|
-
.ui-chip:disabled {
|
|
99
|
-
opacity: 0.5;
|
|
100
|
-
cursor: not-allowed;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.ui-chip--struck {
|
|
104
|
-
text-decoration: line-through;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* Filter chip: 32 px. */
|
|
108
|
-
.ui-chip--filter {
|
|
109
|
-
min-height: 32px;
|
|
110
|
-
border-radius: var(--radius-full);
|
|
111
|
-
padding: 0 var(--space-3);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.ui-chip--filter.is-selected,
|
|
115
|
-
.ui-chip--filter[aria-pressed="true"] {
|
|
116
|
-
background: var(--surface-accent);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
@media (prefers-reduced-motion: reduce) {
|
|
120
|
-
.ui-chip {
|
|
121
|
-
transition: none;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@media (max-width: 700px), (pointer: coarse) {
|
|
126
|
-
.ui-chip {
|
|
127
|
-
min-height: 36px;
|
|
128
|
-
}
|
|
129
|
-
.ui-chip--filter {
|
|
130
|
-
min-height: 40px;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
</style>
|
package/src/astro/CopyChip.astro
CHANGED
|
@@ -7,19 +7,37 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @prop {string} text The value to display and copy.
|
|
9
9
|
* @prop {string} [label] Accessible label for the copy button (default: "Copy").
|
|
10
|
+
* @prop {string} [copiedLabel] Accessible label once copied (default: "Copied").
|
|
10
11
|
* @prop {string} [class]
|
|
12
|
+
*
|
|
13
|
+
* The script reads both labels from `data-label` and `data-copied-label`.
|
|
14
|
+
* They are written only when they differ from the default, so markup with the
|
|
15
|
+
* defaults stays the same.
|
|
11
16
|
*/
|
|
12
17
|
interface Props {
|
|
13
18
|
text: string;
|
|
14
19
|
label?: string;
|
|
20
|
+
copiedLabel?: string;
|
|
15
21
|
class?: string;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
text,
|
|
26
|
+
label = "Copy",
|
|
27
|
+
copiedLabel = "Copied",
|
|
28
|
+
class: className,
|
|
29
|
+
...rest
|
|
30
|
+
} = Astro.props;
|
|
19
31
|
const classes = ["ui-copy-chip", className].filter(Boolean).join(" ");
|
|
20
32
|
---
|
|
21
33
|
|
|
22
|
-
<span
|
|
34
|
+
<span
|
|
35
|
+
class={classes}
|
|
36
|
+
data-copy={text}
|
|
37
|
+
data-label={label !== "Copy" ? label : undefined}
|
|
38
|
+
data-copied-label={copiedLabel !== "Copied" ? copiedLabel : undefined}
|
|
39
|
+
{...rest}
|
|
40
|
+
>
|
|
23
41
|
<span class="ui-copy-chip__text">{text}</span>
|
|
24
42
|
</span>
|
|
25
43
|
|
|
@@ -29,6 +47,7 @@ const classes = ["ui-copy-chip", className].filter(Boolean).join(" ");
|
|
|
29
47
|
|
|
30
48
|
const text = el.getAttribute("data-copy") ?? "";
|
|
31
49
|
const label = el.getAttribute("data-label") ?? "Copy";
|
|
50
|
+
const copiedLabel = el.getAttribute("data-copied-label") ?? "Copied";
|
|
32
51
|
|
|
33
52
|
const COPY_ICON = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5"/><path d="M3 11V3a1 1 0 0 1 1-1h8"/></svg>`;
|
|
34
53
|
const CHECK_ICON = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="2 8 6 12 14 4"/></svg>`;
|
|
@@ -43,7 +62,7 @@ const classes = ["ui-copy-chip", className].filter(Boolean).join(" ");
|
|
|
43
62
|
try {
|
|
44
63
|
await navigator.clipboard.writeText(text);
|
|
45
64
|
btn.innerHTML = CHECK_ICON;
|
|
46
|
-
btn.setAttribute("aria-label",
|
|
65
|
+
btn.setAttribute("aria-label", copiedLabel);
|
|
47
66
|
setTimeout(() => {
|
|
48
67
|
btn.innerHTML = COPY_ICON;
|
|
49
68
|
btn.setAttribute("aria-label", label);
|
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">
|