@volksverpetzer/ui-web 0.6.0 → 0.8.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/Alert.css ADDED
@@ -0,0 +1,14 @@
1
+ .vvp-ui-alert {
2
+ border-radius: var(--vvp-radius-sm, 8px);
3
+ border: 1px solid var(--vvp-surface-input, #badde8);
4
+ background: var(--vvp-background, #fff);
5
+ color: var(--vvp-text, #111);
6
+ padding: var(--vvp-spacing-md, 10px) var(--vvp-spacing-lg, 16px);
7
+ font-size: var(--vvp-font-size-sm, 14px);
8
+ line-height: 1.5;
9
+ }
10
+
11
+ .vvp-ui-alert--error {
12
+ border-color: var(--vvp-error, #c62828);
13
+ color: var(--vvp-error, #c62828);
14
+ }
@@ -0,0 +1,13 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import "./Alert.css";
3
+ export type AlertVariant = "neutral" | "error";
4
+ export interface AlertProps extends HTMLAttributes<HTMLDivElement> {
5
+ /** @default "neutral" */
6
+ variant?: AlertVariant;
7
+ }
8
+ /**
9
+ * Inline form/status feedback — validation errors, submission results.
10
+ * Plain container like `Card`; put whatever content (text, a title, an
11
+ * icon) you need inside.
12
+ */
13
+ export declare function Alert({ variant, className, ...rest }: AlertProps): import("react").JSX.Element;
package/dist/Alert.js ADDED
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "./Alert.css";
3
+ /**
4
+ * Inline form/status feedback — validation errors, submission results.
5
+ * Plain container like `Card`; put whatever content (text, a title, an
6
+ * icon) you need inside.
7
+ */
8
+ export function Alert({ variant = "neutral", className, ...rest }) {
9
+ const classes = [
10
+ "vvp-ui-alert",
11
+ variant === "error" && "vvp-ui-alert--error",
12
+ className,
13
+ ]
14
+ .filter(Boolean)
15
+ .join(" ");
16
+ return _jsx("div", { role: "alert", className: classes, ...rest });
17
+ }
package/dist/Input.css ADDED
@@ -0,0 +1,52 @@
1
+ /*
2
+ * radius/border-color/padding are pinned to whichever existing token lands
3
+ * closest to vvp_crowdfunding's donation-form input (the component's actual
4
+ * first real consumer, pre-dating this package): radius 14px sits exactly
5
+ * between md/lg, resolved to md since that's the scale's own "buttons and
6
+ * form controls" step; padding 14px/16px both round to lg; the border color
7
+ * (#d8e6ee) is closer to --vvp-surface than the likelier-sounding
8
+ * --vvp-surface-input by actual RGB distance (~16 vs ~32).
9
+ */
10
+ .vvp-ui-input {
11
+ display: block;
12
+ box-sizing: border-box;
13
+ width: 100%;
14
+ min-width: 0;
15
+ border-radius: var(--vvp-radius-md, 12px);
16
+ border: 1px solid var(--vvp-surface, #e2f0f5);
17
+ background: var(--vvp-background, #fff);
18
+ color: var(--vvp-text, #111);
19
+ padding: var(--vvp-spacing-lg, 16px);
20
+ font-family: inherit;
21
+ font-size: var(--vvp-font-size-base, 16px);
22
+ line-height: 1.4;
23
+ transition: border-color 0.15s ease;
24
+ }
25
+
26
+ .vvp-ui-input::placeholder {
27
+ color: var(--vvp-text-muted, #666);
28
+ }
29
+
30
+ .vvp-ui-input:focus-visible {
31
+ outline: 2px solid var(--vvp-primary-muted, #3893c0);
32
+ outline-offset: 2px;
33
+ }
34
+
35
+ .vvp-ui-input:disabled {
36
+ background: var(--vvp-surface-disabled, #ddd);
37
+ color: var(--vvp-text-muted, #666);
38
+ cursor: not-allowed;
39
+ }
40
+
41
+ /*
42
+ * Consumers flag a failed-validation field via the native aria-invalid
43
+ * attribute rather than a bespoke prop — most form libraries already set
44
+ * it automatically, and it's the accessible way to mark the field anyway.
45
+ */
46
+ .vvp-ui-input[aria-invalid="true"] {
47
+ border-color: var(--vvp-error, #c62828);
48
+ }
49
+
50
+ .vvp-ui-input[aria-invalid="true"]:focus-visible {
51
+ outline-color: var(--vvp-error, #c62828);
52
+ }
@@ -0,0 +1,11 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+ import "./Input.css";
3
+ export type InputProps = InputHTMLAttributes<HTMLInputElement>;
4
+ /**
5
+ * Shared brand text input. Styled entirely off the `--vvp-*` custom
6
+ * properties emitted by `@volksverpetzer/design-tokens`, same as `Button`.
7
+ * Mark a field invalid with the native `aria-invalid` attribute rather than
8
+ * a bespoke `invalid` prop, so it composes with whatever validation a
9
+ * consumer already has (react-hook-form, Formik, or hand-rolled).
10
+ */
11
+ export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
package/dist/Input.js ADDED
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import "./Input.css";
4
+ /**
5
+ * Shared brand text input. Styled entirely off the `--vvp-*` custom
6
+ * properties emitted by `@volksverpetzer/design-tokens`, same as `Button`.
7
+ * Mark a field invalid with the native `aria-invalid` attribute rather than
8
+ * a bespoke `invalid` prop, so it composes with whatever validation a
9
+ * consumer already has (react-hook-form, Formik, or hand-rolled).
10
+ */
11
+ export const Input = forwardRef(function Input({ className, ...rest }, ref) {
12
+ const classes = ["vvp-ui-input", className].filter(Boolean).join(" ");
13
+ return _jsx("input", { ref: ref, className: classes, ...rest });
14
+ });
@@ -0,0 +1,60 @@
1
+ .vvp-ui-slider {
2
+ -webkit-appearance: none;
3
+ appearance: none;
4
+ width: 100%;
5
+ height: var(--vvp-spacing-sm, 8px);
6
+ margin: 0;
7
+ border-radius: var(--vvp-radius-full, 9999px);
8
+ background: linear-gradient(
9
+ to right,
10
+ var(--vvp-primary, #1b7194) 0%,
11
+ var(--vvp-primary, #1b7194) var(--vvp-ui-slider-fill, 0%),
12
+ var(--vvp-surface, #e2f0f5) var(--vvp-ui-slider-fill, 0%),
13
+ var(--vvp-surface, #e2f0f5) 100%
14
+ );
15
+ cursor: pointer;
16
+ }
17
+
18
+ .vvp-ui-slider:disabled {
19
+ opacity: 0.6;
20
+ cursor: not-allowed;
21
+ }
22
+
23
+ /*
24
+ * Thumb: duplicated across the -webkit-/-moz- pseudo-elements because
25
+ * browsers drop an entire rule if any selector in it is unsupported — they
26
+ * can't be combined into one shared rule.
27
+ */
28
+ .vvp-ui-slider::-webkit-slider-thumb {
29
+ -webkit-appearance: none;
30
+ appearance: none;
31
+ width: var(--vvp-spacing-lg, 16px);
32
+ height: var(--vvp-spacing-lg, 16px);
33
+ border-radius: var(--vvp-radius-full, 9999px);
34
+ background: var(--vvp-primary, #1b7194);
35
+ border: 2px solid var(--vvp-background, #fff);
36
+ box-shadow: var(--vvp-elevation-xs, 0px 1px 1.41px rgba(0, 0, 0, 0.2));
37
+ cursor: pointer;
38
+ }
39
+
40
+ .vvp-ui-slider::-moz-range-thumb {
41
+ width: var(--vvp-spacing-lg, 16px);
42
+ height: var(--vvp-spacing-lg, 16px);
43
+ border-radius: var(--vvp-radius-full, 9999px);
44
+ background: var(--vvp-primary, #1b7194);
45
+ border: 2px solid var(--vvp-background, #fff);
46
+ box-shadow: var(--vvp-elevation-xs, 0px 1px 1.41px rgba(0, 0, 0, 0.2));
47
+ cursor: pointer;
48
+ }
49
+
50
+ .vvp-ui-slider::-moz-range-track {
51
+ background: transparent;
52
+ }
53
+
54
+ .vvp-ui-slider:disabled::-webkit-slider-thumb {
55
+ cursor: not-allowed;
56
+ }
57
+
58
+ .vvp-ui-slider:disabled::-moz-range-thumb {
59
+ cursor: not-allowed;
60
+ }
@@ -0,0 +1,17 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+ import "./Slider.css";
3
+ export interface SliderProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue" | "onChange"> {
4
+ value: number;
5
+ min?: number;
6
+ max: number;
7
+ step?: number;
8
+ onValueChange?: (value: number) => void;
9
+ }
10
+ /**
11
+ * Single-thumb range control — audio/video scrubbing, volume, numeric
12
+ * filters. A native `<input type="range">` under the hood (keyboard, touch,
13
+ * and screen-reader support come for free) themed with `--vvp-*` tokens.
14
+ * The filled portion up to the thumb is a CSS gradient driven by a custom
15
+ * property computed from `value`, so no extra DOM is needed for the track.
16
+ */
17
+ export declare const Slider: import("react").ForwardRefExoticComponent<SliderProps & import("react").RefAttributes<HTMLInputElement>>;
package/dist/Slider.js ADDED
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import "./Slider.css";
4
+ /**
5
+ * Single-thumb range control — audio/video scrubbing, volume, numeric
6
+ * filters. A native `<input type="range">` under the hood (keyboard, touch,
7
+ * and screen-reader support come for free) themed with `--vvp-*` tokens.
8
+ * The filled portion up to the thumb is a CSS gradient driven by a custom
9
+ * property computed from `value`, so no extra DOM is needed for the track.
10
+ */
11
+ export const Slider = forwardRef(function Slider({ value, min = 0, max, step = 1, onValueChange, className, style, ...rest }, ref) {
12
+ const rawPercent = max > min ? ((value - min) / (max - min)) * 100 : 0;
13
+ const percent = Math.min(100, Math.max(0, rawPercent));
14
+ const classes = ["vvp-ui-slider", className].filter(Boolean).join(" ");
15
+ const fillStyle = {
16
+ ...style,
17
+ "--vvp-ui-slider-fill": `${percent}%`,
18
+ };
19
+ return (_jsx("input", { ref: ref, type: "range", className: classes, value: value, min: min, max: max, step: step, style: fillStyle, onChange: (event) => onValueChange?.(event.target.valueAsNumber), ...rest }));
20
+ });
@@ -0,0 +1,6 @@
1
+ .vvp-ui-theme-toggle {
2
+ position: fixed;
3
+ top: var(--vvp-spacing-lg, 16px);
4
+ right: var(--vvp-spacing-lg, 16px);
5
+ z-index: 50;
6
+ }
@@ -0,0 +1,9 @@
1
+ import "./ThemeToggle.css";
2
+ /**
3
+ * Fixed-corner light/dark toggle. Persists to localStorage, falls back to
4
+ * `prefers-color-scheme`, and flips the `dark` class on `<html>` — the same
5
+ * convention the `--vvp-*` tokens' `.dark` scope expects. Browser-only (no
6
+ * Divi/React Native use case), so it always renders — there's no server
7
+ * variant to opt out of.
8
+ */
9
+ export declare function ThemeToggle(): import("react").JSX.Element;
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useSyncExternalStore } from "react";
4
+ import { Button } from "./Button";
5
+ import "./ThemeToggle.css";
6
+ const STORAGE_KEY = "vvp-theme";
7
+ const listeners = new Set();
8
+ // Session fallback for when localStorage throws (private/sandboxed
9
+ // contexts): without it, setIsDark's write would fail silently and
10
+ // getIsDark would keep re-deriving the same value from matchMedia on every
11
+ // read, making the toggle look inert even though the click registered.
12
+ let memoryIsDark = null;
13
+ function getIsDark() {
14
+ if (memoryIsDark !== null)
15
+ return memoryIsDark;
16
+ try {
17
+ const stored = localStorage.getItem(STORAGE_KEY);
18
+ if (stored)
19
+ return stored === "dark";
20
+ }
21
+ catch {
22
+ // localStorage can throw (e.g. blocked storage in private/sandboxed
23
+ // contexts) — fall back to OS preference below.
24
+ }
25
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
26
+ }
27
+ // The server can't know the stored/OS preference, so it always reports
28
+ // light. useSyncExternalStore uses this consistently during hydration
29
+ // (matching the server-rendered markup exactly), then re-renders with the
30
+ // real client value right after — no hydration mismatch, unlike reading
31
+ // localStorage in a mount effect.
32
+ function getServerIsDark() {
33
+ return false;
34
+ }
35
+ function subscribe(callback) {
36
+ listeners.add(callback);
37
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
38
+ media.addEventListener("change", callback);
39
+ return () => {
40
+ listeners.delete(callback);
41
+ media.removeEventListener("change", callback);
42
+ };
43
+ }
44
+ function setIsDark(next) {
45
+ memoryIsDark = next;
46
+ try {
47
+ localStorage.setItem(STORAGE_KEY, next ? "dark" : "light");
48
+ }
49
+ catch {
50
+ // Storage unavailable — the toggle still works for this session via
51
+ // memoryIsDark above, it just won't persist across reloads.
52
+ }
53
+ listeners.forEach((listener) => listener());
54
+ }
55
+ function applyTheme(dark) {
56
+ document.documentElement.classList.toggle("dark", dark);
57
+ }
58
+ const SunIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2" }), _jsx("path", { d: "M12 20v2" }), _jsx("path", { d: "m4.93 4.93 1.41 1.41" }), _jsx("path", { d: "m17.66 17.66 1.41 1.41" }), _jsx("path", { d: "M2 12h2" }), _jsx("path", { d: "M20 12h2" }), _jsx("path", { d: "m6.34 17.66-1.41 1.41" }), _jsx("path", { d: "m19.07 4.93-1.41 1.41" })] }));
59
+ const MoonIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }) }));
60
+ /**
61
+ * Fixed-corner light/dark toggle. Persists to localStorage, falls back to
62
+ * `prefers-color-scheme`, and flips the `dark` class on `<html>` — the same
63
+ * convention the `--vvp-*` tokens' `.dark` scope expects. Browser-only (no
64
+ * Divi/React Native use case), so it always renders — there's no server
65
+ * variant to opt out of.
66
+ */
67
+ export function ThemeToggle() {
68
+ const isDark = useSyncExternalStore(subscribe, getIsDark, getServerIsDark);
69
+ // Only synchronizes the DOM with the resolved value — never calls
70
+ // setState, so it can't trigger cascading renders.
71
+ useEffect(() => {
72
+ applyTheme(isDark);
73
+ }, [isDark]);
74
+ return (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsDark(!isDark), "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", className: "vvp-ui-theme-toggle", children: isDark ? _jsx(SunIcon, {}) : _jsx(MoonIcon, {}) }));
75
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,7 @@ export { Button, type ButtonProps, type ButtonVariant, type ButtonSize, } from "
2
2
  export { Badge, type BadgeProps, type BadgeVariant } from "./Badge";
3
3
  export { Card, type CardProps } from "./Card";
4
4
  export { ProgressBar, type ProgressBarProps, type ProgressBarMilestone, } from "./ProgressBar";
5
+ export { Input, type InputProps } from "./Input";
6
+ export { Alert, type AlertProps, type AlertVariant } from "./Alert";
7
+ export { Slider, type SliderProps } from "./Slider";
8
+ export { ThemeToggle } from "./ThemeToggle";
package/dist/index.js CHANGED
@@ -2,3 +2,7 @@ export { Button, } from "./Button";
2
2
  export { Badge } from "./Badge";
3
3
  export { Card } from "./Card";
4
4
  export { ProgressBar, } from "./ProgressBar";
5
+ export { Input } from "./Input";
6
+ export { Alert } from "./Alert";
7
+ export { Slider } from "./Slider";
8
+ export { ThemeToggle } from "./ThemeToggle";
package/dist/styles.css CHANGED
@@ -1,3 +1,18 @@
1
+ .vvp-ui-alert {
2
+ border-radius: var(--vvp-radius-sm, 8px);
3
+ border: 1px solid var(--vvp-surface-input, #badde8);
4
+ background: var(--vvp-background, #fff);
5
+ color: var(--vvp-text, #111);
6
+ padding: var(--vvp-spacing-md, 10px) var(--vvp-spacing-lg, 16px);
7
+ font-size: var(--vvp-font-size-sm, 14px);
8
+ line-height: 1.5;
9
+ }
10
+
11
+ .vvp-ui-alert--error {
12
+ border-color: var(--vvp-error, #c62828);
13
+ color: var(--vvp-error, #c62828);
14
+ }
15
+
1
16
  .vvp-ui-badge {
2
17
  display: inline-flex;
3
18
  align-items: center;
@@ -148,6 +163,59 @@
148
163
  border: 1px solid var(--vvp-surface-input, #badde8);
149
164
  }
150
165
 
166
+ /*
167
+ * radius/border-color/padding are pinned to whichever existing token lands
168
+ * closest to vvp_crowdfunding's donation-form input (the component's actual
169
+ * first real consumer, pre-dating this package): radius 14px sits exactly
170
+ * between md/lg, resolved to md since that's the scale's own "buttons and
171
+ * form controls" step; padding 14px/16px both round to lg; the border color
172
+ * (#d8e6ee) is closer to --vvp-surface than the likelier-sounding
173
+ * --vvp-surface-input by actual RGB distance (~16 vs ~32).
174
+ */
175
+ .vvp-ui-input {
176
+ display: block;
177
+ box-sizing: border-box;
178
+ width: 100%;
179
+ min-width: 0;
180
+ border-radius: var(--vvp-radius-md, 12px);
181
+ border: 1px solid var(--vvp-surface, #e2f0f5);
182
+ background: var(--vvp-background, #fff);
183
+ color: var(--vvp-text, #111);
184
+ padding: var(--vvp-spacing-lg, 16px);
185
+ font-family: inherit;
186
+ font-size: var(--vvp-font-size-base, 16px);
187
+ line-height: 1.4;
188
+ transition: border-color 0.15s ease;
189
+ }
190
+
191
+ .vvp-ui-input::placeholder {
192
+ color: var(--vvp-text-muted, #666);
193
+ }
194
+
195
+ .vvp-ui-input:focus-visible {
196
+ outline: 2px solid var(--vvp-primary-muted, #3893c0);
197
+ outline-offset: 2px;
198
+ }
199
+
200
+ .vvp-ui-input:disabled {
201
+ background: var(--vvp-surface-disabled, #ddd);
202
+ color: var(--vvp-text-muted, #666);
203
+ cursor: not-allowed;
204
+ }
205
+
206
+ /*
207
+ * Consumers flag a failed-validation field via the native aria-invalid
208
+ * attribute rather than a bespoke prop — most form libraries already set
209
+ * it automatically, and it's the accessible way to mark the field anyway.
210
+ */
211
+ .vvp-ui-input[aria-invalid="true"] {
212
+ border-color: var(--vvp-error, #c62828);
213
+ }
214
+
215
+ .vvp-ui-input[aria-invalid="true"]:focus-visible {
216
+ outline-color: var(--vvp-error, #c62828);
217
+ }
218
+
151
219
  .vvp-ui-progress {
152
220
  width: 100%;
153
221
  text-align: center;
@@ -231,3 +299,71 @@
231
299
  color: var(--vvp-text, #111);
232
300
  }
233
301
 
302
+ .vvp-ui-slider {
303
+ -webkit-appearance: none;
304
+ appearance: none;
305
+ width: 100%;
306
+ height: var(--vvp-spacing-sm, 8px);
307
+ margin: 0;
308
+ border-radius: var(--vvp-radius-full, 9999px);
309
+ background: linear-gradient(
310
+ to right,
311
+ var(--vvp-primary, #1b7194) 0%,
312
+ var(--vvp-primary, #1b7194) var(--vvp-ui-slider-fill, 0%),
313
+ var(--vvp-surface, #e2f0f5) var(--vvp-ui-slider-fill, 0%),
314
+ var(--vvp-surface, #e2f0f5) 100%
315
+ );
316
+ cursor: pointer;
317
+ }
318
+
319
+ .vvp-ui-slider:disabled {
320
+ opacity: 0.6;
321
+ cursor: not-allowed;
322
+ }
323
+
324
+ /*
325
+ * Thumb: duplicated across the -webkit-/-moz- pseudo-elements because
326
+ * browsers drop an entire rule if any selector in it is unsupported — they
327
+ * can't be combined into one shared rule.
328
+ */
329
+ .vvp-ui-slider::-webkit-slider-thumb {
330
+ -webkit-appearance: none;
331
+ appearance: none;
332
+ width: var(--vvp-spacing-lg, 16px);
333
+ height: var(--vvp-spacing-lg, 16px);
334
+ border-radius: var(--vvp-radius-full, 9999px);
335
+ background: var(--vvp-primary, #1b7194);
336
+ border: 2px solid var(--vvp-background, #fff);
337
+ box-shadow: var(--vvp-elevation-xs, 0px 1px 1.41px rgba(0, 0, 0, 0.2));
338
+ cursor: pointer;
339
+ }
340
+
341
+ .vvp-ui-slider::-moz-range-thumb {
342
+ width: var(--vvp-spacing-lg, 16px);
343
+ height: var(--vvp-spacing-lg, 16px);
344
+ border-radius: var(--vvp-radius-full, 9999px);
345
+ background: var(--vvp-primary, #1b7194);
346
+ border: 2px solid var(--vvp-background, #fff);
347
+ box-shadow: var(--vvp-elevation-xs, 0px 1px 1.41px rgba(0, 0, 0, 0.2));
348
+ cursor: pointer;
349
+ }
350
+
351
+ .vvp-ui-slider::-moz-range-track {
352
+ background: transparent;
353
+ }
354
+
355
+ .vvp-ui-slider:disabled::-webkit-slider-thumb {
356
+ cursor: not-allowed;
357
+ }
358
+
359
+ .vvp-ui-slider:disabled::-moz-range-thumb {
360
+ cursor: not-allowed;
361
+ }
362
+
363
+ .vvp-ui-theme-toggle {
364
+ position: fixed;
365
+ top: var(--vvp-spacing-lg, 16px);
366
+ right: var(--vvp-spacing-lg, 16px);
367
+ z-index: 50;
368
+ }
369
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volksverpetzer/ui-web",
3
- "version": "0.6.0",
4
- "description": "Shared brand-visible React components (Button, Badge, Card) for vvp_link_shortener, vvp_divi5_extensions, and crowdfunding. 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.",
3
+ "version": "0.8.0",
4
+ "description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle) 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",
7
7
  "repository": {