@volksverpetzer/ui-web 0.12.0 → 0.13.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/dist/Badge.css +28 -0
- package/dist/Button.css +2 -2
- package/dist/LinkButton.css +1 -1
- package/dist/PillGroup.css +52 -0
- package/dist/PillGroup.d.ts +25 -0
- package/dist/PillGroup.js +45 -0
- package/dist/ThemeToggle.css +9 -0
- package/dist/ToolPage.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +94 -4
- package/package.json +3 -3
package/dist/Badge.css
CHANGED
|
@@ -58,3 +58,31 @@
|
|
|
58
58
|
background: color-mix(in srgb, var(--vvp-pruefpunkt, #442674) 15%, white);
|
|
59
59
|
color: var(--vvp-pruefpunkt, #442674);
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* Dark mode: the light-mode formula above mixes toward literal `white`,
|
|
64
|
+
* which is theme-independent. Dark-mode brand colors are deliberately
|
|
65
|
+
* lightened for legibility on a near-black page, so mixed with `white` at
|
|
66
|
+
* 15% they lose too much contrast against their own text color (as low as
|
|
67
|
+
* ~2:1 for some variant/brand pairings, against a 4.5:1 requirement).
|
|
68
|
+
*
|
|
69
|
+
* Each variant below uses whichever tint direction and percentage clears
|
|
70
|
+
* 4.5:1 for both Volksverpetzer and Mimikama's dark palettes (verified with
|
|
71
|
+
* the WCAG contrast formula) — primary and accent read fine tinted toward
|
|
72
|
+
* white, error and pruefpunkt need black instead.
|
|
73
|
+
*/
|
|
74
|
+
.dark .vvp-ui-badge--primary {
|
|
75
|
+
background: color-mix(in srgb, var(--vvp-primary, #1b7194) 5%, white);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.dark .vvp-ui-badge--accent {
|
|
79
|
+
background: #fff;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.dark .vvp-ui-badge--error {
|
|
83
|
+
background: color-mix(in srgb, var(--vvp-error, #ef5350) 15%, black);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dark .vvp-ui-badge--pruefpunkt {
|
|
87
|
+
background: color-mix(in srgb, var(--vvp-pruefpunkt, #8b5cf6) 10%, black);
|
|
88
|
+
}
|
package/dist/Button.css
CHANGED
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
|
|
57
57
|
.vvp-ui-btn--ghost {
|
|
58
58
|
background: transparent;
|
|
59
|
-
color: var(--vvp-
|
|
60
|
-
border: 2px solid var(--vvp-
|
|
59
|
+
color: var(--vvp-link-text, #1b7194);
|
|
60
|
+
border: 2px solid var(--vvp-link-text, #1b7194);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
.vvp-ui-btn--ghost:hover:not(:disabled) {
|
package/dist/LinkButton.css
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
.vvp-ui-linkbtn {
|
|
14
14
|
border-radius: var(--vvp-radius-md, 12px);
|
|
15
15
|
background: transparent;
|
|
16
|
-
color: var(--vvp-
|
|
16
|
+
color: var(--vvp-link-text, #1b7194);
|
|
17
17
|
border: 1.5px solid var(--vvp-surface, #e2f0f5);
|
|
18
18
|
box-shadow: var(
|
|
19
19
|
--vvp-elevation-accent-rest,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.vvp-ui-pill-group {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.vvp-ui-pill-group__option {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
border: 2px solid var(--vvp-surface, #e2f0f5);
|
|
12
|
+
border-radius: var(--vvp-radius-full, 9999px);
|
|
13
|
+
background: var(--vvp-background, #fff);
|
|
14
|
+
color: var(--vvp-text, #111);
|
|
15
|
+
font-family: inherit;
|
|
16
|
+
font-weight: 700;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
transition:
|
|
19
|
+
background-color 0.15s ease,
|
|
20
|
+
border-color 0.15s ease,
|
|
21
|
+
color 0.15s ease;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.vvp-ui-pill-group[data-size="md"] .vvp-ui-pill-group__option {
|
|
25
|
+
padding: var(--vvp-spacing-md, 10px) var(--vvp-spacing-xl, 20px);
|
|
26
|
+
font-size: var(--vvp-font-size-base, 16px);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.vvp-ui-pill-group[data-size="sm"] .vvp-ui-pill-group__option {
|
|
30
|
+
padding: var(--vvp-spacing-sm, 8px) var(--vvp-spacing-lg, 16px);
|
|
31
|
+
font-size: var(--vvp-font-size-sm, 14px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.vvp-ui-pill-group__option:hover:not(:disabled) {
|
|
35
|
+
border-color: var(--vvp-primary-muted, #3893c0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vvp-ui-pill-group__option:focus-visible {
|
|
39
|
+
outline: 2px solid var(--vvp-primary-muted, #3893c0);
|
|
40
|
+
outline-offset: 2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.vvp-ui-pill-group__option:disabled {
|
|
44
|
+
opacity: 0.6;
|
|
45
|
+
cursor: not-allowed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.vvp-ui-pill-group__option[aria-checked="true"] {
|
|
49
|
+
background: var(--vvp-primary, #1b7194);
|
|
50
|
+
border-color: var(--vvp-primary, #1b7194);
|
|
51
|
+
color: var(--vvp-on-primary, #fff);
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import "./PillGroup.css";
|
|
2
|
+
export interface PillGroupOption {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PillGroupProps {
|
|
8
|
+
/** Visually hidden unless `label` is also rendered by the consumer via `aria-labelledby` — falls back to this as the accessible name. */
|
|
9
|
+
"aria-label"?: string;
|
|
10
|
+
options: PillGroupOption[];
|
|
11
|
+
value: string;
|
|
12
|
+
onChange: (value: string) => void;
|
|
13
|
+
size?: "sm" | "md";
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A row of pill-shaped options for choosing one of a small, fixed set of
|
|
19
|
+
* values — the house alternative to a native `<select>` (see vvp_app's
|
|
20
|
+
* "pills over dropdowns" convention, now ported to web). Implements the
|
|
21
|
+
* ARIA APG radiogroup pattern: one `role="radio"` per pill, roving
|
|
22
|
+
* tabindex, and Left/Right (and Up/Down) arrow keys move both focus and
|
|
23
|
+
* selection, matching native `<select>`/radio-group keyboard behavior.
|
|
24
|
+
*/
|
|
25
|
+
export declare function PillGroup({ "aria-label": ariaLabel, options, value, onChange, size, disabled, className, }: PillGroupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import "./PillGroup.css";
|
|
4
|
+
/**
|
|
5
|
+
* A row of pill-shaped options for choosing one of a small, fixed set of
|
|
6
|
+
* values — the house alternative to a native `<select>` (see vvp_app's
|
|
7
|
+
* "pills over dropdowns" convention, now ported to web). Implements the
|
|
8
|
+
* ARIA APG radiogroup pattern: one `role="radio"` per pill, roving
|
|
9
|
+
* tabindex, and Left/Right (and Up/Down) arrow keys move both focus and
|
|
10
|
+
* selection, matching native `<select>`/radio-group keyboard behavior.
|
|
11
|
+
*/
|
|
12
|
+
export function PillGroup({ "aria-label": ariaLabel, options, value, onChange, size = "md", disabled = false, className, }) {
|
|
13
|
+
const groupId = useId();
|
|
14
|
+
const selectNext = (fromIndex, direction) => {
|
|
15
|
+
const enabled = options
|
|
16
|
+
.map((opt, i) => ({ opt, i }))
|
|
17
|
+
.filter(({ opt }) => !opt.disabled);
|
|
18
|
+
if (enabled.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
const currentPos = enabled.findIndex(({ i }) => i === fromIndex);
|
|
21
|
+
const nextPos = (currentPos + direction + enabled.length) % enabled.length;
|
|
22
|
+
const next = enabled[nextPos];
|
|
23
|
+
if (next)
|
|
24
|
+
onChange(next.opt.value);
|
|
25
|
+
};
|
|
26
|
+
const handleKeyDown = (e, i) => {
|
|
27
|
+
switch (e.key) {
|
|
28
|
+
case "ArrowRight":
|
|
29
|
+
case "ArrowDown":
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
selectNext(i, 1);
|
|
32
|
+
break;
|
|
33
|
+
case "ArrowLeft":
|
|
34
|
+
case "ArrowUp":
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
selectNext(i, -1);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const classes = ["vvp-ui-pill-group", className].filter(Boolean).join(" ");
|
|
41
|
+
return (_jsx("div", { role: "radiogroup", "aria-label": ariaLabel, className: classes, "data-size": size, children: options.map((option, i) => {
|
|
42
|
+
const checked = option.value === value;
|
|
43
|
+
return (_jsx("button", { id: `${groupId}-${option.value}`, type: "button", role: "radio", "aria-checked": checked, disabled: disabled || option.disabled, tabIndex: checked ? 0 : -1, className: "vvp-ui-pill-group__option", onClick: () => onChange(option.value), onKeyDown: (e) => handleKeyDown(e, i), children: option.label }, option.value));
|
|
44
|
+
}) }));
|
|
45
|
+
}
|
package/dist/ThemeToggle.css
CHANGED
|
@@ -3,4 +3,13 @@
|
|
|
3
3
|
top: var(--vvp-spacing-lg, 16px);
|
|
4
4
|
right: var(--vvp-spacing-lg, 16px);
|
|
5
5
|
z-index: 50;
|
|
6
|
+
/* Ghost buttons default to a transparent background, which disappears
|
|
7
|
+
against busy page backgrounds (e.g. crowdfunding.volksverpetzer.de).
|
|
8
|
+
The toggle floats over arbitrary host content, so it needs an
|
|
9
|
+
explicit, opaque background rather than inheriting the ghost variant's. */
|
|
10
|
+
background: var(--vvp-background, #fff);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.vvp-ui-theme-toggle:hover:not(:disabled) {
|
|
14
|
+
background: var(--vvp-surface, #e2f0f5);
|
|
6
15
|
}
|
package/dist/ToolPage.css
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { Alert, type AlertProps, type AlertVariant } from "./Alert";
|
|
|
8
8
|
export { Slider, type SliderProps } from "./Slider";
|
|
9
9
|
export { ThemeToggle } from "./ThemeToggle";
|
|
10
10
|
export { InputButton, type InputButtonProps } from "./InputButton";
|
|
11
|
+
export { PillGroup, type PillGroupProps, type PillGroupOption, } from "./PillGroup";
|
|
11
12
|
export { ToolPage, type ToolPageProps, type ToolPageTag } from "./ToolPage";
|
package/dist/index.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -74,6 +74,34 @@
|
|
|
74
74
|
color: var(--vvp-pruefpunkt, #442674);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/*
|
|
78
|
+
* Dark mode: the light-mode formula above mixes toward literal `white`,
|
|
79
|
+
* which is theme-independent. Dark-mode brand colors are deliberately
|
|
80
|
+
* lightened for legibility on a near-black page, so mixed with `white` at
|
|
81
|
+
* 15% they lose too much contrast against their own text color (as low as
|
|
82
|
+
* ~2:1 for some variant/brand pairings, against a 4.5:1 requirement).
|
|
83
|
+
*
|
|
84
|
+
* Each variant below uses whichever tint direction and percentage clears
|
|
85
|
+
* 4.5:1 for both Volksverpetzer and Mimikama's dark palettes (verified with
|
|
86
|
+
* the WCAG contrast formula) — primary and accent read fine tinted toward
|
|
87
|
+
* white, error and pruefpunkt need black instead.
|
|
88
|
+
*/
|
|
89
|
+
.dark .vvp-ui-badge--primary {
|
|
90
|
+
background: color-mix(in srgb, var(--vvp-primary, #1b7194) 5%, white);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.dark .vvp-ui-badge--accent {
|
|
94
|
+
background: #fff;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.dark .vvp-ui-badge--error {
|
|
98
|
+
background: color-mix(in srgb, var(--vvp-error, #ef5350) 15%, black);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.dark .vvp-ui-badge--pruefpunkt {
|
|
102
|
+
background: color-mix(in srgb, var(--vvp-pruefpunkt, #8b5cf6) 10%, black);
|
|
103
|
+
}
|
|
104
|
+
|
|
77
105
|
.vvp-ui-btn {
|
|
78
106
|
display: inline-flex;
|
|
79
107
|
align-items: center;
|
|
@@ -132,8 +160,8 @@
|
|
|
132
160
|
|
|
133
161
|
.vvp-ui-btn--ghost {
|
|
134
162
|
background: transparent;
|
|
135
|
-
color: var(--vvp-
|
|
136
|
-
border: 2px solid var(--vvp-
|
|
163
|
+
color: var(--vvp-link-text, #1b7194);
|
|
164
|
+
border: 2px solid var(--vvp-link-text, #1b7194);
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
.vvp-ui-btn--ghost:hover:not(:disabled) {
|
|
@@ -258,7 +286,7 @@
|
|
|
258
286
|
.vvp-ui-linkbtn {
|
|
259
287
|
border-radius: var(--vvp-radius-md, 12px);
|
|
260
288
|
background: transparent;
|
|
261
|
-
color: var(--vvp-
|
|
289
|
+
color: var(--vvp-link-text, #1b7194);
|
|
262
290
|
border: 1.5px solid var(--vvp-surface, #e2f0f5);
|
|
263
291
|
box-shadow: var(
|
|
264
292
|
--vvp-elevation-accent-rest,
|
|
@@ -281,6 +309,59 @@
|
|
|
281
309
|
);
|
|
282
310
|
}
|
|
283
311
|
|
|
312
|
+
.vvp-ui-pill-group {
|
|
313
|
+
display: inline-flex;
|
|
314
|
+
flex-wrap: wrap;
|
|
315
|
+
gap: var(--vvp-spacing-sm, 8px);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.vvp-ui-pill-group__option {
|
|
319
|
+
display: inline-flex;
|
|
320
|
+
align-items: center;
|
|
321
|
+
justify-content: center;
|
|
322
|
+
border: 2px solid var(--vvp-surface, #e2f0f5);
|
|
323
|
+
border-radius: var(--vvp-radius-full, 9999px);
|
|
324
|
+
background: var(--vvp-background, #fff);
|
|
325
|
+
color: var(--vvp-text, #111);
|
|
326
|
+
font-family: inherit;
|
|
327
|
+
font-weight: 700;
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
transition:
|
|
330
|
+
background-color 0.15s ease,
|
|
331
|
+
border-color 0.15s ease,
|
|
332
|
+
color 0.15s ease;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.vvp-ui-pill-group[data-size="md"] .vvp-ui-pill-group__option {
|
|
336
|
+
padding: var(--vvp-spacing-md, 10px) var(--vvp-spacing-xl, 20px);
|
|
337
|
+
font-size: var(--vvp-font-size-base, 16px);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.vvp-ui-pill-group[data-size="sm"] .vvp-ui-pill-group__option {
|
|
341
|
+
padding: var(--vvp-spacing-sm, 8px) var(--vvp-spacing-lg, 16px);
|
|
342
|
+
font-size: var(--vvp-font-size-sm, 14px);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.vvp-ui-pill-group__option:hover:not(:disabled) {
|
|
346
|
+
border-color: var(--vvp-primary-muted, #3893c0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.vvp-ui-pill-group__option:focus-visible {
|
|
350
|
+
outline: 2px solid var(--vvp-primary-muted, #3893c0);
|
|
351
|
+
outline-offset: 2px;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.vvp-ui-pill-group__option:disabled {
|
|
355
|
+
opacity: 0.6;
|
|
356
|
+
cursor: not-allowed;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.vvp-ui-pill-group__option[aria-checked="true"] {
|
|
360
|
+
background: var(--vvp-primary, #1b7194);
|
|
361
|
+
border-color: var(--vvp-primary, #1b7194);
|
|
362
|
+
color: var(--vvp-on-primary, #fff);
|
|
363
|
+
}
|
|
364
|
+
|
|
284
365
|
.vvp-ui-progress {
|
|
285
366
|
width: 100%;
|
|
286
367
|
text-align: center;
|
|
@@ -430,6 +511,15 @@
|
|
|
430
511
|
top: var(--vvp-spacing-lg, 16px);
|
|
431
512
|
right: var(--vvp-spacing-lg, 16px);
|
|
432
513
|
z-index: 50;
|
|
514
|
+
/* Ghost buttons default to a transparent background, which disappears
|
|
515
|
+
against busy page backgrounds (e.g. crowdfunding.volksverpetzer.de).
|
|
516
|
+
The toggle floats over arbitrary host content, so it needs an
|
|
517
|
+
explicit, opaque background rather than inheriting the ghost variant's. */
|
|
518
|
+
background: var(--vvp-background, #fff);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.vvp-ui-theme-toggle:hover:not(:disabled) {
|
|
522
|
+
background: var(--vvp-surface, #e2f0f5);
|
|
433
523
|
}
|
|
434
524
|
|
|
435
525
|
.vvp-ui-tool-page {
|
|
@@ -556,7 +646,7 @@
|
|
|
556
646
|
.vvp-ui-tool-page__icon {
|
|
557
647
|
display: inline-flex;
|
|
558
648
|
align-items: center;
|
|
559
|
-
color: var(--vvp-
|
|
649
|
+
color: var(--vvp-link-text, #1b7194);
|
|
560
650
|
}
|
|
561
651
|
|
|
562
652
|
.vvp-ui-tool-page__tags {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volksverpetzer/ui-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle, InputButton) for vvp_link_shortener, vvp_divi5_extensions, crowdfunding, and vectorcrawl. Plain, hand-scoped CSS (vvp-ui-* class prefix) consuming the --vvp-* custom properties emitted by @volksverpetzer/design-tokens — no Tailwind classes, so it drops into a Tailwind app and Divi's WordPress-embedded CSS alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"react": ">=19"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/react": "^19.
|
|
31
|
-
"react": "^19.
|
|
30
|
+
"@types/react": "^19.3.0",
|
|
31
|
+
"react": "^19.3.0",
|
|
32
32
|
"typescript": "^6.0.3"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|