@kwtv/components 0.5.0 → 0.6.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/package.json +2 -2
- package/src/astro/Badge.astro +5 -1
- package/src/astro/Checkbox.astro +22 -4
- package/src/astro/CopyChip.astro +128 -0
- package/src/styles/base.css +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kwtv/components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@kwtv/tokens": "^0.
|
|
37
|
+
"@kwtv/tokens": "^0.6.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/astro/Badge.astro
CHANGED
|
@@ -12,6 +12,9 @@ import Icon from "./Icon.astro";
|
|
|
12
12
|
*/
|
|
13
13
|
interface Props {
|
|
14
14
|
tone?: "neutral" | "success" | "warning" | "danger" | "outline";
|
|
15
|
+
/** Show the tone's default status icon. Defaults to true for status tones
|
|
16
|
+
* (success/warning/danger); pass false to suppress it for label-only use. */
|
|
17
|
+
icon?: boolean;
|
|
15
18
|
removable?: boolean;
|
|
16
19
|
/** aria-label for the × button when removable. Name the filter it clears. */
|
|
17
20
|
removeLabel?: string;
|
|
@@ -20,6 +23,7 @@ interface Props {
|
|
|
20
23
|
|
|
21
24
|
const {
|
|
22
25
|
tone = "neutral",
|
|
26
|
+
icon: showIcon = true,
|
|
23
27
|
removable = false,
|
|
24
28
|
removeLabel = "Verwijderen",
|
|
25
29
|
class: className,
|
|
@@ -31,7 +35,7 @@ const icons: Record<string, string> = {
|
|
|
31
35
|
warning: "alert-triangle",
|
|
32
36
|
danger: "close",
|
|
33
37
|
};
|
|
34
|
-
const icon = icons[tone];
|
|
38
|
+
const icon = showIcon ? icons[tone] : undefined;
|
|
35
39
|
|
|
36
40
|
const classes = [
|
|
37
41
|
"ui-badge",
|
package/src/astro/Checkbox.astro
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Checkbox.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Checkbox. Custom-styled native control: appearance:none box with a CSS
|
|
4
|
+
* checkmark SVG, so the checked state is consistent across surfaces and themes.
|
|
5
|
+
* Wrapped in a label so the whole row is the hit area. 17 px box, 32 px row.
|
|
6
|
+
* Disabled rows keep their text legible.
|
|
6
7
|
*
|
|
7
8
|
* When several checkboxes form a question, put them in a <fieldset> with a
|
|
8
9
|
* <legend> in the consuming page so the question is announced first.
|
|
@@ -62,12 +63,28 @@ const classes = ["ui-check", disabled && "is-disabled", className]
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
.ui-check input {
|
|
66
|
+
appearance: none;
|
|
67
|
+
-webkit-appearance: none;
|
|
65
68
|
width: 17px;
|
|
66
69
|
height: 17px;
|
|
67
70
|
flex-shrink: 0;
|
|
68
71
|
margin-top: calc((var(--leading-sm, 1.375rem) - 17px) / 2);
|
|
69
|
-
|
|
72
|
+
border: 1.5px solid var(--border-strong);
|
|
73
|
+
border-radius: var(--radius-xs, 3px);
|
|
74
|
+
background: var(--surface);
|
|
70
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;
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
.ui-check input:focus-visible {
|
|
@@ -83,6 +100,7 @@ const classes = ["ui-check", disabled && "is-disabled", className]
|
|
|
83
100
|
|
|
84
101
|
.ui-check.is-disabled input {
|
|
85
102
|
cursor: not-allowed;
|
|
103
|
+
opacity: 0.45;
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
@media (max-width: 700px), (pointer: coarse) {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* CopyChip. Displays a short text value (typically an email or code string)
|
|
4
|
+
* in a monospace pill with a copy-to-clipboard button. Renders the text as
|
|
5
|
+
* static HTML; the button is injected client-side by the bundled script, so
|
|
6
|
+
* the component degrades cleanly without JavaScript.
|
|
7
|
+
*
|
|
8
|
+
* @prop {string} text The value to display and copy.
|
|
9
|
+
* @prop {string} [label] Accessible label for the copy button (default: "Copy").
|
|
10
|
+
* @prop {string} [class]
|
|
11
|
+
*/
|
|
12
|
+
interface Props {
|
|
13
|
+
text: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { text, label = "Copy", class: className, ...rest } = Astro.props;
|
|
19
|
+
const classes = ["ui-copy-chip", className].filter(Boolean).join(" ");
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<span class={classes} data-copy={text} {...rest}>
|
|
23
|
+
<span class="ui-copy-chip__text">{text}</span>
|
|
24
|
+
</span>
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
function initCopyChip(el: HTMLElement) {
|
|
28
|
+
if (el.querySelector(".ui-copy-chip__btn")) return;
|
|
29
|
+
|
|
30
|
+
const text = el.getAttribute("data-copy") ?? "";
|
|
31
|
+
const label = el.getAttribute("data-label") ?? "Copy";
|
|
32
|
+
|
|
33
|
+
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
|
+
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>`;
|
|
35
|
+
|
|
36
|
+
const btn = document.createElement("button");
|
|
37
|
+
btn.className = "ui-copy-chip__btn";
|
|
38
|
+
btn.type = "button";
|
|
39
|
+
btn.setAttribute("aria-label", label);
|
|
40
|
+
btn.innerHTML = COPY_ICON;
|
|
41
|
+
|
|
42
|
+
btn.addEventListener("click", async () => {
|
|
43
|
+
try {
|
|
44
|
+
await navigator.clipboard.writeText(text);
|
|
45
|
+
btn.innerHTML = CHECK_ICON;
|
|
46
|
+
btn.setAttribute("aria-label", "Copied");
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
btn.innerHTML = COPY_ICON;
|
|
49
|
+
btn.setAttribute("aria-label", label);
|
|
50
|
+
}, 2000);
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
// Clipboard API unavailable.
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
el.appendChild(btn);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function initAll(root: Document | HTMLElement) {
|
|
60
|
+
root
|
|
61
|
+
.querySelectorAll<HTMLElement>(".ui-copy-chip:not([data-copy-ready])")
|
|
62
|
+
.forEach((el) => {
|
|
63
|
+
el.setAttribute("data-copy-ready", "");
|
|
64
|
+
initCopyChip(el);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
initAll(document);
|
|
69
|
+
document.addEventListener("astro:page-load", () => initAll(document));
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
@layer reset, tokens, base, components, motion, utilities;
|
|
74
|
+
|
|
75
|
+
@layer components {
|
|
76
|
+
.ui-copy-chip {
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--space-1);
|
|
80
|
+
padding: 1px var(--space-2);
|
|
81
|
+
border: 1px solid var(--border);
|
|
82
|
+
border-radius: var(--radius-sm);
|
|
83
|
+
background: var(--surface-hover);
|
|
84
|
+
font-family: var(--font-mono, monospace);
|
|
85
|
+
font-size: 0.875em;
|
|
86
|
+
color: var(--text);
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
vertical-align: middle;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ui-copy-chip__text {
|
|
92
|
+
user-select: all;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.ui-copy-chip__btn {
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
width: 20px;
|
|
100
|
+
height: 20px;
|
|
101
|
+
padding: 0;
|
|
102
|
+
border: none;
|
|
103
|
+
border-radius: var(--radius-xs);
|
|
104
|
+
background: transparent;
|
|
105
|
+
color: var(--text-muted);
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
flex-shrink: 0;
|
|
108
|
+
transition:
|
|
109
|
+
color var(--duration-fast, 100ms) var(--ease-standard, ease),
|
|
110
|
+
background-color var(--duration-fast, 100ms) var(--ease-standard, ease);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ui-copy-chip__btn:hover {
|
|
114
|
+
color: var(--text);
|
|
115
|
+
background: var(--border);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ui-copy-chip__btn:focus-visible {
|
|
119
|
+
outline: 2px solid var(--focus);
|
|
120
|
+
outline-offset: 2px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ui-copy-chip__btn svg {
|
|
124
|
+
width: 13px;
|
|
125
|
+
height: 13px;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</style>
|
package/src/styles/base.css
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
@layer reset, tokens, base, components, motion, utilities;
|
|
11
11
|
|
|
12
12
|
@layer base {
|
|
13
|
+
/* Text selection: sage fill instead of the browser default blue. */
|
|
14
|
+
::selection {
|
|
15
|
+
background-color: var(--surface-accent);
|
|
16
|
+
color: var(--surface-accent-fg);
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
/* Screen-reader-only, still announced. */
|
|
14
20
|
.ui-sr-only {
|
|
15
21
|
position: absolute;
|